162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2019 BayLibre, SAS
462306a36Sopenharmony_ci * Author: Neil Armstrong <narmstrong@baylibre.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/bitfield.h>
862306a36Sopenharmony_ci#include <linux/dma-mapping.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "meson_drv.h"
1162306a36Sopenharmony_ci#include "meson_registers.h"
1262306a36Sopenharmony_ci#include "meson_rdma.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci/*
1562306a36Sopenharmony_ci * The VPU embeds a "Register DMA" that can write a sequence of registers
1662306a36Sopenharmony_ci * on the VPU AHB bus, either manually or triggered by an internal IRQ
1762306a36Sopenharmony_ci * event like VSYNC or a line input counter.
1862306a36Sopenharmony_ci * The initial implementation handles a single channel (over 8), triggered
1962306a36Sopenharmony_ci * by the VSYNC irq and does not handle the RDMA irq.
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define RDMA_DESC_SIZE	(sizeof(uint32_t) * 2)
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ciint meson_rdma_init(struct meson_drm *priv)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	if (!priv->rdma.addr) {
2762306a36Sopenharmony_ci		/* Allocate a PAGE buffer */
2862306a36Sopenharmony_ci		priv->rdma.addr =
2962306a36Sopenharmony_ci			dma_alloc_coherent(priv->dev, SZ_4K,
3062306a36Sopenharmony_ci					   &priv->rdma.addr_dma,
3162306a36Sopenharmony_ci					   GFP_KERNEL);
3262306a36Sopenharmony_ci		if (!priv->rdma.addr)
3362306a36Sopenharmony_ci			return -ENOMEM;
3462306a36Sopenharmony_ci	}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	priv->rdma.offset = 0;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	writel_relaxed(RDMA_CTRL_SW_RESET,
3962306a36Sopenharmony_ci		       priv->io_base + _REG(RDMA_CTRL));
4062306a36Sopenharmony_ci	writel_relaxed(RDMA_DEFAULT_CONFIG |
4162306a36Sopenharmony_ci		       FIELD_PREP(RDMA_CTRL_AHB_WR_BURST, 3) |
4262306a36Sopenharmony_ci		       FIELD_PREP(RDMA_CTRL_AHB_RD_BURST, 0),
4362306a36Sopenharmony_ci		       priv->io_base + _REG(RDMA_CTRL));
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	return 0;
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_civoid meson_rdma_free(struct meson_drm *priv)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	if (!priv->rdma.addr && !priv->rdma.addr_dma)
5162306a36Sopenharmony_ci		return;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	meson_rdma_stop(priv);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	dma_free_coherent(priv->dev, SZ_4K,
5662306a36Sopenharmony_ci			  priv->rdma.addr, priv->rdma.addr_dma);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	priv->rdma.addr = NULL;
5962306a36Sopenharmony_ci	priv->rdma.addr_dma = (dma_addr_t)0;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_civoid meson_rdma_setup(struct meson_drm *priv)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	/* Channel 1: Write Flag, No Address Increment */
6562306a36Sopenharmony_ci	writel_bits_relaxed(RDMA_ACCESS_RW_FLAG_CHAN1 |
6662306a36Sopenharmony_ci			    RDMA_ACCESS_ADDR_INC_CHAN1,
6762306a36Sopenharmony_ci			    RDMA_ACCESS_RW_FLAG_CHAN1,
6862306a36Sopenharmony_ci			    priv->io_base + _REG(RDMA_ACCESS_AUTO));
6962306a36Sopenharmony_ci}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_civoid meson_rdma_stop(struct meson_drm *priv)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	writel_bits_relaxed(RDMA_IRQ_CLEAR_CHAN1,
7462306a36Sopenharmony_ci			    RDMA_IRQ_CLEAR_CHAN1,
7562306a36Sopenharmony_ci			    priv->io_base + _REG(RDMA_CTRL));
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	/* Stop Channel 1 */
7862306a36Sopenharmony_ci	writel_bits_relaxed(RDMA_ACCESS_TRIGGER_CHAN1,
7962306a36Sopenharmony_ci			    FIELD_PREP(RDMA_ACCESS_ADDR_INC_CHAN1,
8062306a36Sopenharmony_ci				       RDMA_ACCESS_TRIGGER_STOP),
8162306a36Sopenharmony_ci			    priv->io_base + _REG(RDMA_ACCESS_AUTO));
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_civoid meson_rdma_reset(struct meson_drm *priv)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	meson_rdma_stop(priv);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	priv->rdma.offset = 0;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic void meson_rdma_writel(struct meson_drm *priv, uint32_t val,
9262306a36Sopenharmony_ci			      uint32_t reg)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	if (priv->rdma.offset >= (SZ_4K / RDMA_DESC_SIZE)) {
9562306a36Sopenharmony_ci		dev_warn_once(priv->dev, "%s: overflow\n", __func__);
9662306a36Sopenharmony_ci		return;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	priv->rdma.addr[priv->rdma.offset++] = reg;
10062306a36Sopenharmony_ci	priv->rdma.addr[priv->rdma.offset++] = val;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci/*
10462306a36Sopenharmony_ci * This will add the register to the RDMA buffer and write it to the
10562306a36Sopenharmony_ci * hardware at the same time.
10662306a36Sopenharmony_ci * When meson_rdma_flush is called, the RDMA will replay the register
10762306a36Sopenharmony_ci * writes in order.
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_civoid meson_rdma_writel_sync(struct meson_drm *priv, uint32_t val, uint32_t reg)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	meson_rdma_writel(priv, val, reg);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	writel_relaxed(val, priv->io_base + _REG(reg));
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_civoid meson_rdma_flush(struct meson_drm *priv)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	meson_rdma_stop(priv);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* Start of Channel 1 register writes buffer */
12162306a36Sopenharmony_ci	writel(priv->rdma.addr_dma,
12262306a36Sopenharmony_ci	       priv->io_base + _REG(RDMA_AHB_START_ADDR_1));
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Last byte on Channel 1 register writes buffer */
12562306a36Sopenharmony_ci	writel(priv->rdma.addr_dma + (priv->rdma.offset * RDMA_DESC_SIZE) - 1,
12662306a36Sopenharmony_ci	       priv->io_base + _REG(RDMA_AHB_END_ADDR_1));
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* Trigger Channel 1 on VSYNC event */
12962306a36Sopenharmony_ci	writel_bits_relaxed(RDMA_ACCESS_TRIGGER_CHAN1,
13062306a36Sopenharmony_ci			    FIELD_PREP(RDMA_ACCESS_TRIGGER_CHAN1,
13162306a36Sopenharmony_ci				       RDMA_ACCESS_TRIGGER_VSYNC),
13262306a36Sopenharmony_ci			    priv->io_base + _REG(RDMA_ACCESS_AUTO));
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	priv->rdma.offset = 0;
13562306a36Sopenharmony_ci}
136