1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2021-2022 Digiteq Automotive |
4 | * author: Martin Tuma <martin.tuma@digiteqautomotive.com> |
5 | * |
6 | * This module handles the DMA transfers. A standard dmaengine API as provided |
7 | * by the XDMA module is used. |
8 | */ |
9 | |
10 | #include <linux/pci.h> |
11 | #include <linux/dma-direction.h> |
12 | #include "mgb4_core.h" |
13 | #include "mgb4_dma.h" |
14 | |
15 | static void chan_irq(void *param) |
16 | { |
17 | struct mgb4_dma_channel *chan = param; |
18 | |
19 | complete(&chan->req_compl); |
20 | } |
21 | |
22 | int mgb4_dma_transfer(struct mgb4_dev *mgbdev, u32 channel, bool write, |
23 | u64 paddr, struct sg_table *sgt) |
24 | { |
25 | struct dma_slave_config cfg; |
26 | struct mgb4_dma_channel *chan; |
27 | struct dma_async_tx_descriptor *tx; |
28 | struct pci_dev *pdev = mgbdev->pdev; |
29 | int ret; |
30 | |
31 | memset(&cfg, 0, sizeof(cfg)); |
32 | |
33 | if (write) { |
34 | cfg.direction = DMA_MEM_TO_DEV; |
35 | cfg.dst_addr = paddr; |
36 | cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
37 | chan = &mgbdev->h2c_chan[channel]; |
38 | } else { |
39 | cfg.direction = DMA_DEV_TO_MEM; |
40 | cfg.src_addr = paddr; |
41 | cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
42 | chan = &mgbdev->c2h_chan[channel]; |
43 | } |
44 | |
45 | ret = dmaengine_slave_config(chan: chan->chan, config: &cfg); |
46 | if (ret) { |
47 | dev_err(&pdev->dev, "failed to config dma: %d\n" , ret); |
48 | return ret; |
49 | } |
50 | |
51 | tx = dmaengine_prep_slave_sg(chan: chan->chan, sgl: sgt->sgl, sg_len: sgt->nents, |
52 | dir: cfg.direction, flags: 0); |
53 | if (!tx) { |
54 | dev_err(&pdev->dev, "failed to prep slave sg\n" ); |
55 | return -EIO; |
56 | } |
57 | |
58 | tx->callback = chan_irq; |
59 | tx->callback_param = chan; |
60 | |
61 | ret = dma_submit_error(cookie: dmaengine_submit(desc: tx)); |
62 | if (ret) { |
63 | dev_err(&pdev->dev, "failed to submit sg\n" ); |
64 | return -EIO; |
65 | } |
66 | |
67 | dma_async_issue_pending(chan: chan->chan); |
68 | |
69 | if (!wait_for_completion_timeout(x: &chan->req_compl, |
70 | timeout: msecs_to_jiffies(m: 10000))) { |
71 | dev_err(&pdev->dev, "dma timeout\n" ); |
72 | dmaengine_terminate_sync(chan: chan->chan); |
73 | return -EIO; |
74 | } |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | int mgb4_dma_channel_init(struct mgb4_dev *mgbdev) |
80 | { |
81 | int i, ret; |
82 | char name[16]; |
83 | struct pci_dev *pdev = mgbdev->pdev; |
84 | |
85 | for (i = 0; i < MGB4_VIN_DEVICES; i++) { |
86 | sprintf(buf: name, fmt: "c2h%d" , i); |
87 | mgbdev->c2h_chan[i].chan = dma_request_chan(dev: &pdev->dev, name); |
88 | if (IS_ERR(ptr: mgbdev->c2h_chan[i].chan)) { |
89 | dev_err(&pdev->dev, "failed to initialize %s" , name); |
90 | ret = PTR_ERR(ptr: mgbdev->c2h_chan[i].chan); |
91 | mgbdev->c2h_chan[i].chan = NULL; |
92 | return ret; |
93 | } |
94 | init_completion(x: &mgbdev->c2h_chan[i].req_compl); |
95 | } |
96 | for (i = 0; i < MGB4_VOUT_DEVICES; i++) { |
97 | sprintf(buf: name, fmt: "h2c%d" , i); |
98 | mgbdev->h2c_chan[i].chan = dma_request_chan(dev: &pdev->dev, name); |
99 | if (IS_ERR(ptr: mgbdev->h2c_chan[i].chan)) { |
100 | dev_err(&pdev->dev, "failed to initialize %s" , name); |
101 | ret = PTR_ERR(ptr: mgbdev->h2c_chan[i].chan); |
102 | mgbdev->h2c_chan[i].chan = NULL; |
103 | return ret; |
104 | } |
105 | init_completion(x: &mgbdev->h2c_chan[i].req_compl); |
106 | } |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | void mgb4_dma_channel_free(struct mgb4_dev *mgbdev) |
112 | { |
113 | int i; |
114 | |
115 | for (i = 0; i < MGB4_VIN_DEVICES; i++) { |
116 | if (mgbdev->c2h_chan[i].chan) |
117 | dma_release_channel(chan: mgbdev->c2h_chan[i].chan); |
118 | } |
119 | for (i = 0; i < MGB4_VOUT_DEVICES; i++) { |
120 | if (mgbdev->h2c_chan[i].chan) |
121 | dma_release_channel(chan: mgbdev->h2c_chan[i].chan); |
122 | } |
123 | } |
124 | |