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

Reply via email to