From: gilles grimaud <[email protected]> Model the RP2040 PADS_BANK0 and PADS_QSPI register banks, including documented reset values, writable masks and atomic aliases. Wire both blocks into the SoC and add focused qtests for their reset and update behavior.
Signed-off-by: gilles grimaud <[email protected]> --- hw/arm/Kconfig | 1 + hw/arm/rp2040.c | 18 +- hw/misc/Kconfig | 3 + hw/misc/meson.build | 1 + hw/misc/rp2040_pads.c | 323 +++++++++++++++++++++++++++++++++ include/hw/arm/rp2040.h | 3 + include/hw/misc/rp2040_pads.h | 42 +++++ tests/qtest/meson.build | 1 + tests/qtest/rp2040-pads-test.c | 105 +++++++++++ 9 files changed, 495 insertions(+), 2 deletions(-) create mode 100644 hw/misc/rp2040_pads.c create mode 100644 include/hw/misc/rp2040_pads.h create mode 100644 tests/qtest/rp2040-pads-test.c diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 384ea0520f..0c455fe5ce 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -380,6 +380,7 @@ config RP2040 select PL011 select RP2040_CLOCKS select RP2040_NYI + select RP2040_PADS select RP2040_PLL select RP2040_PSM select RP2040_RESETS diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c index 7b3bb0e7b1..894f5ec3c2 100644 --- a/hw/arm/rp2040.c +++ b/hw/arm/rp2040.c @@ -50,8 +50,6 @@ static const struct { } rp2040_unimplemented[] = { { "rp2040.iobank0", 0x40014000, 0x4000 }, { "rp2040.ioqspi", 0x40018000, 0x4000 }, - { "rp2040.padsbank0", 0x4001c000, 0x4000 }, - { "rp2040.padsqspi", 0x40020000, 0x4000 }, { "rp2040.busctrl", 0x40030000, 0x4000 }, { "rp2040.uart0_aliases", 0x40035000, 0x3000 }, { "rp2040.uart1_aliases", 0x40039000, 0x3000 }, @@ -166,6 +164,10 @@ static void rp2040_soc_init(Object *obj) } object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS); + object_initialize_child(obj, "pads-bank0", &s->pads_bank0, + TYPE_RP2040_PADS_BANK0); + object_initialize_child(obj, "pads-qspi", &s->pads_qspi, + TYPE_RP2040_PADS_QSPI); object_initialize_child(obj, "pll-sys", &s->pll_sys, TYPE_RP2040_PLL); qdev_prop_set_string(DEVICE(&s->pll_sys), "trace-name", @@ -380,6 +382,18 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp) } sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pads_bank0), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pads_bank0), 0, + RP2040_PADS_BANK0_BASE); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pads_qspi), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pads_qspi), 0, + RP2040_PADS_QSPI_BASE); + qdev_connect_clock_in(DEVICE(&s->watchdog), "clk-ref", qdev_get_clock_out(DEVICE(&s->clocks), "clk-ref")); if (!sysbus_realize(SYS_BUS_DEVICE(&s->watchdog), errp)) { diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index c6a815f9bc..fc1d5bd58b 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -107,6 +107,9 @@ config RP2040_CLOCKS config RP2040_NYI bool +config RP2040_PADS + bool + config RP2040_PLL bool diff --git a/hw/misc/meson.build b/hw/misc/meson.build index cdbfa7de31..a60226c073 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -100,6 +100,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files( )) system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c')) system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c')) +system_ss.add(when: 'CONFIG_RP2040_PADS', if_true: files('rp2040_pads.c')) system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c')) system_ss.add(when: 'CONFIG_RP2040_PSM', if_true: files('rp2040_psm.c')) system_ss.add(when: 'CONFIG_RP2040_RESETS', if_true: files('rp2040_resets.c')) diff --git a/hw/misc/rp2040_pads.c b/hw/misc/rp2040_pads.c new file mode 100644 index 0000000000..e18dc0fa90 --- /dev/null +++ b/hw/misc/rp2040_pads.c @@ -0,0 +1,323 @@ +/* + * RP2040 pad control emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/rp2040_nyi.h" +#include "hw/misc/rp2040_pads.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" + +#define PADS_BANK0_VOLTAGE_SELECT 0x00 +#define PADS_BANK0_GPIO0 0x04 +#define PADS_BANK0_GPIO29 0x78 +#define PADS_BANK0_SWCLK 0x7c +#define PADS_BANK0_SWD 0x80 + +#define PADS_QSPI_VOLTAGE_SELECT 0x00 +#define PADS_QSPI_SCLK 0x04 +#define PADS_QSPI_SS 0x18 + +#define PADS_VOLTAGE_SELECT_MASK BIT(0) +#define PADS_PAD_MASK 0xff +#define PADS_BANK0_GPIO_RESET 0x56 +#define PADS_BANK0_SWD_RESET 0x5a +#define PADS_QSPI_SCLK_RESET 0x56 +#define PADS_QSPI_SD_RESET 0x52 +#define PADS_QSPI_SS_RESET 0x5a + +#define ATOMIC_ALIAS_MASK 0x3000 +#define ATOMIC_XOR 0x1000 +#define ATOMIC_SET 0x2000 +#define ATOMIC_CLR 0x3000 + +static uint32_t rp2040_pads_apply_alias(uint32_t old, uint32_t value, + hwaddr alias) +{ + switch (alias) { + case ATOMIC_XOR: + return old ^ value; + case ATOMIC_SET: + return old | value; + case ATOMIC_CLR: + return old & ~value; + default: + return value; + } +} + +static bool rp2040_pads_bank0_gpio_offset(hwaddr offset, unsigned *index) +{ + if (offset < PADS_BANK0_GPIO0 || offset > PADS_BANK0_GPIO29 || + (offset & 0x3)) { + return false; + } + + *index = (offset - PADS_BANK0_GPIO0) / sizeof(uint32_t); + return *index < ARRAY_SIZE(((RP2040PadsBank0State *)0)->gpio); +} + +static bool rp2040_pads_qspi_pad_offset(hwaddr offset, unsigned *index) +{ + if (offset < PADS_QSPI_SCLK || offset > PADS_QSPI_SS || + (offset & 0x3)) { + return false; + } + + *index = (offset - PADS_QSPI_SCLK) / sizeof(uint32_t); + return *index < ARRAY_SIZE(((RP2040PadsQspiState *)0)->pad); +} + +static uint64_t rp2040_pads_bank0_read(void *opaque, hwaddr addr, + unsigned size) +{ + RP2040PadsBank0State *s = opaque; + hwaddr offset = addr & 0xfff; + unsigned index; + uint64_t value; + + switch (offset) { + case PADS_BANK0_VOLTAGE_SELECT: + value = s->voltage_select; + break; + case PADS_BANK0_SWCLK: + value = s->swclk; + break; + case PADS_BANK0_SWD: + value = s->swd; + break; + default: + if (rp2040_pads_bank0_gpio_offset(offset, &index)) { + value = s->gpio[index]; + } else { + value = 0; + rp2040_log_unimplemented_read("padsbank0", size, + RP2040_PADS_BANK0_BASE + addr, + offset, value); + } + break; + } + + return value; +} + +static void rp2040_pads_bank0_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + RP2040PadsBank0State *s = opaque; + hwaddr alias = addr & ATOMIC_ALIAS_MASK; + hwaddr offset = addr & 0xfff; + unsigned index; + uint32_t value = value64; + + switch (offset) { + case PADS_BANK0_VOLTAGE_SELECT: + s->voltage_select = + rp2040_pads_apply_alias(s->voltage_select, value, alias) & + PADS_VOLTAGE_SELECT_MASK; + break; + case PADS_BANK0_SWCLK: + s->swclk = rp2040_pads_apply_alias(s->swclk, value, alias) & + PADS_PAD_MASK; + break; + case PADS_BANK0_SWD: + s->swd = rp2040_pads_apply_alias(s->swd, value, alias) & + PADS_PAD_MASK; + break; + default: + if (rp2040_pads_bank0_gpio_offset(offset, &index)) { + s->gpio[index] = rp2040_pads_apply_alias(s->gpio[index], value, + alias) & PADS_PAD_MASK; + } else { + rp2040_log_unimplemented_write("padsbank0", size, + RP2040_PADS_BANK0_BASE + addr, + offset, value64); + } + break; + } +} + +static uint64_t rp2040_pads_qspi_read(void *opaque, hwaddr addr, + unsigned size) +{ + RP2040PadsQspiState *s = opaque; + hwaddr offset = addr & 0xfff; + unsigned index; + uint64_t value; + + switch (offset) { + case PADS_QSPI_VOLTAGE_SELECT: + value = s->voltage_select; + break; + default: + if (rp2040_pads_qspi_pad_offset(offset, &index)) { + value = s->pad[index]; + } else { + value = 0; + rp2040_log_unimplemented_read("padsqspi", size, + RP2040_PADS_QSPI_BASE + addr, + offset, value); + } + break; + } + + return value; +} + +static void rp2040_pads_qspi_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + RP2040PadsQspiState *s = opaque; + hwaddr alias = addr & ATOMIC_ALIAS_MASK; + hwaddr offset = addr & 0xfff; + unsigned index; + uint32_t value = value64; + + switch (offset) { + case PADS_QSPI_VOLTAGE_SELECT: + s->voltage_select = + rp2040_pads_apply_alias(s->voltage_select, value, alias) & + PADS_VOLTAGE_SELECT_MASK; + break; + default: + if (rp2040_pads_qspi_pad_offset(offset, &index)) { + s->pad[index] = rp2040_pads_apply_alias(s->pad[index], value, + alias) & PADS_PAD_MASK; + } else { + rp2040_log_unimplemented_write("padsqspi", size, + RP2040_PADS_QSPI_BASE + addr, + offset, value64); + } + break; + } +} + +static const MemoryRegionOps rp2040_pads_bank0_ops = { + .read = rp2040_pads_bank0_read, + .write = rp2040_pads_bank0_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps rp2040_pads_qspi_ops = { + .read = rp2040_pads_qspi_read, + .write = rp2040_pads_qspi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void rp2040_pads_bank0_reset(DeviceState *dev) +{ + RP2040PadsBank0State *s = RP2040_PADS_BANK0(dev); + unsigned i; + + s->voltage_select = 0; + for (i = 0; i < ARRAY_SIZE(s->gpio); i++) { + s->gpio[i] = PADS_BANK0_GPIO_RESET; + } + s->swclk = PADS_BANK0_SWD_RESET; + s->swd = PADS_BANK0_SWD_RESET; +} + +static void rp2040_pads_qspi_reset(DeviceState *dev) +{ + RP2040PadsQspiState *s = RP2040_PADS_QSPI(dev); + + s->voltage_select = 0; + s->pad[0] = PADS_QSPI_SCLK_RESET; + s->pad[1] = PADS_QSPI_SD_RESET; + s->pad[2] = PADS_QSPI_SD_RESET; + s->pad[3] = PADS_QSPI_SD_RESET; + s->pad[4] = PADS_QSPI_SD_RESET; + s->pad[5] = PADS_QSPI_SS_RESET; +} + +static void rp2040_pads_bank0_init(Object *obj) +{ + RP2040PadsBank0State *s = RP2040_PADS_BANK0(obj); + + memory_region_init_io(&s->iomem, obj, &rp2040_pads_bank0_ops, s, + "rp2040.padsbank0", RP2040_PADS_BANK0_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static void rp2040_pads_qspi_init(Object *obj) +{ + RP2040PadsQspiState *s = RP2040_PADS_QSPI(obj); + + memory_region_init_io(&s->iomem, obj, &rp2040_pads_qspi_ops, s, + "rp2040.padsqspi", RP2040_PADS_QSPI_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static const VMStateDescription rp2040_pads_bank0_vmstate = { + .name = TYPE_RP2040_PADS_BANK0, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(voltage_select, RP2040PadsBank0State), + VMSTATE_UINT32_ARRAY(gpio, RP2040PadsBank0State, 30), + VMSTATE_UINT32(swclk, RP2040PadsBank0State), + VMSTATE_UINT32(swd, RP2040PadsBank0State), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription rp2040_pads_qspi_vmstate = { + .name = TYPE_RP2040_PADS_QSPI, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(voltage_select, RP2040PadsQspiState), + VMSTATE_UINT32_ARRAY(pad, RP2040PadsQspiState, 6), + VMSTATE_END_OF_LIST() + } +}; + +static void rp2040_pads_bank0_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_pads_bank0_reset); + dc->vmsd = &rp2040_pads_bank0_vmstate; +} + +static void rp2040_pads_qspi_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_pads_qspi_reset); + dc->vmsd = &rp2040_pads_qspi_vmstate; +} + +static const TypeInfo rp2040_pads_bank0_info = { + .name = TYPE_RP2040_PADS_BANK0, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RP2040PadsBank0State), + .instance_init = rp2040_pads_bank0_init, + .class_init = rp2040_pads_bank0_class_init, +}; + +static const TypeInfo rp2040_pads_qspi_info = { + .name = TYPE_RP2040_PADS_QSPI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RP2040PadsQspiState), + .instance_init = rp2040_pads_qspi_init, + .class_init = rp2040_pads_qspi_class_init, +}; + +static void rp2040_pads_register_types(void) +{ + type_register_static(&rp2040_pads_bank0_info); + type_register_static(&rp2040_pads_qspi_info); +} +type_init(rp2040_pads_register_types) diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h index 6c6eb3d927..033fc17ffc 100644 --- a/include/hw/arm/rp2040.h +++ b/include/hw/arm/rp2040.h @@ -14,6 +14,7 @@ #include "hw/core/clock.h" #include "hw/core/sysbus.h" #include "hw/misc/rp2040_clocks.h" +#include "hw/misc/rp2040_pads.h" #include "hw/misc/rp2040_pll.h" #include "hw/misc/rp2040_psm.h" #include "hw/misc/rp2040_resets.h" @@ -46,6 +47,8 @@ struct RP2040State { ARMv7MState armv7m; PL011State uart[2]; RP2040ClocksState clocks; + RP2040PadsBank0State pads_bank0; + RP2040PadsQspiState pads_qspi; RP2040PllState pll_sys; RP2040PllState pll_usb; RP2040PsmState psm; diff --git a/include/hw/misc/rp2040_pads.h b/include/hw/misc/rp2040_pads.h new file mode 100644 index 0000000000..3fc6cd54bb --- /dev/null +++ b/include/hw/misc/rp2040_pads.h @@ -0,0 +1,42 @@ +/* + * RP2040 pad control emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RP2040_PADS_H +#define HW_MISC_RP2040_PADS_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RP2040_PADS_QSPI "rp2040-pads-qspi" +OBJECT_DECLARE_SIMPLE_TYPE(RP2040PadsQspiState, RP2040_PADS_QSPI) + +#define TYPE_RP2040_PADS_BANK0 "rp2040-pads-bank0" +OBJECT_DECLARE_SIMPLE_TYPE(RP2040PadsBank0State, RP2040_PADS_BANK0) + +#define RP2040_PADS_BANK0_BASE 0x4001c000 +#define RP2040_PADS_BANK0_SIZE 0x4000 +#define RP2040_PADS_QSPI_BASE 0x40020000 +#define RP2040_PADS_QSPI_SIZE 0x4000 + +struct RP2040PadsBank0State { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t voltage_select; + uint32_t gpio[30]; + uint32_t swclk; + uint32_t swd; +}; + +struct RP2040PadsQspiState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t voltage_select; + uint32_t pad[6]; +}; + +#endif diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index e5ddd4ce3b..07a9e97610 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -257,6 +257,7 @@ qtests_arm = \ (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test', 'rp2040-clocks-test', + 'rp2040-pads-test', 'rp2040-resets-test', 'rp2040-rosc-test', 'rp2040-tbman-test', diff --git a/tests/qtest/rp2040-pads-test.c b/tests/qtest/rp2040-pads-test.c new file mode 100644 index 0000000000..cf55ed5c3b --- /dev/null +++ b/tests/qtest/rp2040-pads-test.c @@ -0,0 +1,105 @@ +/* + * QTest testcase for the RP2040 pad controls. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" + +#define PADS_BANK0_BASE 0x4001c000 +#define PADS_BANK0_VOLTAGE 0x00 +#define PADS_BANK0_GPIO0 0x04 +#define PADS_BANK0_GPIO29 0x78 +#define PADS_BANK0_SWCLK 0x7c +#define PADS_BANK0_SWD 0x80 + +#define PADS_QSPI_BASE 0x40020000 +#define PADS_QSPI_VOLTAGE 0x00 +#define PADS_QSPI_SCLK 0x04 +#define PADS_QSPI_SD0 0x08 +#define PADS_QSPI_SD3 0x14 +#define PADS_QSPI_SS 0x18 + +#define ATOMIC_XOR 0x1000 +#define ATOMIC_SET 0x2000 +#define ATOMIC_CLR 0x3000 + +static QTestState *rp2040_pads_start(void) +{ + return qtest_init("-machine raspi-pico"); +} + +static void test_bank0(void) +{ + QTestState *qts = rp2040_pads_start(); + + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + + PADS_BANK0_VOLTAGE), ==, 0); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0), + ==, 0x56); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO29), + ==, 0x56); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_SWCLK), + ==, 0x5a); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_SWD), + ==, 0x5a); + + qtest_writel(qts, PADS_BANK0_BASE + PADS_BANK0_VOLTAGE, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + + PADS_BANK0_VOLTAGE), ==, 1); + + qtest_writel(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0, 0x12); + qtest_writel(qts, PADS_BANK0_BASE + ATOMIC_SET + PADS_BANK0_GPIO0, + 0x81); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0), + ==, 0x93); + qtest_writel(qts, PADS_BANK0_BASE + ATOMIC_CLR + PADS_BANK0_GPIO0, + 0x11); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0), + ==, 0x82); + qtest_writel(qts, PADS_BANK0_BASE + ATOMIC_XOR + PADS_BANK0_GPIO0, + 0x03); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0), + ==, 0x81); + + qtest_quit(qts); +} + +static void test_qspi(void) +{ + QTestState *qts = rp2040_pads_start(); + + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_VOLTAGE), + ==, 0); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SCLK), + ==, 0x56); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SD0), + ==, 0x52); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SD3), + ==, 0x52); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS), + ==, 0x5a); + + qtest_writel(qts, PADS_QSPI_BASE + PADS_QSPI_SS, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS), + ==, 0xff); + qtest_writel(qts, PADS_QSPI_BASE + ATOMIC_CLR + PADS_QSPI_SS, 0x0f); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS), + ==, 0xf0); + qtest_writel(qts, PADS_QSPI_BASE + ATOMIC_XOR + PADS_QSPI_SS, 0x33); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS), + ==, 0xc3); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/rp2040-pads/bank0", test_bank0); + qtest_add_func("/rp2040-pads/qspi", test_qspi); + + return g_test_run(); +} -- 2.55.0
