qemu/hw/gpio/nrf51_gpio.c
Chris Laplante 8d6b2f947d hw/gpio/nrf51: implement DETECT signal
Implement nRF51 DETECT signal in the GPIO peripheral.

The reference manual makes mention of a per-pin DETECT signal, but these
are not exposed to the user. See https://devzone.nordicsemi.com/f/nordic-q-a/39858/gpio-per-pin-detect-signal-available
for more information. Currently, I don't see a reason to model these.

Signed-off-by: Chris Laplante <chris@laplante.io>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Message-id: 20230728160324.1159090-2-chris@laplante.io
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2023-08-22 17:30:59 +01:00

331 lines
8.8 KiB
C

/*
* 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 "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();
break;
}
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 = (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;
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)