diff --git a/include/hw/misc/interleaver.h b/include/hw/misc/interleaver.h
new file mode 100644
index 0000000000..953d987556
--- /dev/null
+++ b/include/hw/misc/interleaver.h
@@ -0,0 +1,40 @@
+/*
+ * QEMU Interleaver device
+ *
+ * Copyright (C) 2020 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_INTERLEAVER_H
+#define HW_MISC_INTERLEAVER_H
+
+/*
+ * Example of 32x16 interleaver accesses (32-bit bus, 2x 16-bit banks):
+ *
+ * Each interleaved 32-bit access on the bus results in contiguous 16-bit
+ * access on each banked device:
+ *
+ *                      ____________________________________________________
+ *   Bus accesses       |        1st 32-bit       |        2nd 32-bit       |
+ *                      -----------------------------------------------------
+ *                             |            |            |            |
+ *                             v            |            v            |
+ *                      ______________      |     ______________      |
+ *   1st bank accesses  | 1st 16-bit |      |     | 2nd 16-bit |      |
+ *                      --------------      |     --------------      |
+ *                                          v                         v
+ *                                   ______________            ______________
+ *   2nd bank accesses               | 1st 16-bit |            | 2nd 16-bit |
+ *                                   --------------            --------------
+ */
+
+#define TYPE_INTERLEAVER_16X8_DEVICE    "interleaver-16x8-device"
+#define TYPE_INTERLEAVER_32X8_DEVICE    "interleaver-32x8-device"
+#define TYPE_INTERLEAVER_32X16_DEVICE   "interleaver-32x16-device"
+#define TYPE_INTERLEAVER_64X8_DEVICE    "interleaver-64x8-device"
+#define TYPE_INTERLEAVER_64X16_DEVICE   "interleaver-64x16-device"
+#define TYPE_INTERLEAVER_64X32_DEVICE   "interleaver-64x32-device"
+
+#endif
+
diff --git a/hw/misc/interleaver.c b/hw/misc/interleaver.c
new file mode 100644
index 0000000000..46099e9e11
--- /dev/null
+++ b/hw/misc/interleaver.c
@@ -0,0 +1,254 @@
+/*
+ * QEMU Interleaver device
+ *
+ * The interleaver device to allow making interleaved memory accesses.
+ *
+ * This device support using the following configurations (INPUT x OUTPUT):
+ * 16x8, 32x8, 32x16, 64x8, 64x16 and 64x32.
+ *
+ * Copyright (C) 2020 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/interleaver.h"
+#include "trace.h"
+
+#define TYPE_INTERLEAVER_DEVICE "interleaver-device"
+
+typedef struct InterleaverDeviceClass {
+    /*< private >*/
+    SysBusDeviceClass parent_class;
+    /*< public >*/
+    MemoryRegionOps ops;
+    unsigned input_access_size;
+    unsigned output_access_size;
+    MemOp output_memop;
+    unsigned mr_count;
+    char *name;
+} InterleaverDeviceClass;
+
+#define INTERLEAVER_DEVICE_CLASS(klass) \
+    OBJECT_CLASS_CHECK(InterleaverDeviceClass, (klass), TYPE_INTERLEAVER_DEVICE)
+#define INTERLEAVER_DEVICE_GET_CLASS(obj) \
+    OBJECT_GET_CLASS(InterleaverDeviceClass, (obj), TYPE_INTERLEAVER_DEVICE)
+
+#define INTERLEAVER_REGIONS_MAX 8 /* 64x8 */
+
+typedef struct {
+    /*< private >*/
+    SysBusDevice parent_obj;
+    /*< public >*/
+    MemoryRegion iomem;
+    uint64_t size;
+    MemoryRegion *mr[INTERLEAVER_REGIONS_MAX];
+} InterleaverDeviceState;
+
+#define INTERLEAVER_DEVICE(obj) \
+    OBJECT_CHECK(InterleaverDeviceState, (obj), TYPE_INTERLEAVER_DEVICE)
+
+static const char *memresult_str[] = {"OK", "ERROR", "DECODE_ERROR"};
+
+static const char *emtpy_mr_name = "EMPTY";
+
+static MemTxResult interleaver_read(void *opaque,
+                                    hwaddr offset, uint64_t *data,
+                                    unsigned size, MemTxAttrs attrs)
+{
+    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
+    InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_GET_CLASS(s);
+    unsigned idx = (offset / idc->output_access_size) & (idc->mr_count - 1);
+    hwaddr addr = (offset & ~(idc->input_access_size - 1)) / idc->mr_count;
+    MemTxResult r = MEMTX_ERROR;
+
+    trace_interleaver_read_enter(idc->input_access_size,
+                                 idc->output_access_size, size,
+                                 idc->mr_count, idx,
+                                 s->mr[idx] ? memory_region_name(s->mr[idx])
+                                            : emtpy_mr_name,
+                                 offset, addr);
+    if (s->mr[idx]) {
+        r = memory_region_dispatch_read(s->mr[idx],
+                                        addr,
+                                        data,
+                                        idc->output_memop,
+                                        attrs);
+    }
+    trace_interleaver_read_exit(size, *data, memresult_str[r]);
+
+    return r;
+}
+
+static MemTxResult interleaver_write(void *opaque,
+                                     hwaddr offset, uint64_t data,
+                                     unsigned size, MemTxAttrs attrs)
+{
+    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
+    InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_GET_CLASS(s);
+    unsigned idx = (offset / idc->output_access_size) & (idc->mr_count - 1);
+    hwaddr addr = (offset & ~(idc->input_access_size - 1)) / idc->mr_count;
+    MemTxResult r = MEMTX_ERROR;
+
+    trace_interleaver_write_enter(idc->input_access_size,
+                                  idc->output_access_size, size,
+                                  idc->mr_count, idx,
+                                  s->mr[idx] ? memory_region_name(s->mr[idx])
+                                             : emtpy_mr_name,
+                                  offset, addr);
+    if (s->mr[idx]) {
+        r = memory_region_dispatch_write(s->mr[idx],
+                                         addr,
+                                         data,
+                                         idc->output_memop,
+                                         attrs);
+    }
+    trace_interleaver_write_exit(size, data, memresult_str[r]);
+
+    return r;
+}
+
+static void interleaver_realize(DeviceState *dev, Error **errp)
+{
+    InterleaverDeviceState *s = INTERLEAVER_DEVICE(dev);
+    InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_GET_CLASS(dev);
+    uint64_t expected_mr_size;
+
+    if (s->size == 0) {
+        error_setg(errp, "property 'size' not specified or zero");
+        return;
+    }
+    if (!QEMU_IS_ALIGNED(s->size, idc->input_access_size)) {
+        error_setg(errp, "property 'size' must be multiple of %u",
+                   idc->input_access_size);
+        return;
+    }
+
+    expected_mr_size = s->size / idc->mr_count;
+    for (unsigned i = 0; i < idc->mr_count; i++) {
+        if (s->mr[i] && memory_region_size(s->mr[i]) != expected_mr_size) {
+            error_setg(errp,
+                       "memory region #%u (%s) size mismatches interleaver",
+                       i, memory_region_name(s->mr[i]));
+            return;
+        }
+    }
+    memory_region_init_io(&s->iomem, OBJECT(s), &idc->ops, s,
+                          idc->name, s->size);
+    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static Property interleaver_properties[] = {
+    DEFINE_PROP_UINT64("size", InterleaverDeviceState, size, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void interleaver_class_init(ObjectClass *oc, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+
+    dc->realize = interleaver_realize;
+    device_class_set_props(dc, interleaver_properties);
+    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static void interleaver_class_add_properties(ObjectClass *oc,
+                                             unsigned input_bits,
+                                             unsigned output_bits)
+{
+    InterleaverDeviceClass *idc = INTERLEAVER_DEVICE_CLASS(oc);
+
+    idc->name = g_strdup_printf("interleaver-%ux%u", input_bits, output_bits);
+    idc->input_access_size = input_bits >> 3;
+    idc->output_access_size = output_bits >> 3;
+    idc->output_memop = size_memop(idc->output_access_size);
+    idc->mr_count = input_bits / output_bits;
+    idc->ops = (MemoryRegionOps){
+        .read_with_attrs = interleaver_read,
+        .write_with_attrs = interleaver_write,
+        .valid.min_access_size = 1,
+        .valid.max_access_size = idc->input_access_size,
+        .impl.min_access_size = idc->output_access_size,
+        .impl.max_access_size = idc->output_access_size,
+        .endianness = DEVICE_NATIVE_ENDIAN,
+    };
+
+    for (unsigned i = 0; i < idc->mr_count; i++) {
+        g_autofree char *name = g_strdup_printf("mr%u", i);
+        object_class_property_add_link(oc, name, TYPE_MEMORY_REGION,
+                                       offsetof(InterleaverDeviceState, mr[i]),
+                                       qdev_prop_allow_set_link_before_realize,
+                                       OBJ_PROP_LINK_STRONG);
+    }
+}
+
+static void interleaver_16x8_class_init(ObjectClass *oc, void *data)
+{
+    interleaver_class_add_properties(oc, 16, 8);
+};
+
+static void interleaver_32x8_class_init(ObjectClass *oc, void *data)
+{
+    interleaver_class_add_properties(oc, 32, 8);
+};
+
+static void interleaver_32x16_class_init(ObjectClass *oc, void *data)
+{
+    interleaver_class_add_properties(oc, 32, 16);
+};
+
+static void interleaver_64x8_class_init(ObjectClass *oc, void *data)
+{
+    interleaver_class_add_properties(oc, 64, 8);
+};
+
+static void interleaver_64x16_class_init(ObjectClass *oc, void *data)
+{
+    interleaver_class_add_properties(oc, 64, 16);
+};
+
+static void interleaver_64x32_class_init(ObjectClass *oc, void *data)
+{
+    interleaver_class_add_properties(oc, 64, 32);
+};
+
+static const TypeInfo interleaver_device_types[] = {
+    {
+        .name           = TYPE_INTERLEAVER_16X8_DEVICE,
+        .parent         = TYPE_INTERLEAVER_DEVICE,
+        .class_init     = interleaver_16x8_class_init,
+    }, {
+        .name           = TYPE_INTERLEAVER_32X8_DEVICE,
+        .parent         = TYPE_INTERLEAVER_DEVICE,
+        .class_init     = interleaver_32x8_class_init,
+    }, {
+        .name           = TYPE_INTERLEAVER_32X16_DEVICE,
+        .parent         = TYPE_INTERLEAVER_DEVICE,
+        .class_init     = interleaver_32x16_class_init,
+    }, {
+        .name           = TYPE_INTERLEAVER_64X8_DEVICE,
+        .parent         = TYPE_INTERLEAVER_DEVICE,
+        .class_init     = interleaver_64x8_class_init,
+    }, {
+        .name           = TYPE_INTERLEAVER_64X16_DEVICE,
+        .parent         = TYPE_INTERLEAVER_DEVICE,
+        .class_init     = interleaver_64x16_class_init,
+    }, {
+        .name           = TYPE_INTERLEAVER_64X32_DEVICE,
+        .parent         = TYPE_INTERLEAVER_DEVICE,
+        .class_init     = interleaver_64x32_class_init,
+    }, {
+        .name           = TYPE_INTERLEAVER_DEVICE,
+        .parent         = TYPE_SYS_BUS_DEVICE,
+        .instance_size  = sizeof(InterleaverDeviceState),
+        .class_size     = sizeof(InterleaverDeviceClass),
+        .class_init     = interleaver_class_init,
+        .abstract       = true,
+    }
+};
+
+DEFINE_TYPES(interleaver_device_types)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0886eb3d2b..1efce3dd27 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1961,6 +1961,12 @@ S: Maintained
 F: include/hw/misc/empty_slot.h
 F: hw/misc/empty_slot.c
 
+Interleaver device
+M: Philippe Mathieu-Daudé <f4bug@amsat.org>
+S: Maintained
+F: include/hw/misc/interleaver.h
+F: hw/misc/interleaver.c
+
 Standard VGA
 M: Gerd Hoffmann <kraxel@redhat.com>
 S: Maintained
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 92c397ca07..7ed0f4ccc7 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -21,6 +21,10 @@ config SGA
     bool
     depends on ISA_BUS
 
+config INTERLEAVER
+    bool
+    default y
+
 config ISA_TESTDEV
     bool
     default y if TEST_DEVICES
diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs
index 6be3d255ab..aa753a847f 100644
--- a/hw/misc/Makefile.objs
+++ b/hw/misc/Makefile.objs
@@ -12,6 +12,7 @@ common-obj-$(CONFIG_PCA9552) += pca9552.o
 common-obj-$(CONFIG_UNIMP) += unimp.o
 common-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o
 common-obj-$(CONFIG_FW_CFG_DMA) += vmcoreinfo.o
+common-obj-$(CONFIG_INTERLEAVER) += interleaver.o
 
 # ARM devices
 common-obj-$(CONFIG_PL310) += arm_l2x0.o
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 066752aa90..1b0db146b4 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -217,3 +217,9 @@ grlib_apb_pnp_read(uint64_t addr, uint32_t value) "APB PnP read addr:0x%03"PRIx6
 # pca9552.c
 pca955x_gpio_status(const char *description, const char *buf) "%s GPIOs 0-15 [%s]"
 pca955x_gpio_change(const char *description, unsigned id, unsigned prev_state, unsigned current_state) "%s GPIO id:%u status: %u -> %u"
+
+# interleaver.c
+interleaver_read_enter(unsigned input_access_size, unsigned output_access_size, unsigned size, unsigned mr_count, unsigned index, const char *mr_name, uint64_t in_addr, uint64_t out_addr) "rd ixs:%u oxs:%u sz:%u mr_cnt:%u mr_idx:%u mr_name:'%s' iadr:0x%"PRIx64" oadr:0x%"PRIx64
+interleaver_read_exit(unsigned size, uint64_t value, const char *result) "rd size:%u value:0x%08"PRIx64" result: %s"
+interleaver_write_enter(unsigned input_access_size, unsigned output_access_size, unsigned size, unsigned mr_count, unsigned index, const char *mr_name, uint64_t in_addr, uint64_t out_addr) "wr ixs:%u oxs:%u sz:%u mr_cnt:%u mr_idx:%u mr_name:'%s' iadr:0x%"PRIx64" oadr:0x%"PRIx64
+interleaver_write_exit(unsigned size, uint64_t value, const char *result) "wr size:%u value:0x%08"PRIx64" result: %s"
