Skip to content

Commit fa5d823

Browse files
Sanjay R Mehtavinodkoul
authored andcommitted
dmaengine: ptdma: Initial driver for the AMD PTDMA
Add support for AMD PTDMA controller. It performs high-bandwidth memory to memory and IO copy operation. Device commands are managed via a circular queue of 'descriptors', each of which specifies source and destination addresses for copying a single buffer of data. Signed-off-by: Sanjay R Mehta <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Vinod Koul <[email protected]>
1 parent 64d57d2 commit fa5d823

File tree

8 files changed

+834
-0
lines changed

8 files changed

+834
-0
lines changed

MAINTAINERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,12 @@ S: Supported
979979
T: git https://gitlab.freedesktop.org/agd5f/linux.git
980980
F: drivers/gpu/drm/amd/pm/powerplay/
981981

982+
+AMD PTDMA DRIVER
983+
+M: Sanjay R Mehta <[email protected]>
984+
985+
+S: Maintained
986+
+F: drivers/dma/ptdma/
987+
982988
AMD SEATTLE DEVICE TREE SUPPORT
983989
M: Brijesh Singh <[email protected]>
984990
M: Suravee Suthikulpanit <[email protected]>

drivers/dma/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,8 @@ source "drivers/dma/bestcomm/Kconfig"
738738

739739
source "drivers/dma/mediatek/Kconfig"
740740

741+
source "drivers/dma/ptdma/Kconfig"
742+
741743
source "drivers/dma/qcom/Kconfig"
742744

743745
source "drivers/dma/dw/Kconfig"

drivers/dma/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ obj-$(CONFIG_DMATEST) += dmatest.o
1616
obj-$(CONFIG_ALTERA_MSGDMA) += altera-msgdma.o
1717
obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
1818
obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/
19+
obj-$(CONFIG_AMD_PTDMA) += ptdma/
1920
obj-$(CONFIG_AT_HDMAC) += at_hdmac.o
2021
obj-$(CONFIG_AT_XDMAC) += at_xdmac.o
2122
obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o

drivers/dma/ptdma/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
config AMD_PTDMA
3+
tristate "AMD PassThru DMA Engine"
4+
depends on X86_64 && PCI
5+
help
6+
Enable support for the AMD PTDMA controller. This controller
7+
provides DMA capabilities to perform high bandwidth memory to
8+
memory and IO copy operations. It performs DMA transfer through
9+
queue-based descriptor management. This DMA controller is intended
10+
to be used with AMD Non-Transparent Bridge devices and not for
11+
general purpose peripheral DMA.

drivers/dma/ptdma/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
#
3+
# AMD Passthru DMA driver
4+
#
5+
6+
obj-$(CONFIG_AMD_PTDMA) += ptdma.o
7+
8+
ptdma-objs := ptdma-dev.o
9+
10+
ptdma-$(CONFIG_PCI) += ptdma-pci.o

