Signed-off-by: gilles grimaud <[email protected]>
---
hw/arm/raspi_pico.c | 1 +
hw/arm/rp2040.c | 136 ++++-
hw/misc/meson.build | 1 +
hw/misc/rp2040_sio.c | 907 ++++++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 2 +
include/hw/misc/rp2040_sio.h | 65 +++
tests/qtest/meson.build | 2 +
tests/qtest/rp2040-psm-test.c | 81 +++
tests/qtest/rp2040-sio-test.c | 270 ++++++++++
9 files changed, 1448 insertions(+), 17 deletions(-)
create mode 100644 hw/misc/rp2040_sio.c
create mode 100644 include/hw/misc/rp2040_sio.h
create mode 100644 tests/qtest/rp2040-psm-test.c
create mode 100644 tests/qtest/rp2040-sio-test.c
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
index e361a0dbc9..71cd5817a6 100644
--- a/hw/arm/raspi_pico.c
+++ b/hw/arm/raspi_pico.c
@@ -72,6 +72,7 @@ static void raspi_pico_init(MachineState *machine)
s->rosc_random_seed);
qdev_prop_set_bit(DEVICE(&s->soc.rosc), "random-seed-set",
s->rosc_random_seed_set);
+ qdev_prop_set_uint32(DEVICE(&s->soc.sio), "gpio-hi-in", 1u << 1);
if (machine->firmware) {
qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
machine->firmware);
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index b503a4213c..d0107f98a6 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -16,6 +16,7 @@
#include "hw/core/irq.h"
#include "hw/misc/unimp.h"
#include "qemu/datadir.h"
+#include "target/arm/cpu.h"
#include "target/arm/cpu-qom.h"
#define RP2040_UART0_BASE 0x40034000
@@ -23,25 +24,42 @@
#define RP2040_UART1_BASE 0x40038000
#define RP2040_UART1_IRQ 21
#define RP2040_IO_IRQ_BANK0 13
+#define RP2040_SIO_IRQ_PROC0 15
+#define RP2040_SIO_IRQ_PROC1 16
+#define RP2040_PROC1 1
/*
- * Temporary boot ROM used until the synthetic ROM is introduced. It loads
- * the reset handler from a vector table at the base of the XIP window.
+ * Minimal synthetic ROM. Core 0 enters the XIP image while core 1 waits for
+ * the RP2040 ROM FIFO launch sequence: { 0, 0, 1, VTOR, SP, PC }.
*/
static const uint8_t rp2040_bootrom[] = {
- 0x00, 0x20, 0x04, 0x20, /* initial SP: 0x20042000 */
- 0x09, 0x00, 0x00, 0x00, /* reset handler: 0x00000009 */
- 0x03, 0x48, /* ldr r0, vtor */
- 0x04, 0x49, /* ldr r1, xip_base */
- 0x01, 0x60, /* str r1, [r0] */
- 0x04, 0x48, /* ldr r0, xip_reset_vector */
- 0x01, 0x68, /* ldr r1, [r0] */
- 0x08, 0x47, /* bx r1 */
- 0xfe, 0xe7, /* b . */
- 0xc0, 0x46, /* nop */
- 0x08, 0xed, 0x00, 0xe0, /* VTOR: 0xe000ed08 */
- 0x00, 0x00, 0x00, 0x10, /* XIP base: 0x10000000 */
- 0x04, 0x00, 0x00, 0x10, /* XIP reset vector: 0x10000004 */
+ 0x00, 0x20, 0x04, 0x20, 0x41, 0x00, 0x00, 0x00,
+ 0x97, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1f, 0x4c, 0x20, 0x68, 0x00, 0x28, 0x05, 0xd1,
+ 0x1e, 0x48, 0x1f, 0x49, 0x01, 0x60, 0x1f, 0x48,
+ 0x01, 0x68, 0x08, 0x47, 0x1e, 0x4d, 0x00, 0x26,
+ 0x00, 0xf0, 0x1e, 0xf8, 0x03, 0x2e, 0x07, 0xd2,
+ 0xb1, 0x00, 0x6a, 0x58, 0x90, 0x42, 0x0c, 0xd0,
+ 0x00, 0x26, 0x00, 0xf0, 0x1c, 0xf8, 0xf3, 0xe7,
+ 0x03, 0x2e, 0x01, 0xd1, 0x07, 0x46, 0x04, 0xe0,
+ 0x04, 0x2e, 0x01, 0xd1, 0x03, 0x46, 0x00, 0xe0,
+ 0x05, 0x46, 0x00, 0xf0, 0x10, 0xf8, 0x01, 0x36,
+ 0x06, 0x2e, 0xe5, 0xd1, 0x0d, 0x49, 0x0f, 0x60,
+ 0x83, 0xf3, 0x08, 0x88, 0x28, 0x47, 0xfe, 0xe7,
+ 0x09, 0x4c, 0x20, 0x6d, 0x01, 0x21, 0x08, 0x42,
+ 0xfb, 0xd0, 0xa0, 0x6d, 0x70, 0x47, 0x06, 0x4c,
+ 0x21, 0x6d, 0x02, 0x22, 0x11, 0x42, 0xfb, 0xd0,
+ 0x60, 0x65, 0x70, 0x47, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xd0, 0x08, 0xed, 0x00, 0xe0,
+ 0x00, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x10,
+ 0xb4, 0x00, 0x00, 0x00,
};
static const struct {
@@ -63,7 +81,6 @@ static const struct {
{ "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
{ "rp2040.pio0", 0x50200000, 0x10000 },
{ "rp2040.pio1", 0x50300000, 0x10000 },
- { "rp2040.sio", 0xd0000000, 0x1000 },
};
static void rp2040_update_nmi(RP2040State *s)
@@ -100,6 +117,71 @@ static void rp2040_set_irq(void *opaque, int irq, int
level)
rp2040_update_nmi(s);
}
+static void rp2040_start_core1_async_work(CPUState *cs, run_on_cpu_data data)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+
+ cpu_reset(cs);
+ cpu->power_state = PSCI_ON;
+ env->halt_reason = NOT_HALTED;
+ arm_rebuild_hflags(env);
+ cs->halted = 0;
+ cpu_resume(cs);
+}
+
+static bool rp2040_core1_powered_off(RP2040State *s)
+{
+ ARMCPU *cpu = s->armv7m[RP2040_PROC1].cpu;
+
+ return !cpu || cpu->power_state == PSCI_OFF;
+}
+
+static void rp2040_start_core1(RP2040State *s)
+{
+ if (!s->armv7m[RP2040_PROC1].cpu ||
+ !rp2040_core1_powered_off(s)) {
+ return;
+ }
+
+ async_run_on_cpu(CPU(s->armv7m[RP2040_PROC1].cpu),
+ rp2040_start_core1_async_work, RUN_ON_CPU_NULL);
+}
+
+static void rp2040_stop_core1_async_work(CPUState *cs, run_on_cpu_data data)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+
+ cpu->power_state = PSCI_OFF;
+ cpu->env.halt_reason = HALT_PSCI;
+ cs->halted = 1;
+ cs->exception_index = EXCP_HLT;
+}
+
+static void rp2040_stop_core1(RP2040State *s)
+{
+ if (!s->armv7m[RP2040_PROC1].cpu ||
+ rp2040_core1_powered_off(s)) {
+ return;
+ }
+
+ async_run_on_cpu(CPU(s->armv7m[RP2040_PROC1].cpu),
+ rp2040_stop_core1_async_work, RUN_ON_CPU_NULL);
+}
+
+static void rp2040_psm_update(void *opaque)
+{
+ RP2040State *s = opaque;
+ bool proc1_forced_off = rp2040_psm_get_frce_off(&s->psm) &
+ RP2040_PSM_PROC1;
+
+ if (proc1_forced_off) {
+ rp2040_stop_core1(s);
+ } else {
+ rp2040_start_core1(s);
+ }
+}
+
static void rp2040_update_uart_pins(RP2040State *s)
{
pl011_set_tx_connected(&s->uart[0],
@@ -158,7 +240,8 @@ static void rp2040_soc_init(Object *obj)
RP2040_NUM_IRQS);
qdev_prop_set_uint32(DEVICE(&s->armv7m[i]), "mpu-ns-regions", 8);
}
- qdev_prop_set_bit(DEVICE(&s->armv7m[1]), "start-powered-off", true);
+ qdev_prop_set_bit(DEVICE(&s->armv7m[RP2040_PROC1]),
+ "start-powered-off", true);
for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
g_autofree char *name = g_strdup_printf("uart%d", i);
@@ -196,6 +279,7 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
+ object_initialize_child(obj, "sio", &s->sio, TYPE_RP2040_SIO);
object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
object_initialize_child(obj, "watchdog", &s->watchdog,
@@ -315,12 +399,20 @@ static void rp2040_soc_realize(DeviceState *dev, Error
**errp)
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->psm), 0, RP2040_PSM_BASE);
+ rp2040_psm_set_update_callback(&s->psm, rp2040_psm_update, s);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->resets), errp)) {
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->resets), 0, RP2040_RESETS_BASE);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->sio), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->sio), 0, RP2040_SIO_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->sio), 0,
+ s->irq[RP2040_SIO_IRQ_PROC0]);
+
for (i = 0; i < RP2040_NUM_CORES; i++) {
g_autofree char *memory_name =
g_strdup_printf("rp2040.proc%d-memory", i);
@@ -334,6 +426,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error
**errp)
UINT64_C(1) << 32);
memory_region_add_subregion(&s->cpu_memory[i], 0,
&s->cpu_board_alias[i]);
+ if (i == RP2040_PROC1) {
+ memory_region_add_subregion_overlap(
+ &s->cpu_memory[i], RP2040_SIO_BASE,
+ rp2040_sio_core_region(&s->sio, i), 1);
+ }
qdev_connect_clock_in(DEVICE(&s->armv7m[i]), "cpuclk", s->sysclk);
object_property_set_link(OBJECT(&s->armv7m[i]), "memory",
OBJECT(&s->cpu_memory[i]), &err);
@@ -352,6 +449,7 @@ static void rp2040_soc_realize(DeviceState *dev, Error
**errp)
s->nmi_irq[i] = qdev_get_gpio_in_named(DEVICE(&s->armv7m[i]),
"NMI", 0);
}
+ rp2040_update_nmi(s);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->syscfg), errp)) {
return;
@@ -370,6 +468,8 @@ static void rp2040_soc_realize(DeviceState *dev, Error
**errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->iobank0), 0, RP2040_IOBANK0_BASE);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 0,
s->irq[RP2040_IO_IRQ_BANK0]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 1,
+ s->cpu_irq[RP2040_PROC1][RP2040_IO_IRQ_BANK0]);
qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 0,
qdev_get_gpio_in_named(dev, "uart-pin", 0));
qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 1,
@@ -378,6 +478,8 @@ static void rp2040_soc_realize(DeviceState *dev, Error
**errp)
qdev_get_gpio_in_named(dev, "uart-pin", 2));
qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 1,
qdev_get_gpio_in_named(dev, "uart-pin", 3));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->sio), 1,
+ s->cpu_irq[RP2040_PROC1][RP2040_SIO_IRQ_PROC1]);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) {
return;
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 5534cba1a8..db0776227d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -106,6 +106,7 @@ system_ss.add(when: 'CONFIG_RP2040', if_true:
files('rp2040_pll.c'))
system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_psm.c'))
system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_resets.c'))
system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_rosc.c'))
+system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_sio.c'))
system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040',
if_true: files('rp2040_sysinfo.c'))
diff --git a/hw/misc/rp2040_sio.c b/hw/misc/rp2040_sio.c
new file mode 100644
index 0000000000..5d12e043ee
--- /dev/null
+++ b/hw/misc/rp2040_sio.c
@@ -0,0 +1,907 @@
+/*
+ * RP2040 single-cycle IO block emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_sio.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define SIO_CPUID 0x000
+#define SIO_GPIO_IN 0x004
+#define SIO_GPIO_HI_IN 0x008
+#define SIO_GPIO_OUT 0x010
+#define SIO_GPIO_OUT_SET 0x014
+#define SIO_GPIO_OUT_CLR 0x018
+#define SIO_GPIO_OUT_XOR 0x01c
+#define SIO_GPIO_OE 0x020
+#define SIO_GPIO_OE_SET 0x024
+#define SIO_GPIO_OE_CLR 0x028
+#define SIO_GPIO_OE_XOR 0x02c
+#define SIO_GPIO_HI_OUT 0x030
+#define SIO_GPIO_HI_OUT_SET 0x034
+#define SIO_GPIO_HI_OUT_CLR 0x038
+#define SIO_GPIO_HI_OUT_XOR 0x03c
+#define SIO_GPIO_HI_OE 0x040
+#define SIO_GPIO_HI_OE_SET 0x044
+#define SIO_GPIO_HI_OE_CLR 0x048
+#define SIO_GPIO_HI_OE_XOR 0x04c
+#define SIO_FIFO_ST 0x050
+#define SIO_FIFO_WR 0x054
+#define SIO_FIFO_RD 0x058
+#define SIO_SPINLOCK_ST 0x05c
+#define SIO_DIV_UDIVIDEND 0x060
+#define SIO_DIV_UDIVISOR 0x064
+#define SIO_DIV_SDIVIDEND 0x068
+#define SIO_DIV_SDIVISOR 0x06c
+#define SIO_DIV_QUOTIENT 0x070
+#define SIO_DIV_REMAINDER 0x074
+#define SIO_DIV_CSR 0x078
+#define SIO_INTERP0_BASE 0x080
+#define SIO_INTERP1_BASE 0x0c0
+#define SIO_INTERP_BLOCK_SIZE 0x040
+#define SIO_INTERP_ACCUM0 0x00
+#define SIO_INTERP_ACCUM1 0x04
+#define SIO_INTERP_BASE0 0x08
+#define SIO_INTERP_BASE1 0x0c
+#define SIO_INTERP_BASE2 0x10
+#define SIO_INTERP_POP_LANE0 0x14
+#define SIO_INTERP_POP_LANE1 0x18
+#define SIO_INTERP_POP_FULL 0x1c
+#define SIO_INTERP_PEEK_LANE0 0x20
+#define SIO_INTERP_PEEK_LANE1 0x24
+#define SIO_INTERP_PEEK_FULL 0x28
+#define SIO_INTERP_CTRL_LANE0 0x2c
+#define SIO_INTERP_CTRL_LANE1 0x30
+#define SIO_INTERP_ACCUM0_ADD 0x34
+#define SIO_INTERP_ACCUM1_ADD 0x38
+#define SIO_INTERP_BASE_1AND0 0x3c
+#define SIO_SPINLOCK_BASE 0x100
+#define SIO_SPINLOCK_LAST 0x17c
+
+#define SIO_GPIO_MASK 0x3fffffff
+#define SIO_GPIO_HI_MASK 0x3f
+#define SIO_FIFO_ST_VLD BIT(0)
+#define SIO_FIFO_ST_RDY BIT(1)
+#define SIO_FIFO_ST_WC_MASK (BIT(3) | BIT(2))
+#define SIO_DIV_CSR_READY BIT(0)
+#define SIO_DIV_CSR_DIRTY BIT(1)
+#define SIO_INTERP_CTRL_FORCE_MSB_SHIFT 19
+#define SIO_INTERP_CTRL_ADD_RAW BIT(18)
+#define SIO_INTERP_CTRL_CROSS_RESULT BIT(17)
+#define SIO_INTERP_CTRL_CROSS_INPUT BIT(16)
+#define SIO_INTERP_CTRL_SIGNED BIT(15)
+#define SIO_INTERP_CTRL_MASK_MSB_SHIFT 10
+#define SIO_INTERP_CTRL_MASK_LSB_SHIFT 5
+#define SIO_INTERP_CTRL_SHIFT_MASK 0x1f
+#define SIO_INTERP_CTRL_LSB_MASK (0x1f << 5)
+#define SIO_INTERP_CTRL_MSB_MASK (0x1f << 10)
+#define SIO_INTERP0_CTRL_BLEND BIT(21)
+#define SIO_INTERP1_CTRL_CLAMP BIT(22)
+#define SIO_INTERP_CTRL_OVERF BIT(25)
+#define SIO_INTERP_CTRL_OVERF1 BIT(24)
+#define SIO_INTERP_CTRL_OVERF0 BIT(23)
+#define SIO_INTERP_CTRL_OVERF_MASK (SIO_INTERP_CTRL_OVERF | \
+ SIO_INTERP_CTRL_OVERF1 | \
+ SIO_INTERP_CTRL_OVERF0)
+
+typedef struct RP2040SioInterpResult {
+ uint32_t raw[RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t lane[RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t full;
+} RP2040SioInterpResult;
+
+static void rp2040_sio_update_fifo_irq(RP2040SioState *s)
+{
+ int i;
+
+ for (i = 0; i < RP2040_SIO_NUM_CORES; i++) {
+ qemu_set_irq(s->fifo_irq[i], s->fifo_level[i] != 0);
+ }
+}
+
+static uint32_t rp2040_sio_fifo_status(RP2040SioState *s, unsigned core)
+{
+ unsigned peer = core ^ 1;
+ uint32_t value = s->fifo_sticky[core] & SIO_FIFO_ST_WC_MASK;
+
+ if (s->fifo_level[core] != 0) {
+ value |= SIO_FIFO_ST_VLD;
+ }
+ if (s->fifo_level[peer] < RP2040_SIO_FIFO_DEPTH) {
+ value |= SIO_FIFO_ST_RDY;
+ }
+
+ return value;
+}
+
+static void rp2040_sio_fifo_push(RP2040SioState *s, unsigned core,
+ uint32_t value)
+{
+ unsigned peer = core ^ 1;
+
+ if (s->fifo_level[peer] == RP2040_SIO_FIFO_DEPTH) {
+ s->fifo_sticky[core] |= BIT(2);
+ return;
+ }
+
+ s->fifo[peer][s->fifo_wptr[peer]] = value;
+ s->fifo_wptr[peer] = (s->fifo_wptr[peer] + 1) % RP2040_SIO_FIFO_DEPTH;
+ s->fifo_level[peer]++;
+ rp2040_sio_update_fifo_irq(s);
+}
+
+static uint32_t rp2040_sio_fifo_pop(RP2040SioState *s, unsigned core)
+{
+ uint32_t value;
+
+ if (s->fifo_level[core] == 0) {
+ s->fifo_sticky[core] |= BIT(3);
+ return 0;
+ }
+
+ value = s->fifo[core][s->fifo_rptr[core]];
+ s->fifo_rptr[core] = (s->fifo_rptr[core] + 1) % RP2040_SIO_FIFO_DEPTH;
+ s->fifo_level[core]--;
+ rp2040_sio_update_fifo_irq(s);
+
+ return value;
+}
+
+static bool rp2040_sio_spinlock_offset(hwaddr offset, unsigned *index)
+{
+ if (offset < SIO_SPINLOCK_BASE || offset > SIO_SPINLOCK_LAST ||
+ (offset & 0x3)) {
+ return false;
+ }
+
+ *index = (offset - SIO_SPINLOCK_BASE) / sizeof(uint32_t);
+ return *index < 32;
+}
+
+static int32_t rp2040_sio_sign32(uint32_t value)
+{
+ int32_t signed_value = (int32_t)value;
+
+ if (signed_value > 0) {
+ return 1;
+ }
+ if (signed_value < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static void rp2040_sio_divide(RP2040SioState *s, unsigned core,
+ bool is_signed)
+{
+ uint32_t dividend = s->div_dividend[core];
+ uint32_t divisor = s->div_divisor[core];
+ uint32_t quotient;
+ uint32_t remainder;
+
+ if (is_signed) {
+ int32_t signed_dividend = (int32_t)dividend;
+ int32_t signed_divisor = (int32_t)divisor;
+
+ if (signed_divisor == 0) {
+ quotient = -rp2040_sio_sign32(dividend);
+ remainder = dividend;
+ } else if (signed_dividend == INT32_MIN && signed_divisor == -1) {
+ quotient = dividend;
+ remainder = 0;
+ } else {
+ quotient = signed_dividend / signed_divisor;
+ remainder = signed_dividend % signed_divisor;
+ }
+ } else if (divisor == 0) {
+ quotient = UINT32_MAX;
+ remainder = dividend;
+ } else {
+ quotient = dividend / divisor;
+ remainder = dividend % divisor;
+ }
+
+ s->div_quotient[core] = quotient;
+ s->div_remainder[core] = remainder;
+ s->div_dirty[core] = true;
+}
+
+static uint32_t rp2040_sio_div_csr(RP2040SioState *s, unsigned core)
+{
+ return SIO_DIV_CSR_READY |
+ (s->div_dirty[core] ? SIO_DIV_CSR_DIRTY : 0);
+}
+
+static unsigned rp2040_sio_interp_index(unsigned interp, unsigned lane)
+{
+ return interp * RP2040_SIO_INTERP_NUM_LANES + lane;
+}
+
+static unsigned rp2040_sio_interp_base_index(unsigned interp, unsigned base)
+{
+ return interp * RP2040_SIO_INTERP_NUM_BASES + base;
+}
+
+static uint32_t rp2040_sio_interp_ctrl_mask(unsigned interp, unsigned lane)
+{
+ uint32_t mask = SIO_INTERP_CTRL_SHIFT_MASK |
+ SIO_INTERP_CTRL_LSB_MASK |
+ SIO_INTERP_CTRL_MSB_MASK |
+ SIO_INTERP_CTRL_SIGNED |
+ SIO_INTERP_CTRL_CROSS_INPUT |
+ SIO_INTERP_CTRL_CROSS_RESULT |
+ SIO_INTERP_CTRL_ADD_RAW |
+ (0x3 << SIO_INTERP_CTRL_FORCE_MSB_SHIFT);
+
+ if (interp == 0 && lane == 0) {
+ mask |= SIO_INTERP0_CTRL_BLEND;
+ } else if (interp == 1 && lane == 0) {
+ mask |= SIO_INTERP1_CTRL_CLAMP;
+ }
+
+ return mask;
+}
+
+static bool rp2040_sio_interp_offset(hwaddr addr, unsigned *interp,
+ hwaddr *reg)
+{
+ if (addr >= SIO_INTERP0_BASE &&
+ addr < SIO_INTERP0_BASE + SIO_INTERP_BLOCK_SIZE) {
+ *interp = 0;
+ *reg = addr - SIO_INTERP0_BASE;
+ return true;
+ }
+
+ if (addr >= SIO_INTERP1_BASE &&
+ addr < SIO_INTERP1_BASE + SIO_INTERP_BLOCK_SIZE) {
+ *interp = 1;
+ *reg = addr - SIO_INTERP1_BASE;
+ return true;
+ }
+
+ return false;
+}
+
+static uint32_t rp2040_sio_interp_mask(unsigned lsb, unsigned msb)
+{
+ if (msb < lsb) {
+ return 0;
+ }
+
+ if (msb == 31) {
+ return UINT32_MAX << lsb;
+ }
+
+ return (BIT(msb + 1) - 1) & ~(BIT(lsb) - 1);
+}
+
+static uint32_t rp2040_sio_interp_sign_extend(uint32_t value, unsigned msb)
+{
+ if (msb == 31 || !(value & BIT(msb))) {
+ return value;
+ }
+
+ return value | (UINT32_MAX << (msb + 1));
+}
+
+static uint32_t rp2040_sio_interp_raw(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ uint32_t input;
+ uint32_t shifted;
+ uint32_t value;
+ unsigned shift = ctrl & SIO_INTERP_CTRL_SHIFT_MASK;
+ unsigned lsb = (ctrl & SIO_INTERP_CTRL_LSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_LSB_SHIFT;
+ unsigned msb = (ctrl & SIO_INTERP_CTRL_MSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_MSB_SHIFT;
+
+ input = s->interp_accum[core][rp2040_sio_interp_index(
+ interp,
+ (ctrl & SIO_INTERP_CTRL_CROSS_INPUT) ?
+ (lane ^ 1) : lane)];
+ if (ctrl & SIO_INTERP_CTRL_ADD_RAW) {
+ return input;
+ }
+
+ shifted = input >> shift;
+ value = shifted & rp2040_sio_interp_mask(lsb, msb);
+ if (ctrl & SIO_INTERP_CTRL_SIGNED) {
+ value = rp2040_sio_interp_sign_extend(value, msb);
+ }
+
+ return value;
+}
+
+static uint32_t rp2040_sio_interp_force_msb(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane,
+ uint32_t value)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ uint32_t force = (ctrl >> SIO_INTERP_CTRL_FORCE_MSB_SHIFT) & 0x3;
+
+ return value | (force << 28);
+}
+
+static uint32_t rp2040_sio_interp_blend_result(RP2040SioState *s,
+ unsigned core, uint32_t alpha)
+{
+ uint32_t ctrl1 = s->interp_ctrl[core][rp2040_sio_interp_index(0, 1)];
+ uint32_t base0 = s->interp_base[core][rp2040_sio_interp_base_index(0, 0)];
+ uint32_t base1 = s->interp_base[core][rp2040_sio_interp_base_index(0, 1)];
+ int64_t start;
+ int64_t end;
+
+ if (ctrl1 & SIO_INTERP_CTRL_SIGNED) {
+ start = (int16_t)base0;
+ end = (int16_t)base1;
+ } else {
+ start = (uint16_t)base0;
+ end = (uint16_t)base1;
+ }
+
+ return start + (((end - start) * (alpha & 0xff)) >> 8);
+}
+
+static void rp2040_sio_interp_compute(RP2040SioState *s, unsigned core,
+ unsigned interp,
+ RP2040SioInterpResult *result)
+{
+ bool blend = interp == 0 &&
+ (s->interp_ctrl[core][rp2040_sio_interp_index(0, 0)] &
+ SIO_INTERP0_CTRL_BLEND);
+ bool clamp = interp == 1 &&
+ (s->interp_ctrl[core][rp2040_sio_interp_index(1, 0)] &
+ SIO_INTERP1_CTRL_CLAMP);
+ uint32_t base0 = s->interp_base[core]
+ [rp2040_sio_interp_base_index(interp, 0)];
+ uint32_t base1 = s->interp_base[core]
+ [rp2040_sio_interp_base_index(interp, 1)];
+ uint32_t base2 = s->interp_base[core]
+ [rp2040_sio_interp_base_index(interp, 2)];
+
+ result->raw[0] = rp2040_sio_interp_raw(s, core, interp, 0);
+ result->raw[1] = rp2040_sio_interp_raw(s, core, interp, 1);
+ result->lane[0] = base0 + result->raw[0];
+ result->lane[1] = base1 + result->raw[1];
+ result->full = base2 + result->raw[0] + result->raw[1];
+
+ if (blend) {
+ uint32_t alpha = result->raw[1] & 0xff;
+
+ result->lane[0] = alpha;
+ result->lane[1] = rp2040_sio_interp_blend_result(s, core, alpha);
+ result->full = base2 + result->raw[0];
+ } else if (clamp) {
+ uint32_t ctrl0 = s->interp_ctrl[core][rp2040_sio_interp_index(1, 0)];
+
+ if (ctrl0 & SIO_INTERP_CTRL_SIGNED) {
+ int32_t value = result->raw[0];
+ int32_t lower = base0;
+ int32_t upper = base1;
+
+ result->lane[0] = value < lower ? lower :
+ value > upper ? upper : value;
+ } else {
+ result->lane[0] = result->raw[0] < base0 ? base0 :
+ result->raw[0] > base1 ? base1 :
+ result->raw[0];
+ }
+ }
+}
+
+static uint32_t rp2040_sio_interp_pop(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ RP2040SioInterpResult result;
+ uint32_t ctrl0 = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, 0)];
+ uint32_t ctrl1 = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, 1)];
+ uint32_t next0;
+ uint32_t next1;
+
+ rp2040_sio_interp_compute(s, core, interp, &result);
+ next0 = (ctrl0 & SIO_INTERP_CTRL_CROSS_RESULT) ?
+ result.lane[1] : result.lane[0];
+ next1 = (ctrl1 & SIO_INTERP_CTRL_CROSS_RESULT) ?
+ result.lane[0] : result.lane[1];
+
+ s->interp_accum[core][rp2040_sio_interp_index(interp, 0)] = next0;
+ s->interp_accum[core][rp2040_sio_interp_index(interp, 1)] = next1;
+
+ return lane < 2 ? rp2040_sio_interp_force_msb(s, core, interp, lane,
+ result.lane[lane]) :
+ result.full;
+}
+
+static uint32_t rp2040_sio_interp_overf(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ unsigned shift = ctrl & SIO_INTERP_CTRL_SHIFT_MASK;
+ unsigned lsb = (ctrl & SIO_INTERP_CTRL_LSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_LSB_SHIFT;
+ unsigned msb = (ctrl & SIO_INTERP_CTRL_MSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_MSB_SHIFT;
+ uint32_t input = s->interp_accum[core][rp2040_sio_interp_index(
+ interp,
+ (ctrl &
+ SIO_INTERP_CTRL_CROSS_INPUT) ?
+ (lane ^ 1) : lane)];
+ uint32_t shifted = input >> shift;
+
+ return (shifted & ~rp2040_sio_interp_mask(lsb, msb)) != 0;
+}
+
+static uint32_t rp2040_sio_interp_ctrl_read(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ bool overf0 = rp2040_sio_interp_overf(s, core, interp, 0);
+ bool overf1 = rp2040_sio_interp_overf(s, core, interp, 1);
+
+ ctrl &= ~SIO_INTERP_CTRL_OVERF_MASK;
+ if (overf0) {
+ ctrl |= SIO_INTERP_CTRL_OVERF0;
+ }
+ if (overf1) {
+ ctrl |= SIO_INTERP_CTRL_OVERF1;
+ }
+ if (overf0 || overf1) {
+ ctrl |= SIO_INTERP_CTRL_OVERF;
+ }
+
+ return ctrl;
+}
+
+static uint32_t rp2040_sio_interp_read(RP2040SioState *s, unsigned core,
+ unsigned interp, hwaddr reg)
+{
+ RP2040SioInterpResult result;
+
+ switch (reg) {
+ case SIO_INTERP_ACCUM0:
+ case SIO_INTERP_ACCUM1:
+ return s->interp_accum[core][rp2040_sio_interp_index(
+ interp, reg == SIO_INTERP_ACCUM1)];
+ case SIO_INTERP_BASE0:
+ case SIO_INTERP_BASE1:
+ case SIO_INTERP_BASE2:
+ return s->interp_base[core][rp2040_sio_interp_base_index(
+ interp, (reg - SIO_INTERP_BASE0) /
+ sizeof(uint32_t))];
+ case SIO_INTERP_POP_LANE0:
+ return rp2040_sio_interp_pop(s, core, interp, 0);
+ case SIO_INTERP_POP_LANE1:
+ return rp2040_sio_interp_pop(s, core, interp, 1);
+ case SIO_INTERP_POP_FULL:
+ return rp2040_sio_interp_pop(s, core, interp, 2);
+ case SIO_INTERP_PEEK_LANE0:
+ case SIO_INTERP_PEEK_LANE1:
+ case SIO_INTERP_PEEK_FULL:
+ rp2040_sio_interp_compute(s, core, interp, &result);
+ if (reg == SIO_INTERP_PEEK_LANE0) {
+ return rp2040_sio_interp_force_msb(s, core, interp, 0,
+ result.lane[0]);
+ }
+ if (reg == SIO_INTERP_PEEK_LANE1) {
+ return rp2040_sio_interp_force_msb(s, core, interp, 1,
+ result.lane[1]);
+ }
+ return result.full;
+ case SIO_INTERP_CTRL_LANE0:
+ case SIO_INTERP_CTRL_LANE1:
+ return rp2040_sio_interp_ctrl_read(s, core, interp,
+ reg == SIO_INTERP_CTRL_LANE1);
+ case SIO_INTERP_ACCUM0_ADD:
+ case SIO_INTERP_ACCUM1_ADD:
+ return rp2040_sio_interp_raw(s, core, interp,
+ reg == SIO_INTERP_ACCUM1_ADD);
+ case SIO_INTERP_BASE_1AND0:
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static void rp2040_sio_interp_write(RP2040SioState *s, unsigned core,
+ unsigned interp, hwaddr reg,
+ uint32_t value)
+{
+ unsigned lane;
+ unsigned base;
+ bool base01_signed;
+
+ switch (reg) {
+ case SIO_INTERP_ACCUM0:
+ case SIO_INTERP_ACCUM1:
+ lane = reg == SIO_INTERP_ACCUM1;
+ s->interp_accum[core][rp2040_sio_interp_index(interp, lane)] = value;
+ break;
+ case SIO_INTERP_BASE0:
+ case SIO_INTERP_BASE1:
+ case SIO_INTERP_BASE2:
+ base = (reg - SIO_INTERP_BASE0) / sizeof(uint32_t);
+ s->interp_base[core][rp2040_sio_interp_base_index(interp, base)] =
+ value;
+ break;
+ case SIO_INTERP_CTRL_LANE0:
+ case SIO_INTERP_CTRL_LANE1:
+ lane = reg == SIO_INTERP_CTRL_LANE1;
+ s->interp_ctrl[core][rp2040_sio_interp_index(interp, lane)] =
+ value & rp2040_sio_interp_ctrl_mask(interp, lane);
+ break;
+ case SIO_INTERP_ACCUM0_ADD:
+ case SIO_INTERP_ACCUM1_ADD:
+ lane = reg == SIO_INTERP_ACCUM1_ADD;
+ s->interp_accum[core][rp2040_sio_interp_index(interp, lane)] +=
+ value & 0x00ffffff;
+ break;
+ case SIO_INTERP_BASE_1AND0:
+ base01_signed = interp == 0 &&
+ (s->interp_ctrl[core]
+ [rp2040_sio_interp_index(0, 0)] &
+ SIO_INTERP0_CTRL_BLEND) ?
+ (s->interp_ctrl[core]
+ [rp2040_sio_interp_index(0, 1)] &
+ SIO_INTERP_CTRL_SIGNED) : false;
+
+ for (base = 0; base < 2; base++) {
+ uint16_t half = value >> (base * 16);
+ bool sign = base01_signed ||
+ (s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp,
+ base)] &
+ SIO_INTERP_CTRL_SIGNED);
+
+ s->interp_base[core][rp2040_sio_interp_base_index(interp, base)] =
+ sign ? (uint32_t)(int16_t)half : half;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t rp2040_sio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040SioCoreView *view = opaque;
+ RP2040SioState *s = view->sio;
+ unsigned core = view->core;
+ unsigned interp;
+ unsigned index;
+ unsigned shift = (addr & 3) * 8;
+ hwaddr interp_reg;
+ hwaddr reg = addr & ~3ULL;
+ uint64_t value;
+
+ if (rp2040_sio_interp_offset(reg, &interp, &interp_reg)) {
+ value = rp2040_sio_interp_read(s, core, interp, interp_reg);
+ return extract64(value, shift, size * 8);
+ }
+
+ switch (reg) {
+ case SIO_CPUID:
+ value = core;
+ break;
+ case SIO_GPIO_IN:
+ value = s->gpio_in;
+ break;
+ case SIO_GPIO_HI_IN:
+ value = s->gpio_hi_in;
+ break;
+ case SIO_GPIO_OUT:
+ value = s->gpio_out;
+ break;
+ case SIO_GPIO_OE:
+ value = s->gpio_oe;
+ break;
+ case SIO_GPIO_HI_OUT:
+ value = s->gpio_hi_out;
+ break;
+ case SIO_GPIO_HI_OE:
+ value = s->gpio_hi_oe;
+ break;
+ case SIO_FIFO_ST:
+ value = rp2040_sio_fifo_status(s, core);
+ break;
+ case SIO_FIFO_RD:
+ value = rp2040_sio_fifo_pop(s, core);
+ break;
+ case SIO_SPINLOCK_ST:
+ value = s->spinlock_st;
+ break;
+ case SIO_DIV_UDIVIDEND:
+ case SIO_DIV_SDIVIDEND:
+ value = s->div_dividend[core];
+ break;
+ case SIO_DIV_UDIVISOR:
+ case SIO_DIV_SDIVISOR:
+ value = s->div_divisor[core];
+ break;
+ case SIO_DIV_QUOTIENT:
+ value = s->div_quotient[core];
+ s->div_dirty[core] = false;
+ break;
+ case SIO_DIV_REMAINDER:
+ value = s->div_remainder[core];
+ break;
+ case SIO_DIV_CSR:
+ value = rp2040_sio_div_csr(s, core);
+ break;
+ default:
+ if (rp2040_sio_spinlock_offset(reg, &index)) {
+ value = (s->spinlock_st & BIT(index)) ? 0 : BIT(index);
+ s->spinlock_st |= BIT(index);
+ } else {
+ value = 0;
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented read at offset 0x%"
+ HWADDR_PRIx "\n", __func__, addr & 0xfff);
+ }
+ break;
+ }
+
+ return extract64(value, shift, size * 8);
+}
+
+static void rp2040_sio_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040SioCoreView *view = opaque;
+ RP2040SioState *s = view->sio;
+ unsigned core = view->core;
+ unsigned interp;
+ unsigned index;
+ unsigned shift = (addr & 3) * 8;
+ hwaddr interp_reg;
+ hwaddr reg = addr & ~3ULL;
+ uint32_t value = (uint32_t)value64 << shift;
+
+ if (rp2040_sio_interp_offset(reg, &interp, &interp_reg)) {
+ rp2040_sio_interp_write(s, core, interp, interp_reg, value);
+ return;
+ }
+
+ switch (reg) {
+ case SIO_GPIO_OUT:
+ s->gpio_out = value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OUT_SET:
+ s->gpio_out |= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OUT_CLR:
+ s->gpio_out &= ~(value & SIO_GPIO_MASK);
+ break;
+ case SIO_GPIO_OUT_XOR:
+ s->gpio_out ^= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OE:
+ s->gpio_oe = value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OE_SET:
+ s->gpio_oe |= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OE_CLR:
+ s->gpio_oe &= ~(value & SIO_GPIO_MASK);
+ break;
+ case SIO_GPIO_OE_XOR:
+ s->gpio_oe ^= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_HI_OUT:
+ s->gpio_hi_out = value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OUT_SET:
+ s->gpio_hi_out |= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OUT_CLR:
+ s->gpio_hi_out &= ~(value & SIO_GPIO_HI_MASK);
+ break;
+ case SIO_GPIO_HI_OUT_XOR:
+ s->gpio_hi_out ^= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OE:
+ s->gpio_hi_oe = value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OE_SET:
+ s->gpio_hi_oe |= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OE_CLR:
+ s->gpio_hi_oe &= ~(value & SIO_GPIO_HI_MASK);
+ break;
+ case SIO_GPIO_HI_OE_XOR:
+ s->gpio_hi_oe ^= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_FIFO_ST:
+ s->fifo_sticky[core] &= ~(value & SIO_FIFO_ST_WC_MASK);
+ break;
+ case SIO_FIFO_WR:
+ rp2040_sio_fifo_push(s, core, value);
+ break;
+ case SIO_DIV_UDIVIDEND:
+ s->div_dividend[core] = value;
+ rp2040_sio_divide(s, core, false);
+ break;
+ case SIO_DIV_UDIVISOR:
+ s->div_divisor[core] = value;
+ rp2040_sio_divide(s, core, false);
+ break;
+ case SIO_DIV_SDIVIDEND:
+ s->div_dividend[core] = value;
+ rp2040_sio_divide(s, core, true);
+ break;
+ case SIO_DIV_SDIVISOR:
+ s->div_divisor[core] = value;
+ rp2040_sio_divide(s, core, true);
+ break;
+ case SIO_DIV_QUOTIENT:
+ s->div_quotient[core] = value;
+ s->div_dirty[core] = true;
+ break;
+ case SIO_DIV_REMAINDER:
+ s->div_remainder[core] = value;
+ s->div_dirty[core] = true;
+ break;
+ default:
+ if (rp2040_sio_spinlock_offset(reg, &index)) {
+ s->spinlock_st &= ~BIT(index);
+ } else {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unimplemented write at offset 0x%"
+ HWADDR_PRIx "\n", __func__, addr & 0xfff);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_sio_ops = {
+ .read = rp2040_sio_read,
+ .write = rp2040_sio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void rp2040_sio_reset(DeviceState *dev)
+{
+ RP2040SioState *s = RP2040_SIO(dev);
+
+ s->gpio_in &= SIO_GPIO_MASK;
+ s->gpio_hi_in &= SIO_GPIO_HI_MASK;
+ s->gpio_out = 0;
+ s->gpio_oe = 0;
+ s->gpio_hi_out = 0;
+ s->gpio_hi_oe = 0;
+ memset(s->fifo, 0, sizeof(s->fifo));
+ memset(s->fifo_rptr, 0, sizeof(s->fifo_rptr));
+ memset(s->fifo_wptr, 0, sizeof(s->fifo_wptr));
+ memset(s->fifo_level, 0, sizeof(s->fifo_level));
+ memset(s->fifo_sticky, 0, sizeof(s->fifo_sticky));
+ memset(s->div_dividend, 0, sizeof(s->div_dividend));
+ memset(s->div_divisor, 0, sizeof(s->div_divisor));
+ memset(s->div_quotient, 0, sizeof(s->div_quotient));
+ memset(s->div_remainder, 0, sizeof(s->div_remainder));
+ memset(s->div_dirty, 0, sizeof(s->div_dirty));
+ memset(s->interp_accum, 0, sizeof(s->interp_accum));
+ memset(s->interp_base, 0, sizeof(s->interp_base));
+ memset(s->interp_ctrl, 0, sizeof(s->interp_ctrl));
+ s->spinlock_st = 0;
+ rp2040_sio_update_fifo_irq(s);
+}
+
+static void rp2040_sio_init(Object *obj)
+{
+ RP2040SioState *s = RP2040_SIO(obj);
+ int i;
+
+ for (i = 0; i < RP2040_SIO_NUM_CORES; i++) {
+ g_autofree char *name = g_strdup_printf("rp2040-sio-core%d", i);
+
+ s->core_view[i].sio = s;
+ s->core_view[i].core = i;
+ memory_region_init_io(&s->core_view[i].iomem, obj,
+ &rp2040_sio_ops, &s->core_view[i], name,
+ 0x1000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->core_view[i].iomem);
+ }
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->fifo_irq[0]);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->fifo_irq[1]);
+}
+
+MemoryRegion *rp2040_sio_core_region(RP2040SioState *s, unsigned core)
+{
+ g_assert(core < RP2040_SIO_NUM_CORES);
+
+ return &s->core_view[core].iomem;
+}
+
+static const VMStateDescription vmstate_rp2040_sio = {
+ .name = TYPE_RP2040_SIO,
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(gpio_out, RP2040SioState),
+ VMSTATE_UINT32(gpio_in, RP2040SioState),
+ VMSTATE_UINT32(gpio_hi_in, RP2040SioState),
+ VMSTATE_UINT32(gpio_oe, RP2040SioState),
+ VMSTATE_UINT32(gpio_hi_out, RP2040SioState),
+ VMSTATE_UINT32(gpio_hi_oe, RP2040SioState),
+ VMSTATE_UINT32_2DARRAY(fifo, RP2040SioState, RP2040_SIO_NUM_CORES,
+ RP2040_SIO_FIFO_DEPTH),
+ VMSTATE_UINT8_ARRAY(fifo_rptr, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT8_ARRAY(fifo_wptr, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT8_ARRAY(fifo_level, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(fifo_sticky, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_dividend, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_divisor, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_quotient, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_remainder, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_BOOL_ARRAY(div_dirty, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_2DARRAY_V(interp_accum, RP2040SioState,
+ RP2040_SIO_NUM_CORES,
+ RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES, 3),
+ VMSTATE_UINT32_2DARRAY_V(interp_base, RP2040SioState,
+ RP2040_SIO_NUM_CORES,
+ RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_BASES, 3),
+ VMSTATE_UINT32_2DARRAY_V(interp_ctrl, RP2040SioState,
+ RP2040_SIO_NUM_CORES,
+ RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES, 3),
+ VMSTATE_UINT32(spinlock_st, RP2040SioState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property rp2040_sio_properties[] = {
+ DEFINE_PROP_UINT32("gpio-in", RP2040SioState, gpio_in, 0),
+ DEFINE_PROP_UINT32("gpio-hi-in", RP2040SioState, gpio_hi_in, 0),
+};
+
+static void rp2040_sio_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_sio_reset);
+ device_class_set_props(dc, rp2040_sio_properties);
+ dc->vmsd = &vmstate_rp2040_sio;
+}
+
+static const TypeInfo rp2040_sio_info = {
+ .name = TYPE_RP2040_SIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040SioState),
+ .instance_init = rp2040_sio_init,
+ .class_init = rp2040_sio_class_init,
+};
+
+static void rp2040_sio_register_types(void)
+{
+ type_register_static(&rp2040_sio_info);
+}
+type_init(rp2040_sio_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 6beb4cafff..9fcc50eb3e 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -21,6 +21,7 @@
#include "hw/misc/rp2040_psm.h"
#include "hw/misc/rp2040_resets.h"
#include "hw/misc/rp2040_rosc.h"
+#include "hw/misc/rp2040_sio.h"
#include "hw/misc/rp2040_sysinfo.h"
#include "hw/misc/rp2040_syscfg.h"
#include "hw/misc/rp2040_tbman.h"
@@ -61,6 +62,7 @@ struct RP2040State {
RP2040SysCfgState syscfg;
RP2040SysInfoState sysinfo;
RP2040RoscState rosc;
+ RP2040SioState sio;
RP2040TbmanState tbman;
RP2040VregState vreg;
RP2040WatchdogState watchdog;
diff --git a/include/hw/misc/rp2040_sio.h b/include/hw/misc/rp2040_sio.h
new file mode 100644
index 0000000000..58eb3d1e2c
--- /dev/null
+++ b/include/hw/misc/rp2040_sio.h
@@ -0,0 +1,65 @@
+/*
+ * RP2040 single-cycle IO block emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_SIO_H
+#define HW_MISC_RP2040_SIO_H
+
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_SIO "rp2040-sio"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040SioState, RP2040_SIO)
+
+#define RP2040_SIO_BASE 0xd0000000
+#define RP2040_SIO_NUM_CORES 2
+#define RP2040_SIO_FIFO_DEPTH 8
+#define RP2040_SIO_NUM_INTERPS 2
+#define RP2040_SIO_INTERP_NUM_LANES 2
+#define RP2040_SIO_INTERP_NUM_BASES 3
+
+typedef struct RP2040SioCoreView {
+ RP2040SioState *sio;
+ MemoryRegion iomem;
+ unsigned core;
+} RP2040SioCoreView;
+
+struct RP2040SioState {
+ SysBusDevice parent_obj;
+
+ RP2040SioCoreView core_view[RP2040_SIO_NUM_CORES];
+ qemu_irq fifo_irq[RP2040_SIO_NUM_CORES];
+ uint32_t gpio_in;
+ uint32_t gpio_hi_in;
+ uint32_t gpio_out;
+ uint32_t gpio_oe;
+ uint32_t gpio_hi_out;
+ uint32_t gpio_hi_oe;
+ uint32_t fifo[RP2040_SIO_NUM_CORES][RP2040_SIO_FIFO_DEPTH];
+ uint8_t fifo_rptr[RP2040_SIO_NUM_CORES];
+ uint8_t fifo_wptr[RP2040_SIO_NUM_CORES];
+ uint8_t fifo_level[RP2040_SIO_NUM_CORES];
+ uint32_t fifo_sticky[RP2040_SIO_NUM_CORES];
+ uint32_t div_dividend[RP2040_SIO_NUM_CORES];
+ uint32_t div_divisor[RP2040_SIO_NUM_CORES];
+ uint32_t div_quotient[RP2040_SIO_NUM_CORES];
+ uint32_t div_remainder[RP2040_SIO_NUM_CORES];
+ bool div_dirty[RP2040_SIO_NUM_CORES];
+ uint32_t interp_accum[RP2040_SIO_NUM_CORES]
+ [RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t interp_base[RP2040_SIO_NUM_CORES]
+ [RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_BASES];
+ uint32_t interp_ctrl[RP2040_SIO_NUM_CORES]
+ [RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t spinlock_st;
+};
+
+MemoryRegion *rp2040_sio_core_region(RP2040SioState *s, unsigned core);
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index dee90c57c8..fca71c8bef 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -269,8 +269,10 @@ qtests_arm = \
'rp2040-iobank0-test',
'rp2040-ioqspi-test',
'rp2040-pads-test',
+ 'rp2040-psm-test',
'rp2040-resets-test',
'rp2040-rosc-test',
+ 'rp2040-sio-test',
'rp2040-tbman-test',
'rp2040-uart-test',
'rp2040-vreg-test',
diff --git a/tests/qtest/rp2040-psm-test.c b/tests/qtest/rp2040-psm-test.c
new file mode 100644
index 0000000000..fa60b83fe2
--- /dev/null
+++ b/tests/qtest/rp2040-psm-test.c
@@ -0,0 +1,81 @@
+/*
+ * QTest testcase for the RP2040 power-on state machine.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "hw/misc/rp2040.h"
+#include "qemu/bitops.h"
+
+#define PSM_BASE 0x40010000
+#define PSM_FRCE_ON 0x00
+#define PSM_FRCE_OFF 0x04
+#define PSM_WDSEL 0x08
+#define PSM_DONE 0x0c
+#define PSM_VALID_MASK 0x0001ffff
+#define PSM_PROC1 BIT(16)
+
+static QTestState *rp2040_psm_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_reset_values(void)
+{
+ QTestState *qts = rp2040_psm_start();
+
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_ON), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_WDSEL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE), ==,
+ PSM_VALID_MASK);
+
+ qtest_quit(qts);
+}
+
+static void test_proc1_force_off(void)
+{
+ QTestState *qts = rp2040_psm_start();
+
+ qtest_writel(qts, PSM_BASE + RP2040_ATOMIC_SET + PSM_FRCE_OFF,
+ PSM_PROC1);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), ==,
+ PSM_PROC1);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE) & PSM_PROC1, ==,
+ 0);
+
+ qtest_writel(qts, PSM_BASE + RP2040_ATOMIC_CLR + PSM_FRCE_OFF,
+ PSM_PROC1);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE) & PSM_PROC1, ==,
+ PSM_PROC1);
+
+ qtest_quit(qts);
+}
+
+static void test_register_masks(void)
+{
+ QTestState *qts = rp2040_psm_start();
+
+ qtest_writel(qts, PSM_BASE + PSM_FRCE_ON, UINT32_MAX);
+ qtest_writel(qts, PSM_BASE + PSM_WDSEL, UINT32_MAX);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_ON), ==,
+ PSM_VALID_MASK);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_WDSEL), ==,
+ PSM_VALID_MASK);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-psm/reset-values", test_reset_values);
+ qtest_add_func("/rp2040-psm/proc1-force-off", test_proc1_force_off);
+ qtest_add_func("/rp2040-psm/register-masks", test_register_masks);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/rp2040-sio-test.c b/tests/qtest/rp2040-sio-test.c
new file mode 100644
index 0000000000..13ee513ebe
--- /dev/null
+++ b/tests/qtest/rp2040-sio-test.c
@@ -0,0 +1,270 @@
+/*
+ * QTest testcase for the RP2040 SIO block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define SIO_BASE 0xd0000000
+#define SIO_CPUID 0x000
+#define SIO_GPIO_IN 0x004
+#define SIO_GPIO_HI_IN 0x008
+#define SIO_GPIO_OUT 0x010
+#define SIO_GPIO_OUT_SET 0x014
+#define SIO_GPIO_OUT_CLR 0x018
+#define SIO_GPIO_OUT_XOR 0x01c
+#define SIO_GPIO_HI_OUT 0x030
+#define SIO_GPIO_HI_OUT_SET 0x034
+#define SIO_GPIO_HI_OUT_CLR 0x038
+#define SIO_GPIO_HI_OUT_XOR 0x03c
+#define SIO_FIFO_ST 0x050
+#define SIO_FIFO_RD 0x058
+#define SIO_SPINLOCK_ST 0x05c
+#define SIO_SPINLOCK0 0x100
+#define SIO_DIV_UDIVIDEND 0x060
+#define SIO_DIV_UDIVISOR 0x064
+#define SIO_DIV_SDIVIDEND 0x068
+#define SIO_DIV_SDIVISOR 0x06c
+#define SIO_DIV_QUOTIENT 0x070
+#define SIO_DIV_REMAINDER 0x074
+#define SIO_DIV_CSR 0x078
+#define SIO_INTERP0_ACCUM0 0x080
+#define SIO_INTERP0_ACCUM1 0x084
+#define SIO_INTERP0_BASE0 0x088
+#define SIO_INTERP0_BASE1 0x08c
+#define SIO_INTERP0_POP_LANE1 0x098
+#define SIO_INTERP0_PEEK_LANE0 0x0a0
+#define SIO_INTERP0_PEEK_LANE1 0x0a4
+#define SIO_INTERP0_CTRL_LANE0 0x0ac
+#define SIO_INTERP0_CTRL_LANE1 0x0b0
+#define SIO_INTERP0_ACCUM0_ADD 0x0b4
+#define SIO_INTERP0_BASE_1AND0 0x0bc
+#define SIO_INTERP1_ACCUM0 0x0c0
+#define SIO_INTERP1_BASE0 0x0c8
+#define SIO_INTERP1_BASE1 0x0cc
+#define SIO_INTERP1_PEEK_LANE0 0x0e0
+#define SIO_INTERP1_CTRL_LANE0 0x0ec
+
+#define INTERP_CTRL_SHIFT(n) (n)
+#define INTERP_CTRL_MASK(lsb, msb) (((lsb) << 5) | ((msb) << 10))
+#define INTERP_CTRL_SIGNED BIT(15)
+#define INTERP_CTRL_CROSS_RESULT BIT(17)
+#define INTERP0_CTRL_BLEND BIT(21)
+#define INTERP1_CTRL_CLAMP BIT(22)
+
+#define DIV_CSR_READY BIT(0)
+#define DIV_CSR_DIRTY BIT(1)
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_sio_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_CPUID), ==, 0);
+ g_assert_cmphex(qtest_readb(qts, SIO_BASE + SIO_CPUID), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_IN), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_IN), ==, BIT(1));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_OUT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_OUT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), ==, BIT(1));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_gpio_alias_registers(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT, BIT(3));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT_SET, BIT(5));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT_CLR, BIT(3));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT_XOR, BIT(7));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_OUT), ==,
+ BIT(5) | BIT(7));
+
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT, BIT(0));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT_SET, BIT(2));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT_CLR, BIT(0));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT_XOR, BIT(5));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_OUT), ==,
+ BIT(2) | BIT(5));
+
+ qtest_quit(qts);
+}
+
+static void test_sio_fifo_empty_read_sets_roe(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_readl(qts, SIO_BASE + SIO_FIFO_RD);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), ==,
+ BIT(3) | BIT(1));
+
+ qtest_writel(qts, SIO_BASE + SIO_FIFO_ST, BIT(3));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), ==, BIT(1));
+
+ qtest_quit(qts);
+}
+
+static void test_sio_spinlock_claim_release(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK0), ==, BIT(0));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK0), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), ==, BIT(0));
+
+ qtest_writel(qts, SIO_BASE + SIO_SPINLOCK0, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_divider(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_DIV_UDIVIDEND, 100);
+ qtest_writel(qts, SIO_BASE + SIO_DIV_UDIVISOR, 7);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_DIV_CSR), ==,
+ DIV_CSR_READY | DIV_CSR_DIRTY);
+ g_assert_cmpuint(qtest_readl(qts, SIO_BASE + SIO_DIV_REMAINDER), ==, 2);
+ g_assert_cmpuint(qtest_readl(qts, SIO_BASE + SIO_DIV_QUOTIENT), ==, 14);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_DIV_CSR), ==,
+ DIV_CSR_READY);
+
+ qtest_writel(qts, SIO_BASE + SIO_DIV_SDIVIDEND, -100);
+ qtest_writel(qts, SIO_BASE + SIO_DIV_SDIVISOR, 7);
+ g_assert_cmpint((int32_t)qtest_readl(qts,
+ SIO_BASE + SIO_DIV_QUOTIENT),
+ ==, -14);
+ g_assert_cmpint((int32_t)qtest_readl(qts,
+ SIO_BASE + SIO_DIV_REMAINDER),
+ ==, -2);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_mask_and_sign(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM0, 0x1234abcd);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ INTERP_CTRL_MASK(4, 7));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_ACCUM0_ADD),
+ ==, 0x000000c0);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ INTERP_CTRL_MASK(4, 7) | INTERP_CTRL_SIGNED);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_ACCUM0_ADD),
+ ==, 0xffffffc0);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_pop_cross_result(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t full_mask = INTERP_CTRL_MASK(0, 31);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM0, 123);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM1, 456);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE0, 1);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE1, 0);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ full_mask | INTERP_CTRL_CROSS_RESULT);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE1,
+ full_mask | INTERP_CTRL_CROSS_RESULT);
+
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE0),
+ ==, 124);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_POP_LANE1),
+ ==, 456);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE0),
+ ==, 457);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_blend(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t full_mask = INTERP_CTRL_MASK(0, 31);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ full_mask | INTERP0_CTRL_BLEND);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE1, full_mask);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM1, 128);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE_1AND0, 0x30005000);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE1),
+ ==, 0x00004000);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE_1AND0, 0xe000f000);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE1),
+ ==, 0x0000e800);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE1,
+ full_mask | INTERP_CTRL_SIGNED);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE_1AND0, 0xe000f000);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE1),
+ ==, 0xffffe800);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_clamp(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_CTRL_LANE0,
+ INTERP1_CTRL_CLAMP | INTERP_CTRL_SIGNED |
+ INTERP_CTRL_SHIFT(2) | INTERP_CTRL_MASK(0, 29));
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_BASE0, 0);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_BASE1, 255);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, -1024);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0),
+ ==, 0);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, 512);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0),
+ ==, 128);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, 1024);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0),
+ ==, 255);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-sio/reset-values", test_sio_reset_values);
+ qtest_add_func("/rp2040-sio/gpio-alias-registers",
+ test_sio_gpio_alias_registers);
+ qtest_add_func("/rp2040-sio/fifo-empty-read-sets-roe",
+ test_sio_fifo_empty_read_sets_roe);
+ qtest_add_func("/rp2040-sio/spinlock-claim-release",
+ test_sio_spinlock_claim_release);
+ qtest_add_func("/rp2040-sio/divider", test_sio_divider);
+ qtest_add_func("/rp2040-sio/interp-mask-and-sign",
+ test_sio_interp_mask_and_sign);
+ qtest_add_func("/rp2040-sio/interp-pop-cross-result",
+ test_sio_interp_pop_cross_result);
+ qtest_add_func("/rp2040-sio/interp-blend", test_sio_interp_blend);
+ qtest_add_func("/rp2040-sio/interp-clamp", test_sio_interp_clamp);
+
+ return g_test_run();
+}
--
2.55.0