Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1da177e4 LT |
2 | /* |
3 | * linux/arch/arm/kernel/dma.c | |
4 | * | |
5 | * Copyright (C) 1995-2000 Russell King | |
6 | * | |
1da177e4 LT |
7 | * Front-end to the DMA handling. This handles the allocation/freeing |
8 | * of DMA channels, and provides a unified interface to the machines | |
9 | * DMA facilities. | |
10 | */ | |
11 | #include <linux/module.h> | |
1da177e4 LT |
12 | #include <linux/init.h> |
13 | #include <linux/spinlock.h> | |
14 | #include <linux/errno.h> | |
d667522f | 15 | #include <linux/scatterlist.h> |
e193ba29 RK |
16 | #include <linux/seq_file.h> |
17 | #include <linux/proc_fs.h> | |
1da177e4 LT |
18 | |
19 | #include <asm/dma.h> | |
20 | ||
21 | #include <asm/mach/dma.h> | |
22 | ||
bd31b859 | 23 | DEFINE_RAW_SPINLOCK(dma_spin_lock); |
d7b4a756 | 24 | EXPORT_SYMBOL(dma_spin_lock); |
1da177e4 | 25 | |
2f757f2a | 26 | static dma_t *dma_chan[MAX_DMA_CHANNELS]; |
1da177e4 | 27 | |
3afb6e9c RK |
28 | static inline dma_t *dma_channel(unsigned int chan) |
29 | { | |
2f757f2a | 30 | if (chan >= MAX_DMA_CHANNELS) |
3afb6e9c RK |
31 | return NULL; |
32 | ||
2f757f2a RK |
33 | return dma_chan[chan]; |
34 | } | |
35 | ||
36 | int __init isa_dma_add(unsigned int chan, dma_t *dma) | |
37 | { | |
38 | if (!dma->d_ops) | |
39 | return -EINVAL; | |
d667522f RK |
40 | |
41 | sg_init_table(&dma->buf, 1); | |
42 | ||
2f757f2a RK |
43 | if (dma_chan[chan]) |
44 | return -EBUSY; | |
45 | dma_chan[chan] = dma; | |
46 | return 0; | |
3afb6e9c RK |
47 | } |
48 | ||
1da177e4 LT |
49 | /* |
50 | * Request DMA channel | |
51 | * | |
52 | * On certain platforms, we have to allocate an interrupt as well... | |
53 | */ | |
1df81302 | 54 | int request_dma(unsigned int chan, const char *device_id) |
1da177e4 | 55 | { |
3afb6e9c | 56 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
57 | int ret; |
58 | ||
3afb6e9c | 59 | if (!dma) |
1da177e4 LT |
60 | goto bad_dma; |
61 | ||
62 | if (xchg(&dma->lock, 1) != 0) | |
63 | goto busy; | |
64 | ||
65 | dma->device_id = device_id; | |
66 | dma->active = 0; | |
67 | dma->invalid = 1; | |
68 | ||
69 | ret = 0; | |
70 | if (dma->d_ops->request) | |
1df81302 | 71 | ret = dma->d_ops->request(chan, dma); |
1da177e4 LT |
72 | |
73 | if (ret) | |
74 | xchg(&dma->lock, 0); | |
75 | ||
76 | return ret; | |
77 | ||
78 | bad_dma: | |
4ed89f22 | 79 | pr_err("dma: trying to allocate DMA%d\n", chan); |
1da177e4 LT |
80 | return -EINVAL; |
81 | ||
82 | busy: | |
83 | return -EBUSY; | |
84 | } | |
d7b4a756 | 85 | EXPORT_SYMBOL(request_dma); |
1da177e4 LT |
86 | |
87 | /* | |
88 | * Free DMA channel | |
89 | * | |
90 | * On certain platforms, we have to free interrupt as well... | |
91 | */ | |
1df81302 | 92 | void free_dma(unsigned int chan) |
1da177e4 | 93 | { |
3afb6e9c | 94 | dma_t *dma = dma_channel(chan); |
1da177e4 | 95 | |
3afb6e9c | 96 | if (!dma) |
1da177e4 LT |
97 | goto bad_dma; |
98 | ||
99 | if (dma->active) { | |
4ed89f22 | 100 | pr_err("dma%d: freeing active DMA\n", chan); |
1df81302 | 101 | dma->d_ops->disable(chan, dma); |
1da177e4 LT |
102 | dma->active = 0; |
103 | } | |
104 | ||
105 | if (xchg(&dma->lock, 0) != 0) { | |
106 | if (dma->d_ops->free) | |
1df81302 | 107 | dma->d_ops->free(chan, dma); |
1da177e4 LT |
108 | return; |
109 | } | |
110 | ||
4ed89f22 | 111 | pr_err("dma%d: trying to free free DMA\n", chan); |
1da177e4 LT |
112 | return; |
113 | ||
114 | bad_dma: | |
4ed89f22 | 115 | pr_err("dma: trying to free DMA%d\n", chan); |
1da177e4 | 116 | } |
d7b4a756 | 117 | EXPORT_SYMBOL(free_dma); |
1da177e4 LT |
118 | |
119 | /* Set DMA Scatter-Gather list | |
120 | */ | |
1df81302 | 121 | void set_dma_sg (unsigned int chan, struct scatterlist *sg, int nr_sg) |
1da177e4 | 122 | { |
3afb6e9c | 123 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
124 | |
125 | if (dma->active) | |
4ed89f22 | 126 | pr_err("dma%d: altering DMA SG while DMA active\n", chan); |
1da177e4 LT |
127 | |
128 | dma->sg = sg; | |
129 | dma->sgcount = nr_sg; | |
1da177e4 LT |
130 | dma->invalid = 1; |
131 | } | |
d7b4a756 | 132 | EXPORT_SYMBOL(set_dma_sg); |
1da177e4 LT |
133 | |
134 | /* Set DMA address | |
135 | * | |
136 | * Copy address to the structure, and set the invalid bit | |
137 | */ | |
1df81302 | 138 | void __set_dma_addr (unsigned int chan, void *addr) |
1da177e4 | 139 | { |
3afb6e9c | 140 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
141 | |
142 | if (dma->active) | |
4ed89f22 | 143 | pr_err("dma%d: altering DMA address while DMA active\n", chan); |
1da177e4 | 144 | |
7cdad482 RK |
145 | dma->sg = NULL; |
146 | dma->addr = addr; | |
1da177e4 LT |
147 | dma->invalid = 1; |
148 | } | |
d7b4a756 | 149 | EXPORT_SYMBOL(__set_dma_addr); |
1da177e4 LT |
150 | |
151 | /* Set DMA byte count | |
152 | * | |
153 | * Copy address to the structure, and set the invalid bit | |
154 | */ | |
1df81302 | 155 | void set_dma_count (unsigned int chan, unsigned long count) |
1da177e4 | 156 | { |
3afb6e9c | 157 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
158 | |
159 | if (dma->active) | |
4ed89f22 | 160 | pr_err("dma%d: altering DMA count while DMA active\n", chan); |
1da177e4 | 161 | |
7cdad482 RK |
162 | dma->sg = NULL; |
163 | dma->count = count; | |
1da177e4 LT |
164 | dma->invalid = 1; |
165 | } | |
d7b4a756 | 166 | EXPORT_SYMBOL(set_dma_count); |
1da177e4 LT |
167 | |
168 | /* Set DMA direction mode | |
169 | */ | |
f0ffc816 | 170 | void set_dma_mode (unsigned int chan, unsigned int mode) |
1da177e4 | 171 | { |
3afb6e9c | 172 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
173 | |
174 | if (dma->active) | |
4ed89f22 | 175 | pr_err("dma%d: altering DMA mode while DMA active\n", chan); |
1da177e4 LT |
176 | |
177 | dma->dma_mode = mode; | |
178 | dma->invalid = 1; | |
179 | } | |
d7b4a756 | 180 | EXPORT_SYMBOL(set_dma_mode); |
1da177e4 LT |
181 | |
182 | /* Enable DMA channel | |
183 | */ | |
1df81302 | 184 | void enable_dma (unsigned int chan) |
1da177e4 | 185 | { |
3afb6e9c | 186 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
187 | |
188 | if (!dma->lock) | |
189 | goto free_dma; | |
190 | ||
191 | if (dma->active == 0) { | |
192 | dma->active = 1; | |
1df81302 | 193 | dma->d_ops->enable(chan, dma); |
1da177e4 LT |
194 | } |
195 | return; | |
196 | ||
197 | free_dma: | |
4ed89f22 | 198 | pr_err("dma%d: trying to enable free DMA\n", chan); |
1da177e4 LT |
199 | BUG(); |
200 | } | |
d7b4a756 | 201 | EXPORT_SYMBOL(enable_dma); |
1da177e4 LT |
202 | |
203 | /* Disable DMA channel | |
204 | */ | |
1df81302 | 205 | void disable_dma (unsigned int chan) |
1da177e4 | 206 | { |
3afb6e9c | 207 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
208 | |
209 | if (!dma->lock) | |
210 | goto free_dma; | |
211 | ||
212 | if (dma->active == 1) { | |
213 | dma->active = 0; | |
1df81302 | 214 | dma->d_ops->disable(chan, dma); |
1da177e4 LT |
215 | } |
216 | return; | |
217 | ||
218 | free_dma: | |
4ed89f22 | 219 | pr_err("dma%d: trying to disable free DMA\n", chan); |
1da177e4 LT |
220 | BUG(); |
221 | } | |
d7b4a756 | 222 | EXPORT_SYMBOL(disable_dma); |
1da177e4 LT |
223 | |
224 | /* | |
225 | * Is the specified DMA channel active? | |
226 | */ | |
1df81302 | 227 | int dma_channel_active(unsigned int chan) |
1da177e4 | 228 | { |
3afb6e9c RK |
229 | dma_t *dma = dma_channel(chan); |
230 | return dma->active; | |
1da177e4 | 231 | } |
ec14d796 | 232 | EXPORT_SYMBOL(dma_channel_active); |
1da177e4 | 233 | |
1df81302 | 234 | void set_dma_page(unsigned int chan, char pagenr) |
1da177e4 | 235 | { |
4ed89f22 | 236 | pr_err("dma%d: trying to set_dma_page\n", chan); |
1da177e4 | 237 | } |
d7b4a756 | 238 | EXPORT_SYMBOL(set_dma_page); |
1da177e4 | 239 | |
1df81302 | 240 | void set_dma_speed(unsigned int chan, int cycle_ns) |
1da177e4 | 241 | { |
3afb6e9c | 242 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
243 | int ret = 0; |
244 | ||
245 | if (dma->d_ops->setspeed) | |
1df81302 | 246 | ret = dma->d_ops->setspeed(chan, dma, cycle_ns); |
1da177e4 LT |
247 | dma->speed = ret; |
248 | } | |
d7b4a756 | 249 | EXPORT_SYMBOL(set_dma_speed); |
1da177e4 | 250 | |
1df81302 | 251 | int get_dma_residue(unsigned int chan) |
1da177e4 | 252 | { |
3afb6e9c | 253 | dma_t *dma = dma_channel(chan); |
1da177e4 LT |
254 | int ret = 0; |
255 | ||
256 | if (dma->d_ops->residue) | |
1df81302 | 257 | ret = dma->d_ops->residue(chan, dma); |
1da177e4 LT |
258 | |
259 | return ret; | |
260 | } | |
d7b4a756 | 261 | EXPORT_SYMBOL(get_dma_residue); |
e193ba29 RK |
262 | |
263 | #ifdef CONFIG_PROC_FS | |
264 | static int proc_dma_show(struct seq_file *m, void *v) | |
265 | { | |
266 | int i; | |
267 | ||
268 | for (i = 0 ; i < MAX_DMA_CHANNELS ; i++) { | |
269 | dma_t *dma = dma_channel(i); | |
270 | if (dma && dma->lock) | |
271 | seq_printf(m, "%2d: %s\n", i, dma->device_id); | |
272 | } | |
273 | return 0; | |
274 | } | |
275 | ||
e193ba29 RK |
276 | static int __init proc_dma_init(void) |
277 | { | |
3f3942ac | 278 | proc_create_single("dma", 0, NULL, proc_dma_show); |
e193ba29 RK |
279 | return 0; |
280 | } | |
281 | ||
282 | __initcall(proc_dma_init); | |
283 | #endif |