drivers/dma/ptdma/ptdma-dev.c

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* AMD Passthru DMA device driver
4+
* -- Based on the CCP driver
5+
*
6+
* Copyright (C) 2016,2021 Advanced Micro Devices, Inc.
7+
*
8+
* Author: Sanjay R Mehta <[email protected]>
9+
* Author: Gary R Hook <[email protected]>
10+
*/
11+
12+
#include <linux/bitfield.h>
13+
#include <linux/dma-mapping.h>
14+
#include <linux/debugfs.h>
15+
#include <linux/interrupt.h>
16+
#include <linux/kernel.h>
17+
#include <linux/module.h>
18+
#include <linux/pci.h>
19+
20+
#include "ptdma.h"
21+
22+
/* Human-readable error strings */
23+
static char *pt_error_codes[] = {
24+
"",
25+
"ERR 01: ILLEGAL_ENGINE",
26+
"ERR 03: ILLEGAL_FUNCTION_TYPE",
27+
"ERR 04: ILLEGAL_FUNCTION_MODE",
28+
"ERR 06: ILLEGAL_FUNCTION_SIZE",
29+
"ERR 08: ILLEGAL_FUNCTION_RSVD",
30+
"ERR 09: ILLEGAL_BUFFER_LENGTH",
31+
"ERR 10: VLSB_FAULT",
32+
"ERR 11: ILLEGAL_MEM_ADDR",
33+
"ERR 12: ILLEGAL_MEM_SEL",
34+
"ERR 13: ILLEGAL_CONTEXT_ID",
35+
"ERR 15: 0xF Reserved",
36+
"ERR 18: CMD_TIMEOUT",
37+
"ERR 19: IDMA0_AXI_SLVERR",
38+
"ERR 20: IDMA0_AXI_DECERR",
39+
"ERR 21: 0x15 Reserved",
40+
"ERR 22: IDMA1_AXI_SLAVE_FAULT",
41+
"ERR 23: IDMA1_AIXI_DECERR",
42+
"ERR 24: 0x18 Reserved",
43+
"ERR 27: 0x1B Reserved",
44+
"ERR 38: ODMA0_AXI_SLVERR",
45+
"ERR 39: ODMA0_AXI_DECERR",
46+
"ERR 40: 0x28 Reserved",
47+
"ERR 41: ODMA1_AXI_SLVERR",
48+
"ERR 42: ODMA1_AXI_DECERR",
49+
"ERR 43: LSB_PARITY_ERR",
50+
};
51+
52+
static void pt_log_error(struct pt_device *d, int e)
53+
{
54+
dev_err(d->dev, "PTDMA error: %s (0x%x)\n", pt_error_codes[e], e);
55+
}
56+
57+
void pt_start_queue(struct pt_cmd_queue *cmd_q)
58+
{
59+
/* Turn on the run bit */
60+
iowrite32(cmd_q->qcontrol | CMD_Q_RUN, cmd_q->reg_control);
61+
}
62+
63+
void pt_stop_queue(struct pt_cmd_queue *cmd_q)
64+
{
65+
/* Turn off the run bit */
66+
iowrite32(cmd_q->qcontrol & ~CMD_Q_RUN, cmd_q->reg_control);
67+
}
68+
69+
static int pt_core_execute_cmd(struct ptdma_desc *desc, struct pt_cmd_queue *cmd_q)
70+
{
71+
bool soc = FIELD_GET(DWORD0_SOC, desc->dw0);
72+
u8 *q_desc = (u8 *)&cmd_q->qbase[cmd_q->qidx];
73+
u32 tail;
74+
75+
if (soc) {
76+
desc->dw0 |= FIELD_PREP(DWORD0_IOC, desc->dw0);
77+
desc->dw0 &= ~DWORD0_SOC;
78+
}
79+
mutex_lock(&cmd_q->q_mutex);
80+
81+
/* Copy 32-byte command descriptor to hw queue. */
82+
memcpy(q_desc, desc, 32);
83+
cmd_q->qidx = (cmd_q->qidx + 1) % CMD_Q_LEN;
84+
85+
/* The data used by this command must be flushed to memory */
86+
wmb();
87+
88+
/* Write the new tail address back to the queue register */
89+
tail = lower_32_bits(cmd_q->qdma_tail + cmd_q->qidx * Q_DESC_SIZE);
90+
iowrite32(tail, cmd_q->reg_control + 0x0004);
91+
92+
/* Turn the queue back on using our cached control register */
93+
pt_start_queue(cmd_q);
94+
mutex_unlock(&cmd_q->q_mutex);
95+
96+
return 0;
97+
}
98+
99+
int pt_core_perform_passthru(struct pt_cmd_queue *cmd_q,
100+
struct pt_passthru_engine *pt_engine)
101+
{
102+
struct ptdma_desc desc;
103+
104+
cmd_q->cmd_error = 0;
105+
memset(&desc, 0, sizeof(desc));
106+
desc.dw0 = CMD_DESC_DW0_VAL;
107+
desc.length = pt_engine->src_len;
108+
desc.src_lo = lower_32_bits(pt_engine->src_dma);
109+
desc.dw3.src_hi = upper_32_bits(pt_engine->src_dma);
110+
desc.dst_lo = lower_32_bits(pt_engine->dst_dma);
111+
desc.dw5.dst_hi = upper_32_bits(pt_engine->dst_dma);
112+
113+
return pt_core_execute_cmd(&desc, cmd_q);
114+
}
115+
116+
static inline void pt_core_disable_queue_interrupts(struct pt_device *pt)
117+
{
118+
iowrite32(0, pt->cmd_q.reg_control + 0x000C);
119+
}
120+
121+
static inline void pt_core_enable_queue_interrupts(struct pt_device *pt)
122+
{
123+
iowrite32(SUPPORTED_INTERRUPTS, pt->cmd_q.reg_control + 0x000C);
124+
}
125+
126+
static irqreturn_t pt_core_irq_handler(int irq, void *data)
127+
{
128+
struct pt_device *pt = data;
129+
struct pt_cmd_queue *cmd_q = &pt->cmd_q;
130+
u32 status;
131+
132+
pt_core_disable_queue_interrupts(pt);
133+
status = ioread32(cmd_q->reg_control + 0x0010);
134+
if (status) {
135+
cmd_q->int_status = status;
136+
cmd_q->q_status = ioread32(cmd_q->reg_control + 0x0100);
137+
cmd_q->q_int_status = ioread32(cmd_q->reg_control + 0x0104);
138+
139+
/* On error, only save the first error value */
140+
if ((status & INT_ERROR) && !cmd_q->cmd_error)
141+
cmd_q->cmd_error = CMD_Q_ERROR(cmd_q->q_status);
142+
143+
/* Acknowledge the interrupt */
144+
iowrite32(status, cmd_q->reg_control + 0x0010);
145+
pt_core_enable_queue_interrupts(pt);
146+
}
147+
return IRQ_HANDLED;
148+
}
149+
150+
int pt_core_init(struct pt_device *pt)
151+
{
152+
char dma_pool_name[MAX_DMAPOOL_NAME_LEN];
153+
struct pt_cmd_queue *cmd_q = &pt->cmd_q;
154+
u32 dma_addr_lo, dma_addr_hi;
155+
struct device *dev = pt->dev;
156+
struct dma_pool *dma_pool;
157+
int ret;
158+
159+
/* Allocate a dma pool for the queue */
160+
snprintf(dma_pool_name, sizeof(dma_pool_name), "%s_q", dev_name(pt->dev));
161+
162+
dma_pool = dma_pool_create(dma_pool_name, dev,
163+
PT_DMAPOOL_MAX_SIZE,
164+
PT_DMAPOOL_ALIGN, 0);
165+
if (!dma_pool)
166+
return -ENOMEM;
167+
168+
/* ptdma core initialisation */
169+
iowrite32(CMD_CONFIG_VHB_EN, pt->io_regs + CMD_CONFIG_OFFSET);
170+
iowrite32(CMD_QUEUE_PRIO, pt->io_regs + CMD_QUEUE_PRIO_OFFSET);
171+
iowrite32(CMD_TIMEOUT_DISABLE, pt->io_regs + CMD_TIMEOUT_OFFSET);
172+
iowrite32(CMD_CLK_GATE_CONFIG, pt->io_regs + CMD_CLK_GATE_CTL_OFFSET);
173+
iowrite32(CMD_CONFIG_REQID, pt->io_regs + CMD_REQID_CONFIG_OFFSET);
174+
175+
cmd_q->pt = pt;
176+
cmd_q->dma_pool = dma_pool;
177+
mutex_init(&cmd_q->q_mutex);
178+
179+
/* Page alignment satisfies our needs for N <= 128 */
180+
cmd_q->qsize = Q_SIZE(Q_DESC_SIZE);
181+
cmd_q->qbase = dma_alloc_coherent(dev, cmd_q->qsize,
182+
&cmd_q->qbase_dma,
183+
GFP_KERNEL);
184+
if (!cmd_q->qbase) {
185+
dev_err(dev, "unable to allocate command queue\n");
186+
ret = -ENOMEM;
187+
goto e_dma_alloc;
188+
}
189+
190+
cmd_q->qidx = 0;
191+
192+
/* Preset some register values */
193+
cmd_q->reg_control = pt->io_regs + CMD_Q_STATUS_INCR;
194+
195+
/* Turn off the queues and disable interrupts until ready */
196+
pt_core_disable_queue_interrupts(pt);
197+
198+
cmd_q->qcontrol = 0; /* Start with nothing */
199+
iowrite32(cmd_q->qcontrol, cmd_q->reg_control);
200+
201+
ioread32(cmd_q->reg_control + 0x0104);
202+
ioread32(cmd_q->reg_control + 0x0100);
203+
204+
/* Clear the interrupt status */
205+
iowrite32(SUPPORTED_INTERRUPTS, cmd_q->reg_control + 0x0010);
206+
207+
/* Request an irq */
208+
ret = request_irq(pt->pt_irq, pt_core_irq_handler, 0, dev_name(pt->dev), pt);
209+
if (ret)
210+
goto e_pool;
211+
212+
/* Update the device registers with queue information. */
213+
cmd_q->qcontrol &= ~CMD_Q_SIZE;
214+
cmd_q->qcontrol |= FIELD_PREP(CMD_Q_SIZE, QUEUE_SIZE_VAL);
215+
216+
cmd_q->qdma_tail = cmd_q->qbase_dma;
217+
dma_addr_lo = lower_32_bits(cmd_q->qdma_tail);
218+
iowrite32((u32)dma_addr_lo, cmd_q->reg_control + 0x0004);
219+
iowrite32((u32)dma_addr_lo, cmd_q->reg_control + 0x0008);
220+
221+
dma_addr_hi = upper_32_bits(cmd_q->qdma_tail);
222+
cmd_q->qcontrol |= (dma_addr_hi << 16);
223+
iowrite32(cmd_q->qcontrol, cmd_q->reg_control);
224+
225+
pt_core_enable_queue_interrupts(pt);
226+
227+
return 0;
228+
229+
e_dma_alloc:
230+
dma_free_coherent(dev, cmd_q->qsize, cmd_q->qbase, cmd_q->qbase_dma);
231+
232+
e_pool:
233+
dev_err(dev, "unable to allocate an IRQ\n");
234+
dma_pool_destroy(pt->cmd_q.dma_pool);
235+
236+
return ret;
237+
}
238+
239+
void pt_core_destroy(struct pt_device *pt)
240+
{
241+
struct device *dev = pt->dev;
242+
struct pt_cmd_queue *cmd_q = &pt->cmd_q;
243+
struct pt_cmd *cmd;
244+
245+
/* Disable and clear interrupts */
246+
pt_core_disable_queue_interrupts(pt);
247+
248+
/* Turn off the run bit */
249+
pt_stop_queue(cmd_q);
250+
251+
/* Clear the interrupt status */
252+
iowrite32(SUPPORTED_INTERRUPTS, cmd_q->reg_control + 0x0010);
253+
ioread32(cmd_q->reg_control + 0x0104);
254+
ioread32(cmd_q->reg_control + 0x0100);
255+
256+
free_irq(pt->pt_irq, pt);
257+
258+
dma_free_coherent(dev, cmd_q->qsize, cmd_q->qbase,
259+
cmd_q->qbase_dma);
260+
261+
/* Flush the cmd queue */
262+
while (!list_empty(&pt->cmd)) {
263+
/* Invoke the callback directly with an error code */
264+
cmd = list_first_entry(&pt->cmd, struct pt_cmd, entry);
265+
list_del(&cmd->entry);
266+
cmd->pt_cmd_callback(cmd->data, -ENODEV);
267+
}
268+
}

0 commit comments

Comments
 (0)