Install keymaps from new location
[qemu] / hw / mac_dbdma.c
index 401384c..75a9f17 100644 (file)
@@ -3,6 +3,20 @@
  *
  * Copyright (c) 2005-2007 Fabrice Bellard
  * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2009 Laurent Vivier
+ *
+ * some parts from linux-2.6.28, arch/powerpc/include/asm/dbdma.h
+ *
+ *   Definitions for using the Apple Descriptor-Based DMA controller
+ *   in Power Macintosh computers.
+ *
+ *   Copyright (C) 1996 Paul Mackerras.
+ *
+ * some parts from mol 0.9.71
+ *
+ *   Descriptor based DMA emulation
+ *
+ *   Copyright (C) 1998-2004 Samuel Rydh (samuel@ibrium.se)
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * THE SOFTWARE.
  */
 #include "hw.h"
-#include "ppc_mac.h"
+#include "isa.h"
+#include "mac_dbdma.h"
 
-/* DBDMA: currently no op - should suffice right now */
+/* debug DBDMA */
+//#define DEBUG_DBDMA
 
-static void dbdma_writeb (void *opaque,
-                          target_phys_addr_t addr, uint32_t value)
+#ifdef DEBUG_DBDMA
+#define DBDMA_DPRINTF(fmt, ...)                                 \
+    do { printf("DBDMA: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DBDMA_DPRINTF(fmt, ...)
+#endif
+
+/*
+ */
+
+/*
+ * DBDMA control/status registers.  All little-endian.
+ */
+
+#define DBDMA_CONTROL         0x00
+#define DBDMA_STATUS          0x01
+#define DBDMA_CMDPTR_HI       0x02
+#define DBDMA_CMDPTR_LO       0x03
+#define DBDMA_INTR_SEL        0x04
+#define DBDMA_BRANCH_SEL      0x05
+#define DBDMA_WAIT_SEL        0x06
+#define DBDMA_XFER_MODE       0x07
+#define DBDMA_DATA2PTR_HI     0x08
+#define DBDMA_DATA2PTR_LO     0x09
+#define DBDMA_RES1            0x0A
+#define DBDMA_ADDRESS_HI      0x0B
+#define DBDMA_BRANCH_ADDR_HI  0x0C
+#define DBDMA_RES2            0x0D
+#define DBDMA_RES3            0x0E
+#define DBDMA_RES4            0x0F
+
+#define DBDMA_REGS            16
+#define DBDMA_SIZE            (DBDMA_REGS * sizeof(uint32_t))
+
+#define DBDMA_CHANNEL_SHIFT   7
+#define DBDMA_CHANNEL_SIZE    (1 << DBDMA_CHANNEL_SHIFT)
+
+#define DBDMA_CHANNELS        (0x1000 >> DBDMA_CHANNEL_SHIFT)
+
+/* Bits in control and status registers */
+
+#define RUN    0x8000
+#define PAUSE  0x4000
+#define FLUSH  0x2000
+#define WAKE   0x1000
+#define DEAD   0x0800
+#define ACTIVE 0x0400
+#define BT     0x0100
+#define DEVSTAT        0x00ff
+
+/*
+ * DBDMA command structure.  These fields are all little-endian!
+ */
+
+typedef struct dbdma_cmd {
+    uint16_t req_count;          /* requested byte transfer count */
+    uint16_t command;    /* command word (has bit-fields) */
+    uint32_t phy_addr;   /* physical data address */
+    uint32_t cmd_dep;    /* command-dependent field */
+    uint16_t res_count;          /* residual count after completion */
+    uint16_t xfer_status; /* transfer status */
+} dbdma_cmd;
+
+/* DBDMA command values in command field */
+
+#define COMMAND_MASK    0xf000
+#define OUTPUT_MORE    0x0000  /* transfer memory data to stream */
+#define OUTPUT_LAST    0x1000  /* ditto followed by end marker */
+#define INPUT_MORE     0x2000  /* transfer stream data to memory */
+#define INPUT_LAST     0x3000  /* ditto, expect end marker */
+#define STORE_WORD     0x4000  /* write word (4 bytes) to device reg */
+#define LOAD_WORD      0x5000  /* read word (4 bytes) from device reg */
+#define DBDMA_NOP      0x6000  /* do nothing */
+#define DBDMA_STOP     0x7000  /* suspend processing */
+
+/* Key values in command field */
+
+#define KEY_MASK        0x0700
+#define KEY_STREAM0    0x0000  /* usual data stream */
+#define KEY_STREAM1    0x0100  /* control/status stream */
+#define KEY_STREAM2    0x0200  /* device-dependent stream */
+#define KEY_STREAM3    0x0300  /* device-dependent stream */
+#define KEY_STREAM4    0x0400  /* reserved */
+#define KEY_REGS       0x0500  /* device register space */
+#define KEY_SYSTEM     0x0600  /* system memory-mapped space */
+#define KEY_DEVICE     0x0700  /* device memory-mapped space */
+
+/* Interrupt control values in command field */
+
+#define INTR_MASK       0x0030
+#define INTR_NEVER     0x0000  /* don't interrupt */
+#define INTR_IFSET     0x0010  /* intr if condition bit is 1 */
+#define INTR_IFCLR     0x0020  /* intr if condition bit is 0 */
+#define INTR_ALWAYS    0x0030  /* always interrupt */
+
+/* Branch control values in command field */
+
+#define BR_MASK         0x000c
+#define BR_NEVER       0x0000  /* don't branch */
+#define BR_IFSET       0x0004  /* branch if condition bit is 1 */
+#define BR_IFCLR       0x0008  /* branch if condition bit is 0 */
+#define BR_ALWAYS      0x000c  /* always branch */
+
+/* Wait control values in command field */
+
+#define WAIT_MASK       0x0003
+#define WAIT_NEVER     0x0000  /* don't wait */
+#define WAIT_IFSET     0x0001  /* wait if condition bit is 1 */
+#define WAIT_IFCLR     0x0002  /* wait if condition bit is 0 */
+#define WAIT_ALWAYS    0x0003  /* always wait */
+
+typedef struct DBDMA_channel {
+    int channel;
+    uint32_t regs[DBDMA_REGS];
+    qemu_irq irq;
+    DBDMA_io io;
+    DBDMA_rw rw;
+    DBDMA_flush flush;
+    dbdma_cmd current;
+    int processing;
+} DBDMA_channel;
+
+#ifdef DEBUG_DBDMA
+static void dump_dbdma_cmd(dbdma_cmd *cmd)
+{
+    printf("dbdma_cmd %p\n", cmd);
+    printf("    req_count 0x%04x\n", le16_to_cpu(cmd->req_count));
+    printf("    command 0x%04x\n", le16_to_cpu(cmd->command));
+    printf("    phy_addr 0x%08x\n", le32_to_cpu(cmd->phy_addr));
+    printf("    cmd_dep 0x%08x\n", le32_to_cpu(cmd->cmd_dep));
+    printf("    res_count 0x%04x\n", le16_to_cpu(cmd->res_count));
+    printf("    xfer_status 0x%04x\n", le16_to_cpu(cmd->xfer_status));
+}
+#else
+static void dump_dbdma_cmd(dbdma_cmd *cmd)
 {
-    printf("%s: 0x" PADDRX " <= 0x%08x\n", __func__, addr, value);
+}
+#endif
+static void dbdma_cmdptr_load(DBDMA_channel *ch)
+{
+    DBDMA_DPRINTF("dbdma_cmdptr_load 0x%08x\n",
+                  be32_to_cpu(ch->regs[DBDMA_CMDPTR_LO]));
+    cpu_physical_memory_read(be32_to_cpu(ch->regs[DBDMA_CMDPTR_LO]),
+                             (uint8_t*)&ch->current, sizeof(dbdma_cmd));
 }
 
-static void dbdma_writew (void *opaque,
-                          target_phys_addr_t addr, uint32_t value)
+static void dbdma_cmdptr_save(DBDMA_channel *ch)
 {
+    DBDMA_DPRINTF("dbdma_cmdptr_save 0x%08x\n",
+                  be32_to_cpu(ch->regs[DBDMA_CMDPTR_LO]));
+    DBDMA_DPRINTF("xfer_status 0x%08x res_count 0x%04x\n",
+                  le16_to_cpu(ch->current.xfer_status),
+                  le16_to_cpu(ch->current.res_count));
+    cpu_physical_memory_write(be32_to_cpu(ch->regs[DBDMA_CMDPTR_LO]),
+                              (uint8_t*)&ch->current, sizeof(dbdma_cmd));
 }
 
-static void dbdma_writel (void *opaque,
-                          target_phys_addr_t addr, uint32_t value)
+static void kill_channel(DBDMA_channel *ch)
 {
+    DBDMA_DPRINTF("kill_channel\n");
+
+    ch->regs[DBDMA_STATUS] |= cpu_to_be32(DEAD);
+    ch->regs[DBDMA_STATUS] &= cpu_to_be32(~ACTIVE);
+
+    qemu_irq_raise(ch->irq);
 }
 
-static uint32_t dbdma_readb (void *opaque, target_phys_addr_t addr)
+static void conditional_interrupt(DBDMA_channel *ch)
 {
-    printf("%s: 0x" PADDRX " => 0x00000000\n", __func__, addr);
+    dbdma_cmd *current = &ch->current;
+    uint16_t intr;
+    uint16_t sel_mask, sel_value;
+    uint32_t status;
+    int cond;
 
-    return 0;
+    DBDMA_DPRINTF("conditional_interrupt\n");
+
+    intr = le16_to_cpu(current->command) & INTR_MASK;
+
+    switch(intr) {
+    case INTR_NEVER:  /* don't interrupt */
+        return;
+    case INTR_ALWAYS: /* always interrupt */
+        qemu_irq_raise(ch->irq);
+        return;
+    }
+
+    status = be32_to_cpu(ch->regs[DBDMA_STATUS]) & DEVSTAT;
+
+    sel_mask = (be32_to_cpu(ch->regs[DBDMA_INTR_SEL]) >> 16) & 0x0f;
+    sel_value = be32_to_cpu(ch->regs[DBDMA_INTR_SEL]) & 0x0f;
+
+    cond = (status & sel_mask) == (sel_value & sel_mask);
+
+    switch(intr) {
+    case INTR_IFSET:  /* intr if condition bit is 1 */
+        if (cond)
+            qemu_irq_raise(ch->irq);
+        return;
+    case INTR_IFCLR:  /* intr if condition bit is 0 */
+        if (!cond)
+            qemu_irq_raise(ch->irq);
+        return;
+    }
 }
 
-static uint32_t dbdma_readw (void *opaque, target_phys_addr_t addr)
+static int conditional_wait(DBDMA_channel *ch)
 {
+    dbdma_cmd *current = &ch->current;
+    uint16_t wait;
+    uint16_t sel_mask, sel_value;
+    uint32_t status;
+    int cond;
+
+    DBDMA_DPRINTF("conditional_wait\n");
+
+    wait = le16_to_cpu(current->command) & WAIT_MASK;
+
+    switch(wait) {
+    case WAIT_NEVER:  /* don't wait */
+        return 0;
+    case WAIT_ALWAYS: /* always wait */
+        return 1;
+    }
+
+    status = be32_to_cpu(ch->regs[DBDMA_STATUS]) & DEVSTAT;
+
+    sel_mask = (be32_to_cpu(ch->regs[DBDMA_WAIT_SEL]) >> 16) & 0x0f;
+    sel_value = be32_to_cpu(ch->regs[DBDMA_WAIT_SEL]) & 0x0f;
+
+    cond = (status & sel_mask) == (sel_value & sel_mask);
+
+    switch(wait) {
+    case WAIT_IFSET:  /* wait if condition bit is 1 */
+        if (cond)
+            return 1;
+        return 0;
+    case WAIT_IFCLR:  /* wait if condition bit is 0 */
+        if (!cond)
+            return 1;
+        return 0;
+    }
     return 0;
 }
 
+static void next(DBDMA_channel *ch)
+{
+    uint32_t cp;
+
+    ch->regs[DBDMA_STATUS] &= cpu_to_be32(~BT);
+
+    cp = be32_to_cpu(ch->regs[DBDMA_CMDPTR_LO]);
+    ch->regs[DBDMA_CMDPTR_LO] = cpu_to_be32(cp + sizeof(dbdma_cmd));
+    dbdma_cmdptr_load(ch);
+}
+
+static void branch(DBDMA_channel *ch)
+{
+    dbdma_cmd *current = &ch->current;
+
+    ch->regs[DBDMA_CMDPTR_LO] = current->cmd_dep;
+    ch->regs[DBDMA_STATUS] |= cpu_to_be32(BT);
+    dbdma_cmdptr_load(ch);
+}
+
+static void conditional_branch(DBDMA_channel *ch)
+{
+    dbdma_cmd *current = &ch->current;
+    uint16_t br;
+    uint16_t sel_mask, sel_value;
+    uint32_t status;
+    int cond;
+
+    DBDMA_DPRINTF("conditional_branch\n");
+
+    /* check if we must branch */
+
+    br = le16_to_cpu(current->command) & BR_MASK;
+
+    switch(br) {
+    case BR_NEVER:  /* don't branch */
+        next(ch);
+        return;
+    case BR_ALWAYS: /* always branch */
+        branch(ch);
+        return;
+    }
+
+    status = be32_to_cpu(ch->regs[DBDMA_STATUS]) & DEVSTAT;
+
+    sel_mask = (be32_to_cpu(ch->regs[DBDMA_BRANCH_SEL]) >> 16) & 0x0f;
+    sel_value = be32_to_cpu(ch->regs[DBDMA_BRANCH_SEL]) & 0x0f;
+
+    cond = (status & sel_mask) == (sel_value & sel_mask);
+
+    switch(br) {
+    case BR_IFSET:  /* branch if condition bit is 1 */
+        if (cond)
+            branch(ch);
+        else
+            next(ch);
+        return;
+    case BR_IFCLR:  /* branch if condition bit is 0 */
+        if (!cond)
+            branch(ch);
+        else
+            next(ch);
+        return;
+    }
+}
+
+static QEMUBH *dbdma_bh;
+static void channel_run(DBDMA_channel *ch);
+
+static void dbdma_end(DBDMA_io *io)
+{
+    DBDMA_channel *ch = io->channel;
+    dbdma_cmd *current = &ch->current;
+
+    if (conditional_wait(ch))
+        goto wait;
+
+    current->xfer_status = cpu_to_le16(be32_to_cpu(ch->regs[DBDMA_STATUS]));
+    current->res_count = cpu_to_le16(be32_to_cpu(io->len));
+    dbdma_cmdptr_save(ch);
+    if (io->is_last)
+        ch->regs[DBDMA_STATUS] &= cpu_to_be32(~FLUSH);
+
+    conditional_interrupt(ch);
+    conditional_branch(ch);
+
+wait:
+    ch->processing = 0;
+    if ((ch->regs[DBDMA_STATUS] & cpu_to_be32(RUN)) &&
+        (ch->regs[DBDMA_STATUS] & cpu_to_be32(ACTIVE)))
+        channel_run(ch);
+}
+
+static void start_output(DBDMA_channel *ch, int key, uint32_t addr,
+                        uint16_t req_count, int is_last)
+{
+    DBDMA_DPRINTF("start_output\n");
+
+    /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+     * are not implemented in the mac-io chip
+     */
+
+    DBDMA_DPRINTF("addr 0x%x key 0x%x\n", addr, key);
+    if (!addr || key > KEY_STREAM3) {
+        kill_channel(ch);
+        return;
+    }
+
+    ch->io.addr = addr;
+    ch->io.len = req_count;
+    ch->io.is_last = is_last;
+    ch->io.dma_end = dbdma_end;
+    ch->io.is_dma_out = 1;
+    ch->processing = 1;
+    ch->rw(&ch->io);
+}
+
+static void start_input(DBDMA_channel *ch, int key, uint32_t addr,
+                       uint16_t req_count, int is_last)
+{
+    DBDMA_DPRINTF("start_input\n");
+
+    /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+     * are not implemented in the mac-io chip
+     */
+
+    if (!addr || key > KEY_STREAM3) {
+        kill_channel(ch);
+        return;
+    }
+
+    ch->io.addr = addr;
+    ch->io.len = req_count;
+    ch->io.is_last = is_last;
+    ch->io.dma_end = dbdma_end;
+    ch->io.is_dma_out = 0;
+    ch->processing = 1;
+    ch->rw(&ch->io);
+}
+
+static void load_word(DBDMA_channel *ch, int key, uint32_t addr,
+                     uint16_t len)
+{
+    dbdma_cmd *current = &ch->current;
+    uint32_t val;
+
+    DBDMA_DPRINTF("load_word\n");
+
+    /* only implements KEY_SYSTEM */
+
+    if (key != KEY_SYSTEM) {
+        printf("DBDMA: LOAD_WORD, unimplemented key %x\n", key);
+        kill_channel(ch);
+        return;
+    }
+
+    cpu_physical_memory_read(addr, (uint8_t*)&val, len);
+
+    if (len == 2)
+        val = (val << 16) | (current->cmd_dep & 0x0000ffff);
+    else if (len == 1)
+        val = (val << 24) | (current->cmd_dep & 0x00ffffff);
+
+    current->cmd_dep = val;
+
+    if (conditional_wait(ch))
+        goto wait;
+
+    current->xfer_status = cpu_to_le16(be32_to_cpu(ch->regs[DBDMA_STATUS]));
+    dbdma_cmdptr_save(ch);
+    ch->regs[DBDMA_STATUS] &= cpu_to_be32(~FLUSH);
+
+    conditional_interrupt(ch);
+    next(ch);
+
+wait:
+    qemu_bh_schedule(dbdma_bh);
+}
+
+static void store_word(DBDMA_channel *ch, int key, uint32_t addr,
+                      uint16_t len)
+{
+    dbdma_cmd *current = &ch->current;
+    uint32_t val;
+
+    DBDMA_DPRINTF("store_word\n");
+
+    /* only implements KEY_SYSTEM */
+
+    if (key != KEY_SYSTEM) {
+        printf("DBDMA: STORE_WORD, unimplemented key %x\n", key);
+        kill_channel(ch);
+        return;
+    }
+
+    val = current->cmd_dep;
+    if (len == 2)
+        val >>= 16;
+    else if (len == 1)
+        val >>= 24;
+
+    cpu_physical_memory_write(addr, (uint8_t*)&val, len);
+
+    if (conditional_wait(ch))
+        goto wait;
+
+    current->xfer_status = cpu_to_le16(be32_to_cpu(ch->regs[DBDMA_STATUS]));
+    dbdma_cmdptr_save(ch);
+    ch->regs[DBDMA_STATUS] &= cpu_to_be32(~FLUSH);
+
+    conditional_interrupt(ch);
+    next(ch);
+
+wait:
+    qemu_bh_schedule(dbdma_bh);
+}
+
+static void nop(DBDMA_channel *ch)
+{
+    dbdma_cmd *current = &ch->current;
+
+    if (conditional_wait(ch))
+        goto wait;
+
+    current->xfer_status = cpu_to_le16(be32_to_cpu(ch->regs[DBDMA_STATUS]));
+    dbdma_cmdptr_save(ch);
+
+    conditional_interrupt(ch);
+    conditional_branch(ch);
+
+wait:
+    qemu_bh_schedule(dbdma_bh);
+}
+
+static void stop(DBDMA_channel *ch)
+{
+    ch->regs[DBDMA_STATUS] &= cpu_to_be32(~(ACTIVE|DEAD|FLUSH));
+
+    /* the stop command does not increment command pointer */
+}
+
+static void channel_run(DBDMA_channel *ch)
+{
+    dbdma_cmd *current = &ch->current;
+    uint16_t cmd, key;
+    uint16_t req_count;
+    uint32_t phy_addr;
+
+    DBDMA_DPRINTF("channel_run\n");
+    dump_dbdma_cmd(current);
+
+    /* clear WAKE flag at command fetch */
+
+    ch->regs[DBDMA_STATUS] &= cpu_to_be32(~WAKE);
+
+    cmd = le16_to_cpu(current->command) & COMMAND_MASK;
+
+    switch (cmd) {
+    case DBDMA_NOP:
+        nop(ch);
+       return;
+
+    case DBDMA_STOP:
+        stop(ch);
+       return;
+    }
+
+    key = le16_to_cpu(current->command) & 0x0700;
+    req_count = le16_to_cpu(current->req_count);
+    phy_addr = le32_to_cpu(current->phy_addr);
+
+    if (key == KEY_STREAM4) {
+        printf("command %x, invalid key 4\n", cmd);
+        kill_channel(ch);
+        return;
+    }
+
+    switch (cmd) {
+    case OUTPUT_MORE:
+        start_output(ch, key, phy_addr, req_count, 0);
+       return;
+
+    case OUTPUT_LAST:
+        start_output(ch, key, phy_addr, req_count, 1);
+       return;
+
+    case INPUT_MORE:
+        start_input(ch, key, phy_addr, req_count, 0);
+       return;
+
+    case INPUT_LAST:
+        start_input(ch, key, phy_addr, req_count, 1);
+       return;
+    }
+
+    if (key < KEY_REGS) {
+        printf("command %x, invalid key %x\n", cmd, key);
+        key = KEY_SYSTEM;
+    }
+
+    /* for LOAD_WORD and STORE_WORD, req_count is on 3 bits
+     * and BRANCH is invalid
+     */
+
+    req_count = req_count & 0x0007;
+    if (req_count & 0x4) {
+        req_count = 4;
+        phy_addr &= ~3;
+    } else if (req_count & 0x2) {
+        req_count = 2;
+        phy_addr &= ~1;
+    } else
+        req_count = 1;
+
+    switch (cmd) {
+    case LOAD_WORD:
+        load_word(ch, key, phy_addr, req_count);
+       return;
+
+    case STORE_WORD:
+        store_word(ch, key, phy_addr, req_count);
+       return;
+    }
+}
+
+static void DBDMA_run (DBDMA_channel *ch)
+{
+    int channel;
+
+    for (channel = 0; channel < DBDMA_CHANNELS; channel++, ch++) {
+            uint32_t status = be32_to_cpu(ch->regs[DBDMA_STATUS]);
+            if (!ch->processing && (status & RUN) && (status & ACTIVE))
+                channel_run(ch);
+    }
+}
+
+static void DBDMA_run_bh(void *opaque)
+{
+    DBDMA_channel *ch = opaque;
+
+    DBDMA_DPRINTF("DBDMA_run_bh\n");
+
+    DBDMA_run(ch);
+}
+
+void DBDMA_register_channel(void *dbdma, int nchan, qemu_irq irq,
+                            DBDMA_rw rw, DBDMA_flush flush,
+                            void *opaque)
+{
+    DBDMA_channel *ch = ( DBDMA_channel *)dbdma + nchan;
+
+    DBDMA_DPRINTF("DBDMA_register_channel 0x%x\n", nchan);
+
+    ch->irq = irq;
+    ch->channel = nchan;
+    ch->rw = rw;
+    ch->flush = flush;
+    ch->io.opaque = opaque;
+    ch->io.channel = ch;
+}
+
+void DBDMA_schedule(void)
+{
+    qemu_notify_event();
+}
+
+static void
+dbdma_control_write(DBDMA_channel *ch)
+{
+    uint16_t mask, value;
+    uint32_t status;
+
+    mask = (be32_to_cpu(ch->regs[DBDMA_CONTROL]) >> 16) & 0xffff;
+    value = be32_to_cpu(ch->regs[DBDMA_CONTROL]) & 0xffff;
+
+    value &= (RUN | PAUSE | FLUSH | WAKE | DEVSTAT);
+
+    status = be32_to_cpu(ch->regs[DBDMA_STATUS]);
+
+    status = (value & mask) | (status & ~mask);
+
+    if (status & WAKE)
+        status |= ACTIVE;
+    if (status & RUN) {
+        status |= ACTIVE;
+        status &= ~DEAD;
+    }
+    if (status & PAUSE)
+        status &= ~ACTIVE;
+    if ((be32_to_cpu(ch->regs[DBDMA_STATUS]) & RUN) && !(status & RUN)) {
+        /* RUN is cleared */
+        status &= ~(ACTIVE|DEAD);
+    }
+
+    DBDMA_DPRINTF("    status 0x%08x\n", status);
+
+    ch->regs[DBDMA_STATUS] = cpu_to_be32(status);
+
+    if (status & ACTIVE)
+        qemu_bh_schedule(dbdma_bh);
+    if (status & FLUSH)
+        ch->flush(&ch->io);
+}
+
+static void dbdma_writel (void *opaque,
+                          target_phys_addr_t addr, uint32_t value)
+{
+    int channel = addr >> DBDMA_CHANNEL_SHIFT;
+    DBDMA_channel *ch = (DBDMA_channel *)opaque + channel;
+    int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+    DBDMA_DPRINTF("writel 0x" TARGET_FMT_plx " <= 0x%08x\n", addr, value);
+    DBDMA_DPRINTF("channel 0x%x reg 0x%x\n",
+                  (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+    /* cmdptr cannot be modified if channel is RUN or ACTIVE */
+
+    if (reg == DBDMA_CMDPTR_LO &&
+        (ch->regs[DBDMA_STATUS] & cpu_to_be32(RUN | ACTIVE)))
+       return;
+
+    ch->regs[reg] = value;
+
+    switch(reg) {
+    case DBDMA_CONTROL:
+        dbdma_control_write(ch);
+        break;
+    case DBDMA_CMDPTR_LO:
+        /* 16-byte aligned */
+        ch->regs[DBDMA_CMDPTR_LO] &= cpu_to_be32(~0xf);
+        dbdma_cmdptr_load(ch);
+        break;
+    case DBDMA_STATUS:
+    case DBDMA_INTR_SEL:
+    case DBDMA_BRANCH_SEL:
+    case DBDMA_WAIT_SEL:
+        /* nothing to do */
+        break;
+    case DBDMA_XFER_MODE:
+    case DBDMA_CMDPTR_HI:
+    case DBDMA_DATA2PTR_HI:
+    case DBDMA_DATA2PTR_LO:
+    case DBDMA_ADDRESS_HI:
+    case DBDMA_BRANCH_ADDR_HI:
+    case DBDMA_RES1:
+    case DBDMA_RES2:
+    case DBDMA_RES3:
+    case DBDMA_RES4:
+        /* unused */
+        break;
+    }
+}
+
 static uint32_t dbdma_readl (void *opaque, target_phys_addr_t addr)
 {
-    return 0;
+    uint32_t value;
+    int channel = addr >> DBDMA_CHANNEL_SHIFT;
+    DBDMA_channel *ch = (DBDMA_channel *)opaque + channel;
+    int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+    value = ch->regs[reg];
+
+    DBDMA_DPRINTF("readl 0x" TARGET_FMT_plx " => 0x%08x\n", addr, value);
+    DBDMA_DPRINTF("channel 0x%x reg 0x%x\n",
+                  (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+    switch(reg) {
+    case DBDMA_CONTROL:
+        value = 0;
+        break;
+    case DBDMA_STATUS:
+    case DBDMA_CMDPTR_LO:
+    case DBDMA_INTR_SEL:
+    case DBDMA_BRANCH_SEL:
+    case DBDMA_WAIT_SEL:
+        /* nothing to do */
+        break;
+    case DBDMA_XFER_MODE:
+    case DBDMA_CMDPTR_HI:
+    case DBDMA_DATA2PTR_HI:
+    case DBDMA_DATA2PTR_LO:
+    case DBDMA_ADDRESS_HI:
+    case DBDMA_BRANCH_ADDR_HI:
+        /* unused */
+        value = 0;
+        break;
+    case DBDMA_RES1:
+    case DBDMA_RES2:
+    case DBDMA_RES3:
+    case DBDMA_RES4:
+        /* reserved */
+        break;
+    }
+
+    return value;
 }
 
 static CPUWriteMemoryFunc *dbdma_write[] = {
-    &dbdma_writeb,
-    &dbdma_writew,
-    &dbdma_writel,
+    NULL,
+    NULL,
+    dbdma_writel,
 };
 
 static CPUReadMemoryFunc *dbdma_read[] = {
-    &dbdma_readb,
-    &dbdma_readw,
-    &dbdma_readl,
+    NULL,
+    NULL,
+    dbdma_readl,
 };
 
-void dbdma_init (int *dbdma_mem_index)
+static void dbdma_save(QEMUFile *f, void *opaque)
 {
-    *dbdma_mem_index = cpu_register_io_memory(0, dbdma_read, dbdma_write, NULL);
+    DBDMA_channel *s = opaque;
+    unsigned int i, j;
+
+    for (i = 0; i < DBDMA_CHANNELS; i++)
+        for (j = 0; j < DBDMA_REGS; j++)
+            qemu_put_be32s(f, &s[i].regs[j]);
+}
+
+static int dbdma_load(QEMUFile *f, void *opaque, int version_id)
+{
+    DBDMA_channel *s = opaque;
+    unsigned int i, j;
+
+    if (version_id != 2)
+        return -EINVAL;
+
+    for (i = 0; i < DBDMA_CHANNELS; i++)
+        for (j = 0; j < DBDMA_REGS; j++)
+            qemu_get_be32s(f, &s[i].regs[j]);
+
+    return 0;
+}
+
+static void dbdma_reset(void *opaque)
+{
+    DBDMA_channel *s = opaque;
+    int i;
+
+    for (i = 0; i < DBDMA_CHANNELS; i++)
+        memset(s[i].regs, 0, DBDMA_SIZE);
+}
+
+void* DBDMA_init (int *dbdma_mem_index)
+{
+    DBDMA_channel *s;
+
+    s = qemu_mallocz(sizeof(DBDMA_channel) * DBDMA_CHANNELS);
+
+    *dbdma_mem_index = cpu_register_io_memory(0, dbdma_read, dbdma_write, s);
+    register_savevm("dbdma", -1, 1, dbdma_save, dbdma_load, s);
+    qemu_register_reset(dbdma_reset, 0, s);
+    dbdma_reset(s);
+
+    dbdma_bh = qemu_bh_new(DBDMA_run_bh, s);
+
+    return s;
 }