2 * On-chip DMA controller framework.
4 * Copyright (C) 2008 Nokia Corporation
5 * Written by Andrzej Zaborowski <andrew@openedhand.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 or
10 * (at your option) version 3 of the License.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "qemu-common.h"
22 #include "qemu-timer.h"
25 static void transfer_mem2mem(struct soc_dma_ch_s *ch)
27 memcpy(ch->paddr[0], ch->paddr[1], ch->bytes);
28 ch->paddr[0] += ch->bytes;
29 ch->paddr[1] += ch->bytes;
32 static void transfer_mem2fifo(struct soc_dma_ch_s *ch)
34 ch->io_fn[1](ch->io_opaque[1], ch->paddr[0], ch->bytes);
35 ch->paddr[0] += ch->bytes;
38 static void transfer_fifo2mem(struct soc_dma_ch_s *ch)
40 ch->io_fn[0](ch->io_opaque[0], ch->paddr[1], ch->bytes);
41 ch->paddr[1] += ch->bytes;
44 /* This is further optimisable but isn't very important because often
45 * DMA peripherals forbid this kind of transfers and even when they don't,
46 * oprating systems may not need to use them. */
47 static void *fifo_buf;
49 static void transfer_fifo2fifo(struct soc_dma_ch_s *ch)
51 if (ch->bytes > fifo_size)
52 fifo_buf = qemu_realloc(fifo_buf, fifo_size = ch->bytes);
54 /* Implement as transfer_fifo2linear + transfer_linear2fifo. */
55 ch->io_fn[0](ch->io_opaque[0], fifo_buf, ch->bytes);
56 ch->io_fn[1](ch->io_opaque[1], fifo_buf, ch->bytes);
62 uint64_t ch_enable_mask;
66 struct memmap_entry_s {
67 enum soc_dma_port_type type;
68 target_phys_addr_t addr;
83 struct soc_dma_ch_s ch[0];
86 static void soc_dma_ch_schedule(struct soc_dma_ch_s *ch, int delay_bytes)
88 int64_t now = qemu_get_clock(vm_clock);
89 struct dma_s *dma = (struct dma_s *) ch->dma;
91 qemu_mod_timer(ch->timer, now + delay_bytes / dma->channel_freq);
94 static void soc_dma_ch_run(void *opaque)
96 struct soc_dma_ch_s *ch = (struct soc_dma_ch_s *) opaque;
99 ch->dma->setup_fn(ch);
104 soc_dma_ch_schedule(ch, ch->bytes);
108 static inline struct memmap_entry_s *soc_dma_lookup(struct dma_s *dma,
109 target_phys_addr_t addr)
111 struct memmap_entry_s *lo;
115 hi = dma->memmap_size;
119 if (lo[hi].addr <= addr)
126 static inline enum soc_dma_port_type soc_dma_ch_update_type(
127 struct soc_dma_ch_s *ch, int port)
129 struct dma_s *dma = (struct dma_s *) ch->dma;
130 struct memmap_entry_s *entry = soc_dma_lookup(dma, ch->vaddr[port]);
132 if (entry->type == soc_dma_port_fifo) {
133 while (entry < dma->memmap + dma->memmap_size &&
134 entry->u.fifo.out != port)
136 if (entry->addr != ch->vaddr[port] || entry->u.fifo.out != port)
137 return soc_dma_port_other;
139 if (ch->type[port] != soc_dma_access_const)
140 return soc_dma_port_other;
142 ch->io_fn[port] = entry->u.fifo.fn;
143 ch->io_opaque[port] = entry->u.fifo.opaque;
144 return soc_dma_port_fifo;
145 } else if (entry->type == soc_dma_port_mem) {
146 if (entry->addr > ch->vaddr[port] ||
147 entry->addr + entry->u.mem.size <= ch->vaddr[port])
148 return soc_dma_port_other;
150 /* TODO: support constant memory address for source port as used for
151 * drawing solid rectangles by PalmOS(R). */
152 if (ch->type[port] != soc_dma_access_const)
153 return soc_dma_port_other;
155 ch->paddr[port] = (uint8_t *) entry->u.mem.base +
156 (ch->vaddr[port] - entry->addr);
157 /* TODO: save bytes left to the end of the mapping somewhere so we
158 * can check we're not reading beyond it. */
159 return soc_dma_port_mem;
161 return soc_dma_port_other;
164 void soc_dma_ch_update(struct soc_dma_ch_s *ch)
166 enum soc_dma_port_type src, dst;
168 src = soc_dma_ch_update_type(ch, 0);
169 if (src == soc_dma_port_other) {
171 ch->transfer_fn = ch->dma->transfer_fn;
174 dst = soc_dma_ch_update_type(ch, 1);
176 /* TODO: use src and dst as array indices. */
177 if (src == soc_dma_port_mem && dst == soc_dma_port_mem)
178 ch->transfer_fn = transfer_mem2mem;
179 else if (src == soc_dma_port_mem && dst == soc_dma_port_fifo)
180 ch->transfer_fn = transfer_mem2fifo;
181 else if (src == soc_dma_port_fifo && dst == soc_dma_port_mem)
182 ch->transfer_fn = transfer_fifo2mem;
183 else if (src == soc_dma_port_fifo && dst == soc_dma_port_fifo)
184 ch->transfer_fn = transfer_fifo2fifo;
186 ch->transfer_fn = ch->dma->transfer_fn;
188 ch->update = (dst != soc_dma_port_other);
191 static void soc_dma_ch_freq_update(struct dma_s *s)
193 if (s->enabled_count)
194 /* We completely ignore channel priorities and stuff */
195 s->channel_freq = s->soc.freq / s->enabled_count;
197 /* TODO: Signal that we want to disable the functional clock and let
198 * the platform code decide what to do with it, i.e. check that
199 * auto-idle is enabled in the clock controller and if we are stopping
200 * the clock, do the same with any parent clocks that had only one
201 * user keeping them on and auto-idle enabled. */;
204 void soc_dma_set_request(struct soc_dma_ch_s *ch, int level)
206 struct dma_s *dma = (struct dma_s *) ch->dma;
208 dma->enabled_count += level - ch->enable;
211 dma->ch_enable_mask |= 1 << ch->num;
213 dma->ch_enable_mask &= ~(1 << ch->num);
215 if (level != ch->enable) {
216 soc_dma_ch_freq_update(dma);
220 qemu_del_timer(ch->timer);
221 else if (!ch->running)
224 soc_dma_ch_schedule(ch, 1);
228 void soc_dma_reset(struct soc_dma_s *soc)
230 struct dma_s *s = (struct dma_s *) soc;
233 s->ch_enable_mask = 0;
234 s->enabled_count = 0;
235 soc_dma_ch_freq_update(s);
238 /* TODO: take a functional-clock argument */
239 struct soc_dma_s *soc_dma_init(int n)
242 struct dma_s *s = qemu_mallocz(sizeof(*s) + n * sizeof(*s->ch));
246 for (i = 0; i < n; i ++) {
247 s->ch[i].dma = &s->soc;
249 s->ch[i].timer = qemu_new_timer(vm_clock, soc_dma_ch_run, &s->ch[i]);
252 soc_dma_reset(&s->soc);
258 void soc_dma_port_add_fifo(struct soc_dma_s *soc, target_phys_addr_t virt_base,
259 soc_dma_io_t fn, void *opaque, int out)
261 struct memmap_entry_s *entry;
262 struct dma_s *dma = (struct dma_s *) soc;
264 dma->memmap = qemu_realloc(dma->memmap, sizeof(*entry) *
265 (dma->memmap_size + 1));
266 entry = soc_dma_lookup(dma, virt_base);
268 if (dma->memmap_size) {
269 if (entry->type == soc_dma_port_mem) {
270 if (entry->addr <= virt_base &&
271 entry->addr + entry->u.mem.size > virt_base) {
272 fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
273 " collides with RAM region at " TARGET_FMT_lx
274 "-" TARGET_FMT_lx "\n", __FUNCTION__,
275 (target_ulong) virt_base,
276 (target_ulong) entry->addr, (target_ulong)
277 (entry->addr + entry->u.mem.size));
281 if (entry->addr <= virt_base)
284 while (entry < dma->memmap + dma->memmap_size &&
285 entry->addr <= virt_base) {
286 if (entry->addr == virt_base && entry->u.fifo.out == out) {
287 fprintf(stderr, "%s: FIFO at " TARGET_FMT_lx
288 " collides FIFO at " TARGET_FMT_lx "\n",
289 __FUNCTION__, (target_ulong) virt_base,
290 (target_ulong) entry->addr);
297 memmove(entry + 1, entry,
298 (uint8_t *) (dma->memmap + dma->memmap_size ++) -
303 entry->addr = virt_base;
304 entry->type = soc_dma_port_fifo;
305 entry->u.fifo.fn = fn;
306 entry->u.fifo.opaque = opaque;
307 entry->u.fifo.out = out;
310 void soc_dma_port_add_mem(struct soc_dma_s *soc, uint8_t *phys_base,
311 target_phys_addr_t virt_base, size_t size)
313 struct memmap_entry_s *entry;
314 struct dma_s *dma = (struct dma_s *) soc;
316 dma->memmap = qemu_realloc(dma->memmap, sizeof(*entry) *
317 (dma->memmap_size + 1));
318 entry = soc_dma_lookup(dma, virt_base);
320 if (dma->memmap_size) {
321 if (entry->type == soc_dma_port_mem) {
322 if ((entry->addr >= virt_base && entry->addr < virt_base + size) ||
323 (entry->addr <= virt_base &&
324 entry->addr + entry->u.mem.size > virt_base)) {
325 fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
326 " collides with RAM region at " TARGET_FMT_lx
327 "-" TARGET_FMT_lx "\n", __FUNCTION__,
328 (target_ulong) virt_base,
329 (target_ulong) (virt_base + size),
330 (target_ulong) entry->addr, (target_ulong)
331 (entry->addr + entry->u.mem.size));
335 if (entry->addr <= virt_base)
338 if (entry->addr >= virt_base &&
339 entry->addr < virt_base + size) {
340 fprintf(stderr, "%s: RAM at " TARGET_FMT_lx "-" TARGET_FMT_lx
341 " collides with FIFO at " TARGET_FMT_lx
343 (target_ulong) virt_base,
344 (target_ulong) (virt_base + size),
345 (target_ulong) entry->addr);
349 while (entry < dma->memmap + dma->memmap_size &&
350 entry->addr <= virt_base)
354 memmove(entry + 1, entry,
355 (uint8_t *) (dma->memmap + dma->memmap_size ++) -
360 entry->addr = virt_base;
361 entry->type = soc_dma_port_mem;
362 entry->u.mem.base = phys_base;
363 entry->u.mem.size = size;
366 /* TODO: port removal for ports like PCMCIA memory */