On Tue, 2 Jun 2026 at 07:34, Kuan-Jui Chiu <[email protected]> wrote:
>
> This patch add a new model for Cadence GPIO controller which
> supports 32 pins and interrupts for level-triggered/edge-triggered type on
> input pins.
>
> Also define new trace functions for analysis purpose and new configuration to
> enable this model.
>
> Signed-off-by: Kuan-Jui Chiu <[email protected]>
> ---
> hw/gpio/Kconfig | 3 +
> hw/gpio/cadence_gpio.c | 307 +++++++++++++++++++++++++++++++++
> hw/gpio/meson.build | 1 +
> hw/gpio/trace-events | 5 +
> include/hw/gpio/cadence_gpio.h | 55 ++++++
> 5 files changed, 371 insertions(+)
> create mode 100644 hw/gpio/cadence_gpio.c
> create mode 100644 include/hw/gpio/cadence_gpio.h
>
> diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
> index a209294c20c..fcc7c70bd50 100644
> --- a/hw/gpio/Kconfig
> +++ b/hw/gpio/Kconfig
> @@ -30,3 +30,6 @@ config PCF8574
>
> config ZAURUS_SCOOP
> bool
> +
> +config CADENCE_GPIO
> + bool
> diff --git a/hw/gpio/cadence_gpio.c b/hw/gpio/cadence_gpio.c
> new file mode 100644
> index 00000000000..08a8cf71968
> --- /dev/null
> +++ b/hw/gpio/cadence_gpio.c
> @@ -0,0 +1,307 @@
> +/*
> + * 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_per_line(CadenceGPIOState *s, int line,
> + uint32_t new)
We don't need to do this one line at a time. Because all the
registers involved are 32 bits wide and have one bit per
GPIO line, we can use logical operations to calculate them
all at once, like this:
static void cdns_gpio_write_inpvr(CadenceGPIOState *s, uint32_t new)
{
/*
* Update s->inpvr to the specified new value, and recalculate
* s->isr accordingly.
* We can calculate the s->isr value for all the GPIO lines
* at once using bitwise operations.
*/
uint32_t new_isr = 0;
uint32_t any_edges, rising_edges, falling_edges, deassert_mask;
/*
* If ITR is set, this is level triggered: ISR bit set
* when IVR matches new invpr value.
*/
new_isr |= s->itr & ~(s->ivr ^ new);
/*
* If ITR is not set, this is edge-triggered. If IOAR bit
* is set, we trigger on any edge; otherwise we trigger
* on rising edge if IVR is set, falling if IVR bit is 0.
*/
any_edges = s->ioar & (s->invpr ^ 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 ISR
* bit is forced to zero.
*/
deassert_mask = s->bmr | ~s->dmr | s->imr;
new_isr &= ~deassert_mask;
s->isr = new_isr;
s->inpvr = new;
cdns_gpio_update_irq(s);
}
(Untested, but I believe it to be correct.)
> +{
> + uint32_t old = extract32(s->inpvr, line, 1);
> + uint32_t ivr = extract32(s->ivr, line, 1);
> +
> + /* Deassert in bypass mode or not input pin */
> + if (extract32(s->bmr, line, 1) || !extract32(s->dmr, line, 1) ||
> + extract32(s->imr, line, 1)) {
> + s->isr = deposit32(s->isr, line, 1, 0);
> + return;
> + }
> +
> + if (extract32(s->itr, line, 1)) {
> + /* Level-triggered */
> + if (ivr && new) {
> + /* High level */
> + s->isr = deposit32(s->isr, line, 1, 1);
> + }
> + if (!ivr && !new) {
> + /* Low level */
> + s->isr = deposit32(s->isr, line, 1, 1);
> + }
> + } else {
> + /* Edge-triggered */
> + if (extract32(s->ioar, line, 1) && (old != new)) {
> + /* On any edge */
> + s->isr = deposit32(s->isr, line, 1, 1);
> + } else {
> + if (ivr && !old && new) {
> + /* Rising edge */
> + s->isr = deposit32(s->isr, line, 1, 1);
> + }
> + if (!ivr && old && !new) {
> + /* Falling edge */
> + s->isr = deposit32(s->isr, line, 1, 1);
> + }
> + }
> + }
> +}
> +
> +static void cdns_gpio_update_isr(CadenceGPIOState *s)
> +{
> + for (int i = 0; i < CDNS_GPIO_NUM; i++) {
> + uint32_t level = extract32(s->inpvr, i, 1);
> + cdns_gpio_update_isr_per_line(s, i, level);
> + }
If you use my suggestion above, this function becomes just
/* Trigger an update of s->isr */
cdns_gpio_write_inpvr(s, s->invpr);
and since cdns_gpio_write_inpvr() calls cdns_gpio_update_irq()
at the end, you don't need to have all this function's callsites do:
cdns_gpio_update_isr(s);
cdns_gpio_update_irq(s);
-- they can just call cdns_gpio_update_isr() only.
> +}
> +
> +static void cdns_gpio_set(void *opaque, int line, int level)
> +{
> + CadenceGPIOState *s = CADENCE_GPIO(opaque);
> + uint32_t new = level ? 1 : 0;
> +
> + trace_cdns_gpio_set(DEVICE(s)->canonical_path, line, level);
> +
> + cdns_gpio_update_isr_per_line(s, line, new);
> +
> + /* Sync INPVR with new value */
> + s->inpvr = deposit32(s->inpvr, line, 1, new);
> +
> + cdns_gpio_update_irq(s);
Similarly this function becomes something like
new_invpr = deposit32(s->inpvr, line, 1, new);
cdns_gpio_write_invpr(s, new_invpr);
> +}
> +
> +static inline void cdns_gpio_update_output_irq(CadenceGPIOState *s)
> +{
> + for (int i = 0; i < CDNS_GPIO_NUM; i++) {
> + /* Forward the output value to corresponding irq */
> + if (!extract32(s->bmr, i, 1) && !extract32(s->dmr, i, 1) &&
> + extract32(s->oer, i, 1) && s->output[i]) {
> + qemu_set_irq(s->output[i], extract32(s->ovr, i, 1));
You don't need to check if s->output[i] is NULL -- qemu_set_irq()
will handle that correctly (this is the "GPIO is not connected to
anything" case, and so it does nothing).
You might as well pre-calculate the outputs outside the loop:
uint32_t is_output = ~s->bmr & ~s->dmr & s->oer;
so inside the loop you can do if (extract32(is_output, i, 1))
instead of 3 separate extract operations.
> + }
> + }
> +}
thanks
-- PMM