302 lines
8.9 KiB
C
302 lines
8.9 KiB
C
/*
|
|
* ESP32 GPIO emulation
|
|
*
|
|
* Copyright (c) 2019 Espressif Systems (Shanghai) Co. Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/visitor.h"
|
|
#include "hw/hw.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/registerfields.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/gpio/esp32_gpio.h"
|
|
|
|
#define GPIO_OUT 0x04
|
|
#define GPIO_OUT_W1TS 0x08
|
|
#define GPIO_OUT_W1TC 0x0c
|
|
#define GPIO_ENABLE 0x20
|
|
#define GPIO_ENABLE_W1TS 0x24
|
|
#define GPIO_ENABLE_W1TC 0x28
|
|
#define GPIO_IN 0x3c
|
|
#define GPIO_STATUS 0x44
|
|
#define GPIO_STATUS_W1TS 0x48
|
|
#define GPIO_STATUS_W1TC 0x4c
|
|
#define GPIO_PCPU_INT 0x5c
|
|
|
|
#define GPIO_PIN_INT_TYPE_SHIFT 7
|
|
#define GPIO_PIN_INT_TYPE_MASK 0x7
|
|
#define GPIO_PIN_INT_ENA_SHIFT 13
|
|
#define GPIO_PIN_INT_ENA_CPU0 BIT(0)
|
|
|
|
#define GPIO_INTR_DISABLE 0
|
|
#define GPIO_INTR_POSEDGE 1
|
|
#define GPIO_INTR_NEGEDGE 2
|
|
#define GPIO_INTR_ANYEDGE 3
|
|
#define GPIO_INTR_LOW_LEVEL 4
|
|
#define GPIO_INTR_HIGH_LEVEL 5
|
|
|
|
static void esp32_gpio_drive_outputs(Esp32GpioState *s)
|
|
{
|
|
for (int i = 0; i < ESP32_GPIO_COUNT; i++) {
|
|
int level = (s->output_enable & BIT(i))
|
|
? !!(s->output_level & BIT(i))
|
|
: 1;
|
|
qemu_set_irq(s->output_lines[i], level);
|
|
}
|
|
}
|
|
|
|
static uint32_t esp32_gpio_enabled_interrupts(Esp32GpioState *s)
|
|
{
|
|
uint32_t enabled = 0;
|
|
|
|
for (int pin = 0; pin < ESP32_GPIO_COUNT; pin++) {
|
|
uint32_t interrupt_enable = s->pin_config[pin] >> GPIO_PIN_INT_ENA_SHIFT;
|
|
if (interrupt_enable & GPIO_PIN_INT_ENA_CPU0) {
|
|
enabled |= BIT(pin);
|
|
}
|
|
}
|
|
return enabled;
|
|
}
|
|
|
|
static void esp32_gpio_update_irq(Esp32GpioState *s)
|
|
{
|
|
qemu_set_irq(s->irq, !!(s->interrupt_status &
|
|
esp32_gpio_enabled_interrupts(s)));
|
|
}
|
|
|
|
static void esp32_gpio_latch_level_interrupts(Esp32GpioState *s)
|
|
{
|
|
uint32_t levels = qatomic_read(&s->input_level);
|
|
|
|
for (int pin = 0; pin < ESP32_GPIO_COUNT; pin++) {
|
|
uint32_t type = extract32(s->pin_config[pin],
|
|
GPIO_PIN_INT_TYPE_SHIFT,
|
|
3);
|
|
bool level = !!(levels & BIT(pin));
|
|
if ((type == GPIO_INTR_LOW_LEVEL && !level) ||
|
|
(type == GPIO_INTR_HIGH_LEVEL && level)) {
|
|
s->interrupt_status |= BIT(pin);
|
|
}
|
|
}
|
|
}
|
|
|
|
void esp32_gpio_set_input_level(Esp32GpioState *s, int pin, bool level)
|
|
{
|
|
uint32_t old_level;
|
|
uint32_t new_level;
|
|
|
|
do {
|
|
old_level = qatomic_read(&s->input_level);
|
|
new_level = deposit32(old_level, pin, 1, level);
|
|
} while (qatomic_cmpxchg(&s->input_level, old_level, new_level) != old_level);
|
|
|
|
bool old_pin_level = !!(old_level & BIT(pin));
|
|
uint32_t type = extract32(s->pin_config[pin],
|
|
GPIO_PIN_INT_TYPE_SHIFT, 3);
|
|
if ((!old_pin_level && level && type == GPIO_INTR_POSEDGE) ||
|
|
(old_pin_level && !level && type == GPIO_INTR_NEGEDGE) ||
|
|
(old_pin_level != level && type == GPIO_INTR_ANYEDGE) ||
|
|
(!level && type == GPIO_INTR_LOW_LEVEL) ||
|
|
(level && type == GPIO_INTR_HIGH_LEVEL)) {
|
|
s->interrupt_status |= BIT(pin);
|
|
}
|
|
esp32_gpio_update_irq(s);
|
|
}
|
|
|
|
static void esp32_gpio_set_input(void *opaque, int pin, int level)
|
|
{
|
|
esp32_gpio_set_input_level(ESP32_GPIO(opaque), pin, level);
|
|
}
|
|
|
|
static void esp32_gpio_get_input(Object *obj, Visitor *v, const char *name,
|
|
void *opaque, Error **errp)
|
|
{
|
|
Esp32GpioState *s = ESP32_GPIO(obj);
|
|
int pin = GPOINTER_TO_INT(opaque);
|
|
bool level = extract32(qatomic_read(&s->input_level), pin, 1);
|
|
visit_type_bool(v, name, &level, errp);
|
|
}
|
|
|
|
static void esp32_gpio_set_input_property(Object *obj, Visitor *v,
|
|
const char *name, void *opaque,
|
|
Error **errp)
|
|
{
|
|
Esp32GpioState *s = ESP32_GPIO(obj);
|
|
int pin = GPOINTER_TO_INT(opaque);
|
|
bool level;
|
|
|
|
if (visit_type_bool(v, name, &level, errp)) {
|
|
esp32_gpio_set_input(s, pin, level);
|
|
}
|
|
}
|
|
|
|
static uint64_t esp32_gpio_read(void *opaque, hwaddr addr, unsigned int size)
|
|
{
|
|
Esp32GpioState *s = ESP32_GPIO(opaque);
|
|
|
|
switch (addr) {
|
|
case GPIO_OUT:
|
|
return s->output_level;
|
|
case GPIO_ENABLE:
|
|
return s->output_enable;
|
|
case A_GPIO_STRAP:
|
|
return s->strap_mode;
|
|
case GPIO_IN:
|
|
return qatomic_read(&s->input_level);
|
|
case GPIO_STATUS:
|
|
return s->interrupt_status;
|
|
case GPIO_PCPU_INT:
|
|
return s->interrupt_status & esp32_gpio_enabled_interrupts(s);
|
|
default:
|
|
if (addr >= s->pin_config_base &&
|
|
addr < s->pin_config_base + sizeof(s->pin_config)) {
|
|
return s->pin_config[(addr - s->pin_config_base) / 4];
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void esp32_gpio_write(void *opaque, hwaddr addr,
|
|
uint64_t value, unsigned int size)
|
|
{
|
|
Esp32GpioState *s = ESP32_GPIO(opaque);
|
|
|
|
if (addr >= s->pin_config_base &&
|
|
addr < s->pin_config_base + sizeof(s->pin_config)) {
|
|
s->pin_config[(addr - s->pin_config_base) / 4] = value;
|
|
esp32_gpio_latch_level_interrupts(s);
|
|
esp32_gpio_update_irq(s);
|
|
return;
|
|
}
|
|
|
|
switch (addr) {
|
|
case GPIO_OUT:
|
|
s->output_level = value;
|
|
break;
|
|
case GPIO_OUT_W1TS:
|
|
s->output_level |= value;
|
|
break;
|
|
case GPIO_OUT_W1TC:
|
|
s->output_level &= ~value;
|
|
break;
|
|
case GPIO_ENABLE:
|
|
s->output_enable = value;
|
|
break;
|
|
case GPIO_ENABLE_W1TS:
|
|
s->output_enable |= value;
|
|
break;
|
|
case GPIO_ENABLE_W1TC:
|
|
s->output_enable &= ~value;
|
|
break;
|
|
case GPIO_STATUS:
|
|
s->interrupt_status = value;
|
|
esp32_gpio_update_irq(s);
|
|
return;
|
|
case GPIO_STATUS_W1TS:
|
|
s->interrupt_status |= value;
|
|
esp32_gpio_update_irq(s);
|
|
return;
|
|
case GPIO_STATUS_W1TC:
|
|
s->interrupt_status &= ~value;
|
|
esp32_gpio_latch_level_interrupts(s);
|
|
esp32_gpio_update_irq(s);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
esp32_gpio_drive_outputs(s);
|
|
}
|
|
|
|
static const MemoryRegionOps uart_ops = {
|
|
.read = esp32_gpio_read,
|
|
.write = esp32_gpio_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static void esp32_gpio_reset_hold(Object *obj, ResetType type)
|
|
{
|
|
Esp32GpioState *s = ESP32_GPIO(obj);
|
|
s->input_level = UINT32_MAX;
|
|
s->output_level = 0;
|
|
s->output_enable = 0;
|
|
s->interrupt_status = 0;
|
|
memset(s->pin_config, 0, sizeof(s->pin_config));
|
|
esp32_gpio_drive_outputs(s);
|
|
esp32_gpio_update_irq(s);
|
|
}
|
|
|
|
static void esp32_gpio_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
}
|
|
|
|
static void esp32_gpio_init(Object *obj)
|
|
{
|
|
Esp32GpioState *s = ESP32_GPIO(obj);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
|
|
|
/* Set the default value for the strap_mode property */
|
|
object_property_set_int(obj, "strap_mode", ESP32_STRAP_MODE_FLASH_BOOT, &error_fatal);
|
|
s->pin_config_base = 0x88;
|
|
|
|
memory_region_init_io(&s->iomem, obj, &uart_ops, s,
|
|
TYPE_ESP32_GPIO, 0x1000);
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
qdev_init_gpio_in_named(DEVICE(obj), esp32_gpio_set_input,
|
|
ESP32_GPIO_INPUT, ESP32_GPIO_COUNT);
|
|
qdev_init_gpio_out_named(DEVICE(obj), s->output_lines,
|
|
ESP32_GPIO_OUTPUT, ESP32_GPIO_COUNT);
|
|
for (int i = 0; i < ESP32_GPIO_COUNT; i++) {
|
|
char *name = g_strdup_printf("input-level[%d]", i);
|
|
object_property_add(obj, name, "bool", esp32_gpio_get_input,
|
|
esp32_gpio_set_input_property, NULL,
|
|
GINT_TO_POINTER(i));
|
|
g_free(name);
|
|
}
|
|
|
|
esp32_gpio_reset_hold(obj, RESET_TYPE_COLD);
|
|
}
|
|
|
|
static Property esp32_gpio_properties[] = {
|
|
/* The strap_mode needs to be explicitly set in the instance init, thus, set
|
|
* the default value to 0. */
|
|
DEFINE_PROP_UINT32("strap_mode", Esp32GpioState, strap_mode, 0),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void esp32_gpio_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
|
|
|
rc->phases.hold = esp32_gpio_reset_hold;
|
|
dc->realize = esp32_gpio_realize;
|
|
device_class_set_props(dc, esp32_gpio_properties);
|
|
}
|
|
|
|
static const TypeInfo esp32_gpio_info = {
|
|
.name = TYPE_ESP32_GPIO,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(Esp32GpioState),
|
|
.instance_init = esp32_gpio_init,
|
|
.class_init = esp32_gpio_class_init,
|
|
.class_size = sizeof(Esp32GpioClass),
|
|
};
|
|
|
|
static void esp32_gpio_register_types(void)
|
|
{
|
|
type_register_static(&esp32_gpio_info);
|
|
}
|
|
|
|
type_init(esp32_gpio_register_types)
|