Files
QEMU-S5L8950X/hw/gpio/cadence_gpio.c
T
Yaya48 cf256aa081 Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
2026-08-31 02:15:30 +02:00

293 lines
7.2 KiB
C

/*
* Cadence GPIO emulation.
*
* Author: Kuan-Jui Chiu <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/gpio/cadence_gpio.h"
#include "hw/core/irq.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "trace.h"
static void cdns_gpio_update_irq(CadenceGPIOState *s)
{
qemu_set_irq(s->irq, s->isr ? 1 : 0);
}
static void cdns_gpio_update_isr_with_inpvr(CadenceGPIOState *s, uint32_t new)
{
uint32_t new_isr = 0;
uint32_t any_edges, rising_edges, falling_edges, deassert_mask;
/*
* If ITR is set, this is level triggered:
* set corresponding ISR bits when IVR matches new inpvr value.
*/
new_isr |= s->itr & ~(s->ivr ^ new);
/*
* If ITR is not set, this is edge-triggered:
* If IOAR bit is set, trigger on any edge;
* otherwise trigger on rising edge if IVR is set,
* trigger on falling edge if IVR bit is 0.
*/
any_edges = s->ioar & (s->inpvr ^ new);
rising_edges = s->ivr & ~s->inpvr & new;
falling_edges = ~s->ivr & s->inpvr & ~new;
new_isr |= ~s->itr & (any_edges | rising_edges | falling_edges);
/*
* In bypass mode or if this isn't an input pin, the corresponding ISR
* bit is forced to zero.
*/
deassert_mask = s->bmr | ~s->dmr | s->imr;
new_isr &= ~deassert_mask;
s->isr = new_isr;
cdns_gpio_update_irq(s);
}
static void cdns_gpio_update_isr(CadenceGPIOState *s)
{
cdns_gpio_update_isr_with_inpvr(s, s->inpvr);
}
static void cdns_gpio_set(void *opaque, int line, int level)
{
CadenceGPIOState *s = CADENCE_GPIO(opaque);
uint32_t new_inpvr = deposit32(s->inpvr, line, 1, level ? 1 : 0);
trace_cdns_gpio_set(DEVICE(s)->canonical_path, line, level);
cdns_gpio_update_isr_with_inpvr(s, new_inpvr);
/* Sync INPVR with new value */
s->inpvr = new_inpvr;
}
static inline void cdns_gpio_update_output_irq(CadenceGPIOState *s)
{
uint32_t is_output = ~s->bmr & ~s->dmr & s->oer;
for (int i = 0; i < CDNS_GPIO_NUM; i++) {
if (extract32(is_output, i, 1)) {
/* Forward the output value to corresponding irq */
qemu_set_irq(s->output[i], extract32(s->ovr, i, 1));
}
}
}
static uint64_t cdns_gpio_read(void *opaque, hwaddr offset, unsigned size)
{
CadenceGPIOState *s = CADENCE_GPIO(opaque);
uint32_t reg_value = 0x0;
switch (offset) {
case CDNS_GPIO_BYPASS_MODE:
reg_value = s->bmr;
break;
case CDNS_GPIO_DIRECTION_MODE:
reg_value = s->dmr;
break;
case CDNS_GPIO_OUTPUT_EN:
reg_value = s->oer;
break;
case CDNS_GPIO_OUTPUT_VALUE:
reg_value = s->ovr;
break;
case CDNS_GPIO_INPUT_VALUE:
reg_value = s->inpvr;
break;
case CDNS_GPIO_IRQ_MASK:
reg_value = s->imr;
break;
case CDNS_GPIO_IRQ_STATUS:
reg_value = s->isr;
break;
case CDNS_GPIO_IRQ_TYPE:
reg_value = s->itr;
break;
case CDNS_GPIO_IRQ_VALUE:
reg_value = s->ivr;
break;
case CDNS_GPIO_IRQ_ANY_EDGE:
reg_value = s->ioar;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
break;
}
trace_cdns_gpio_read(DEVICE(s)->canonical_path, offset, reg_value);
return reg_value;
}
static void cdns_gpio_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
CadenceGPIOState *s = CADENCE_GPIO(opaque);
trace_cdns_gpio_write(DEVICE(s)->canonical_path, offset, value);
switch (offset) {
case CDNS_GPIO_BYPASS_MODE:
s->bmr = value;
cdns_gpio_update_output_irq(s);
cdns_gpio_update_isr(s);
break;
case CDNS_GPIO_DIRECTION_MODE:
s->dmr = value;
cdns_gpio_update_output_irq(s);
cdns_gpio_update_isr(s);
break;
case CDNS_GPIO_OUTPUT_EN:
s->oer = value;
cdns_gpio_update_output_irq(s);
break;
case CDNS_GPIO_OUTPUT_VALUE:
s->ovr = value;
cdns_gpio_update_output_irq(s);
break;
case CDNS_GPIO_IRQ_EN:
s->imr &= ~value;
cdns_gpio_update_isr(s);
break;
case CDNS_GPIO_IRQ_DIS:
s->imr |= value;
cdns_gpio_update_isr(s);
break;
case CDNS_GPIO_IRQ_TYPE:
s->itr = value;
break;
case CDNS_GPIO_IRQ_VALUE:
s->ivr = value;
break;
case CDNS_GPIO_IRQ_ANY_EDGE:
s->ioar = value;
break;
case CDNS_GPIO_INPUT_VALUE:
case CDNS_GPIO_IRQ_MASK:
case CDNS_GPIO_IRQ_STATUS:
/* Read-Only */
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
break;
}
}
static const MemoryRegionOps cdns_gpio_ops = {
.read = cdns_gpio_read,
.write = cdns_gpio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
.valid = {
.min_access_size = 4,
.max_access_size = 4,
}
};
static const VMStateDescription vmstate_cdns_gpio = {
.name = TYPE_CADENCE_GPIO,
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(bmr, CadenceGPIOState),
VMSTATE_UINT32(dmr, CadenceGPIOState),
VMSTATE_UINT32(oer, CadenceGPIOState),
VMSTATE_UINT32(ovr, CadenceGPIOState),
VMSTATE_UINT32(inpvr, CadenceGPIOState),
VMSTATE_UINT32(imr, CadenceGPIOState),
VMSTATE_UINT32(isr, CadenceGPIOState),
VMSTATE_UINT32(itr, CadenceGPIOState),
VMSTATE_UINT32(ivr, CadenceGPIOState),
VMSTATE_UINT32(ioar, CadenceGPIOState),
VMSTATE_END_OF_LIST()
}
};
static void cdns_gpio_reset(DeviceState *dev)
{
CadenceGPIOState *s = CADENCE_GPIO(dev);
s->bmr = 0;
s->dmr = 0;
s->oer = 0;
s->ovr = 0;
s->inpvr = 0;
s->imr = 0xffffffff;
s->isr = 0;
s->itr = 0;
s->ivr = 0;
s->ioar = 0;
}
static void cdns_gpio_init(Object *obj)
{
CadenceGPIOState *s = CADENCE_GPIO(obj);
memory_region_init_io(&s->iomem, obj, &cdns_gpio_ops, s,
TYPE_CADENCE_GPIO, CDNS_GPIO_REG_SIZE);
qdev_init_gpio_in(DEVICE(s), cdns_gpio_set, CDNS_GPIO_NUM);
qdev_init_gpio_out(DEVICE(s), s->output, CDNS_GPIO_NUM);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
}
static void cdns_gpio_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, cdns_gpio_reset);
dc->vmsd = &vmstate_cdns_gpio;
dc->desc = "Cadence GPIO controller";
}
static const TypeInfo cdns_gpio_info = {
.name = TYPE_CADENCE_GPIO,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(CadenceGPIOState),
.instance_init = cdns_gpio_init,
.class_init = cdns_gpio_class_init,
};
static void cdns_gpio_register_types(void)
{
type_register_static(&cdns_gpio_info);
}
type_init(cdns_gpio_register_types)