1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Copyright (C) 2019 BayLibre, SAS |
4 | * Author: Neil Armstrong <narmstrong@baylibre.com> |
5 | */ |
6 | |
7 | #include <linux/bitfield.h> |
8 | #include <linux/dma-mapping.h> |
9 | |
10 | #include "meson_drv.h" |
11 | #include "meson_registers.h" |
12 | #include "meson_rdma.h" |
13 | |
14 | /* |
15 | * The VPU embeds a "Register DMA" that can write a sequence of registers |
16 | * on the VPU AHB bus, either manually or triggered by an internal IRQ |
17 | * event like VSYNC or a line input counter. |
18 | * The initial implementation handles a single channel (over 8), triggered |
19 | * by the VSYNC irq and does not handle the RDMA irq. |
20 | */ |
21 | |
22 | #define RDMA_DESC_SIZE (sizeof(uint32_t) * 2) |
23 | |
24 | int meson_rdma_init(struct meson_drm *priv) |
25 | { |
26 | if (!priv->rdma.addr) { |
27 | /* Allocate a PAGE buffer */ |
28 | priv->rdma.addr = |
29 | dma_alloc_coherent(dev: priv->dev, SZ_4K, |
30 | dma_handle: &priv->rdma.addr_dma, |
31 | GFP_KERNEL); |
32 | if (!priv->rdma.addr) |
33 | return -ENOMEM; |
34 | } |
35 | |
36 | priv->rdma.offset = 0; |
37 | |
38 | writel_relaxed(RDMA_CTRL_SW_RESET, |
39 | priv->io_base + _REG(RDMA_CTRL)); |
40 | writel_relaxed(RDMA_DEFAULT_CONFIG | |
41 | FIELD_PREP(RDMA_CTRL_AHB_WR_BURST, 3) | |
42 | FIELD_PREP(RDMA_CTRL_AHB_RD_BURST, 0), |
43 | priv->io_base + _REG(RDMA_CTRL)); |
44 | |
45 | return 0; |
46 | } |
47 | |
48 | void meson_rdma_free(struct meson_drm *priv) |
49 | { |
50 | if (!priv->rdma.addr && !priv->rdma.addr_dma) |
51 | return; |
52 | |
53 | meson_rdma_stop(priv); |
54 | |
55 | dma_free_coherent(dev: priv->dev, SZ_4K, |
56 | cpu_addr: priv->rdma.addr, dma_handle: priv->rdma.addr_dma); |
57 | |
58 | priv->rdma.addr = NULL; |
59 | priv->rdma.addr_dma = (dma_addr_t)0; |
60 | } |
61 | |
62 | void meson_rdma_setup(struct meson_drm *priv) |
63 | { |
64 | /* Channel 1: Write Flag, No Address Increment */ |
65 | writel_bits_relaxed(RDMA_ACCESS_RW_FLAG_CHAN1 | |
66 | RDMA_ACCESS_ADDR_INC_CHAN1, |
67 | RDMA_ACCESS_RW_FLAG_CHAN1, |
68 | priv->io_base + _REG(RDMA_ACCESS_AUTO)); |
69 | } |
70 | |
71 | void meson_rdma_stop(struct meson_drm *priv) |
72 | { |
73 | writel_bits_relaxed(RDMA_IRQ_CLEAR_CHAN1, |
74 | RDMA_IRQ_CLEAR_CHAN1, |
75 | priv->io_base + _REG(RDMA_CTRL)); |
76 | |
77 | /* Stop Channel 1 */ |
78 | writel_bits_relaxed(RDMA_ACCESS_TRIGGER_CHAN1, |
79 | FIELD_PREP(RDMA_ACCESS_ADDR_INC_CHAN1, |
80 | RDMA_ACCESS_TRIGGER_STOP), |
81 | priv->io_base + _REG(RDMA_ACCESS_AUTO)); |
82 | } |
83 | |
84 | void meson_rdma_reset(struct meson_drm *priv) |
85 | { |
86 | meson_rdma_stop(priv); |
87 | |
88 | priv->rdma.offset = 0; |
89 | } |
90 | |
91 | static void meson_rdma_writel(struct meson_drm *priv, uint32_t val, |
92 | uint32_t reg) |
93 | { |
94 | if (priv->rdma.offset >= (SZ_4K / RDMA_DESC_SIZE)) { |
95 | dev_warn_once(priv->dev, "%s: overflow\n" , __func__); |
96 | return; |
97 | } |
98 | |
99 | priv->rdma.addr[priv->rdma.offset++] = reg; |
100 | priv->rdma.addr[priv->rdma.offset++] = val; |
101 | } |
102 | |
103 | /* |
104 | * This will add the register to the RDMA buffer and write it to the |
105 | * hardware at the same time. |
106 | * When meson_rdma_flush is called, the RDMA will replay the register |
107 | * writes in order. |
108 | */ |
109 | void meson_rdma_writel_sync(struct meson_drm *priv, uint32_t val, uint32_t reg) |
110 | { |
111 | meson_rdma_writel(priv, val, reg); |
112 | |
113 | writel_relaxed(val, priv->io_base + _REG(reg)); |
114 | } |
115 | |
116 | void meson_rdma_flush(struct meson_drm *priv) |
117 | { |
118 | meson_rdma_stop(priv); |
119 | |
120 | /* Start of Channel 1 register writes buffer */ |
121 | writel(val: priv->rdma.addr_dma, |
122 | addr: priv->io_base + _REG(RDMA_AHB_START_ADDR_1)); |
123 | |
124 | /* Last byte on Channel 1 register writes buffer */ |
125 | writel(val: priv->rdma.addr_dma + (priv->rdma.offset * RDMA_DESC_SIZE) - 1, |
126 | addr: priv->io_base + _REG(RDMA_AHB_END_ADDR_1)); |
127 | |
128 | /* Trigger Channel 1 on VSYNC event */ |
129 | writel_bits_relaxed(RDMA_ACCESS_TRIGGER_CHAN1, |
130 | FIELD_PREP(RDMA_ACCESS_TRIGGER_CHAN1, |
131 | RDMA_ACCESS_TRIGGER_VSYNC), |
132 | priv->io_base + _REG(RDMA_ACCESS_AUTO)); |
133 | |
134 | priv->rdma.offset = 0; |
135 | } |
136 | |