--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+
+#include "meson_drv.h"
+#include "meson_registers.h"
+#include "meson_rdma.h"
+
+/*
+ * The VPU embeds a "Register DMA" that can write a sequence of registers
+ * on the VPU AHB bus, either manually or triggered by an internal IRQ
+ * event like VSYNC or a line input counter.
+ * The initial implementation handles a single channel (over 8), triggered
+ * by the VSYNC irq and does not handle the RDMA irq.
+ */
+
+#define RDMA_DESC_SIZE (sizeof(uint32_t) * 2)
+
+int meson_rdma_init(struct meson_drm *priv)
+{
+       if (!priv->rdma.addr) {
+               /* Allocate a PAGE buffer */
+               priv->rdma.addr =
+                       dma_alloc_coherent(priv->dev, SZ_4K,
+                                          &priv->rdma.addr_phys,
+                                          GFP_KERNEL);
+               if (!priv->rdma.addr)
+                       return -ENOMEM;
+       }
+
+       priv->rdma.offset = 0;
+
+       writel_relaxed(RDMA_CTRL_SW_RESET,
+                      priv->io_base + _REG(RDMA_CTRL));
+       writel_relaxed(RDMA_DEFAULT_CONFIG |
+                      FIELD_PREP(RDMA_CTRL_AHB_WR_BURST, 3) |
+                      FIELD_PREP(RDMA_CTRL_AHB_RD_BURST, 0),
+                      priv->io_base + _REG(RDMA_CTRL));
+
+       return 0;
+}
+
+void meson_rdma_free(struct meson_drm *priv)
+{
+       if (!priv->rdma.addr && !priv->rdma.addr_phys)
+               return;
+
+       meson_rdma_stop(priv);
+
+       dma_free_coherent(priv->dev, SZ_4K,
+                         priv->rdma.addr, priv->rdma.addr_phys);
+
+       priv->rdma.addr = NULL;
+       priv->rdma.addr_phys = (dma_addr_t)NULL;
+}
+
+void meson_rdma_setup(struct meson_drm *priv)
+{
+       /* Channel 1: Write Flag, No Address Increment */
+       writel_bits_relaxed(RDMA_ACCESS_RW_FLAG_CHAN1 |
+                           RDMA_ACCESS_ADDR_INC_CHAN1,
+                           RDMA_ACCESS_RW_FLAG_CHAN1,
+                           priv->io_base + _REG(RDMA_ACCESS_AUTO));
+}
+
+void meson_rdma_stop(struct meson_drm *priv)
+{
+       writel_bits_relaxed(RDMA_IRQ_CLEAR_CHAN1,
+                           RDMA_IRQ_CLEAR_CHAN1,
+                           priv->io_base + _REG(RDMA_CTRL));
+
+       /* Stop Channel 1 */
+       writel_bits_relaxed(RDMA_ACCESS_TRIGGER_CHAN1,
+                           FIELD_PREP(RDMA_ACCESS_ADDR_INC_CHAN1,
+                                      RDMA_ACCESS_TRIGGER_STOP),
+                           priv->io_base + _REG(RDMA_ACCESS_AUTO));
+}
+
+void meson_rdma_reset(struct meson_drm *priv)
+{
+       meson_rdma_stop(priv);
+
+       priv->rdma.offset = 0;
+}
+
+static void meson_rdma_writel(struct meson_drm *priv, uint32_t val,
+                             uint32_t reg)
+{
+       if (priv->rdma.offset >= (SZ_4K / RDMA_DESC_SIZE)) {
+               dev_warn_once(priv->dev, "%s: overflow\n", __func__);
+               return;
+       }
+
+       priv->rdma.addr[priv->rdma.offset++] = reg;
+       priv->rdma.addr[priv->rdma.offset++] = val;
+}
+
+/*
+ * This will add the register to the RDMA buffer and write it to the
+ * hardware at the same time.
+ * When meson_rdma_flush is called, the RDMA will replay the register
+ * writes in order.
+ */
+void meson_rdma_writel_sync(struct meson_drm *priv, uint32_t val, uint32_t reg)
+{
+       meson_rdma_writel(priv, val, reg);
+
+       writel_relaxed(val, priv->io_base + _REG(reg));
+}
+
+void meson_rdma_flush(struct meson_drm *priv)
+{
+       meson_rdma_stop(priv);
+
+       /* Start of Channel 1 register writes buffer */
+       writel(priv->rdma.addr_phys,
+              priv->io_base + _REG(RDMA_AHB_START_ADDR_1));
+
+       /* Last byte on Channel 1 register writes buffer */
+       writel(priv->rdma.addr_phys + (priv->rdma.offset * RDMA_DESC_SIZE) - 1,
+              priv->io_base + _REG(RDMA_AHB_END_ADDR_1));
+
+       /* Trigger Channel 1 on VSYNC event */
+       writel_bits_relaxed(RDMA_ACCESS_TRIGGER_CHAN1,
+                           FIELD_PREP(RDMA_ACCESS_TRIGGER_CHAN1,
+                                      RDMA_ACCESS_TRIGGER_VSYNC),
+                           priv->io_base + _REG(RDMA_ACCESS_AUTO));
+
+       priv->rdma.offset = 0;
+}