| // 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_dma, | |
| 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_dma) | |
| return; | |
| meson_rdma_stop(priv); | |
| dma_free_coherent(priv->dev, SZ_4K, | |
| priv->rdma.addr, priv->rdma.addr_dma); | |
| priv->rdma.addr = NULL; | |
| priv->rdma.addr_dma = (dma_addr_t)0; | |
| } | |
| 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_dma, | |
| priv->io_base + _REG(RDMA_AHB_START_ADDR_1)); | |
| /* Last byte on Channel 1 register writes buffer */ | |
| writel(priv->rdma.addr_dma + (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; | |
| } |