--- /dev/null
+/*
+ * Copyright © 2009 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+
+#define NUM_DMA 5
+
+/*
+ * The config address for DMA0 is programmed
+ * separately from that of DMA1-4, but for simplicity,
+ * we make them all contiguous.
+ */
+
+static __xdata struct cc_dma_channel ao_dma_config[NUM_DMA];
+static __xdata uint8_t * __xdata ao_dma_done[NUM_DMA];
+static __data uint8_t ao_next_dma;
+
+uint8_t
+ao_dma_alloc(__xdata uint8_t *done)
+{
+ uint8_t id;
+
+ if (ao_next_dma == NUM_DMA)
+ ao_panic(AO_PANIC_DMA);
+ id = ao_next_dma++;
+ ao_dma_done[id] = done;
+
+ /* When the first dma object is allocated, set up the DMA
+ * controller
+ */
+ if (id == 0) {
+ DMAIRQ = 0;
+ DMAIF = 0;
+ IEN1 |= IEN1_DMAIE;
+ }
+
+ return id;
+}
+
+void
+ao_dma_set_transfer(uint8_t id,
+ void __xdata *srcaddr,
+ void __xdata *dstaddr,
+ uint16_t count,
+ uint8_t cfg0,
+ uint8_t cfg1)
+{
+ if (DMAARM & (1 << id))
+ ao_panic(AO_PANIC_DMA);
+ ao_dma_config[id].src_high = ((uint16_t) srcaddr) >> 8;
+ ao_dma_config[id].src_low = ((uint16_t) srcaddr);
+ ao_dma_config[id].dst_high = ((uint16_t) dstaddr) >> 8;
+ ao_dma_config[id].dst_low = ((uint16_t) dstaddr);
+ ao_dma_config[id].len_high = count >> 8;
+ ao_dma_config[id].len_low = count;
+ ao_dma_config[id].cfg0 = cfg0;
+ ao_dma_config[id].cfg1 = cfg1 | DMA_CFG1_IRQMASK;
+ if (id == 0) {
+ DMA0CFGH = ((uint16_t) (&ao_dma_config[0])) >> 8;
+ DMA0CFGL = ((uint16_t) (&ao_dma_config[0]));
+ } else {
+ DMA1CFGH = ((uint16_t) (&ao_dma_config[1])) >> 8;
+ DMA1CFGL = ((uint16_t) (&ao_dma_config[1]));
+ }
+}
+
+#define nop() _asm nop _endasm;
+
+void
+ao_dma_start(uint8_t id)
+{
+ uint8_t mask = (1 << id);
+ DMAIRQ &= ~mask;
+ DMAARM = 0x80 | mask;
+ nop(); nop(); nop(); nop();
+ nop(); nop(); nop(); nop();
+ DMAARM = mask;
+ nop(); nop(); nop(); nop();
+ nop(); nop(); nop(); nop();
+ nop();
+ *(ao_dma_done[id]) = 0;
+}
+
+void
+ao_dma_trigger(uint8_t id)
+{
+ DMAREQ |= (1 << id);
+}
+
+void
+ao_dma_abort(uint8_t id)
+{
+ uint8_t mask = (1 << id);
+ DMAARM = 0x80 | mask;
+ DMAIRQ &= ~mask;
+}
+
+void
+ao_dma_isr(void) interrupt 8
+{
+ uint8_t id, mask;
+
+ /* Find the first DMA channel which is done */
+ mask = 1;
+ for (id = 0; id < ao_next_dma; id++) {
+ if (DMAIRQ & mask) {
+ /* Clear CPU interrupt flag */
+ DMAIF = 0;
+ /* Clear the completed ID */
+ DMAIRQ = ~mask;
+ *(ao_dma_done[id]) = 1;
+ ao_wakeup(ao_dma_done[id]);
+ break;
+ }
+ mask <<= 1;
+ }
+}