From b7382e099d660deebedcd8fe10159cf1e85cebab Mon Sep 17 00:00:00 2001
From: Nathan Whitehorn <nwhitehorn@FreeBSD.org>
Date: Sat, 27 Sep 2008 15:13:44 +0000
Subject: [PATCH] Add DMA support for Apple built-in ATA controllers.

Tested by:	grehan, marcotrillo@gmail.com
MFC after:	1 month
---
 sys/conf/files.powerpc           |   1 +
 sys/powerpc/powermac/ata_dbdma.c | 282 +++++++++++++++++++++++++++++++
 sys/powerpc/powermac/ata_dbdma.h |  55 ++++++
 sys/powerpc/powermac/ata_kauai.c | 267 ++++++++++++++++++++++++++---
 sys/powerpc/powermac/ata_macio.c | 225 ++++++++++++++++++++++--
 5 files changed, 796 insertions(+), 34 deletions(-)
 create mode 100644 sys/powerpc/powermac/ata_dbdma.c
 create mode 100644 sys/powerpc/powermac/ata_dbdma.h

diff --git a/sys/conf/files.powerpc b/sys/conf/files.powerpc
index 0dea42652960..bc9e950b0de1 100644
--- a/sys/conf/files.powerpc
+++ b/sys/conf/files.powerpc
@@ -109,6 +109,7 @@ powerpc/ofw/ofw_pcib_pci.c	optional	pci aim
 powerpc/ofw/ofw_syscons.c	optional	sc aim
 powerpc/powermac/ata_kauai.c	optional	powermac ata
 powerpc/powermac/ata_macio.c	optional	powermac ata
+powerpc/powermac/ata_dbdma.c	optional	powermac ata
 powerpc/powermac/dbdma.c	optional	powermac pci
 powerpc/powermac/grackle.c	optional	powermac pci
 powerpc/powermac/hrowpic.c	optional	powermac pci
diff --git a/sys/powerpc/powermac/ata_dbdma.c b/sys/powerpc/powermac/ata_dbdma.c
new file mode 100644
index 000000000000..fe53255337c8
--- /dev/null
+++ b/sys/powerpc/powermac/ata_dbdma.c
@@ -0,0 +1,282 @@
+/*-
+ * Copyright 2008 by Nathan Whitehorn. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Common routines for the DMA engine on both the Apple Kauai and MacIO
+ * ATA controllers.
+ */
+
+#include "opt_ata.h"
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/malloc.h>
+#include <sys/sema.h>
+#include <sys/taskqueue.h>
+#include <vm/uma.h>
+#include <machine/stdarg.h>
+#include <machine/resource.h>
+#include <machine/bus.h>
+#include <sys/rman.h>
+#include <sys/ata.h>
+#include <dev/ata/ata-all.h>
+#include <dev/ata/ata-pci.h>
+#include <ata_if.h>
+
+#include "ata_dbdma.h"
+
+struct ata_dbdma_dmaload_args {
+	struct ata_dbdma_channel *sc;
+
+	int write;
+	int nsegs;
+};
+
+static void
+ata_dbdma_setprd(void *xarg, bus_dma_segment_t *segs, int nsegs, int error)
+{
+	struct ata_dbdma_dmaload_args *arg = xarg;
+	struct ata_dbdma_channel *sc = arg->sc;
+	int branch_type, command;
+	int prev_stop;
+	int i;
+
+	mtx_lock(&sc->dbdma_mtx);
+
+	prev_stop = sc->next_dma_slot-1;
+	if (prev_stop < 0)
+		prev_stop = 0xff;
+
+	for (i = 0; i < nsegs; i++) {
+		/* Loop back to the beginning if this is our last slot */
+		if (sc->next_dma_slot == 0xff)
+			branch_type = DBDMA_ALWAYS;
+		else
+			branch_type = DBDMA_NEVER;
+
+		if (arg->write) {
+			command = (i + 1 < nsegs) ? DBDMA_OUTPUT_MORE : 
+			    DBDMA_OUTPUT_LAST;
+		} else {
+			command = (i + 1 < nsegs) ? DBDMA_INPUT_MORE : 
+			    DBDMA_INPUT_LAST;
+		}
+
+		dbdma_insert_command(sc->dbdma, sc->next_dma_slot++,
+		    command, 0, segs[i].ds_addr, segs[i].ds_len,
+		    DBDMA_NEVER, branch_type, DBDMA_NEVER, 0);
+
+		if (branch_type == DBDMA_ALWAYS)
+			sc->next_dma_slot = 0;
+	}
+
+	/* We have a corner case where the STOP command is the last slot,
+	 * but you can't branch in STOP commands. So add a NOP branch here
+	 * and the STOP in slot 0. */
+
+	if (sc->next_dma_slot == 0xff) {
+		dbdma_insert_branch(sc->dbdma, sc->next_dma_slot, 0);
+		sc->next_dma_slot = 0;
+	}
+
+#if 0
+	dbdma_insert_command(sc->dbdma, sc->next_dma_slot++,
+	    DBDMA_NOP, 0, 0, 0, DBDMA_ALWAYS, DBDMA_NEVER, DBDMA_NEVER, 0);
+#endif
+	dbdma_insert_stop(sc->dbdma, sc->next_dma_slot++);
+	dbdma_insert_nop(sc->dbdma, prev_stop);
+
+	dbdma_sync_commands(sc->dbdma, BUS_DMASYNC_PREWRITE);
+
+	mtx_unlock(&sc->dbdma_mtx);
+
+	arg->nsegs = nsegs;
+}
+
+static int
+ata_dbdma_status(device_t dev)
+{
+	struct ata_dbdma_channel *sc = device_get_softc(dev);
+	struct ata_channel *ch = device_get_softc(dev);
+
+	if (sc->sc_ch.dma.flags & ATA_DMA_ACTIVE) {
+		return (!(dbdma_get_chan_status(sc->dbdma) & 
+		    DBDMA_STATUS_ACTIVE));
+	}
+
+	if (ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_BUSY) {
+		DELAY(100);
+		if (ATA_IDX_INB(ch, ATA_ALTSTAT) & ATA_S_BUSY)
+			return 0;
+	}
+	return 1;
+}
+
+static int
+ata_dbdma_start(struct ata_request *request)
+{
+	struct ata_dbdma_channel *sc = device_get_softc(request->parent);
+
+	sc->sc_ch.dma.flags |= ATA_DMA_ACTIVE;
+	dbdma_wake(sc->dbdma);
+	return 0;
+}
+
+static void
+ata_dbdma_reset(device_t dev)
+{
+	struct ata_dbdma_channel *sc = device_get_softc(dev);
+
+	mtx_lock(&sc->dbdma_mtx);
+
+	dbdma_stop(sc->dbdma);
+	dbdma_insert_stop(sc->dbdma, 0);
+	sc->next_dma_slot=1;
+	dbdma_set_current_cmd(sc->dbdma, 0);
+
+	sc->sc_ch.dma.flags &= ~ATA_DMA_ACTIVE;
+
+	mtx_unlock(&sc->dbdma_mtx);
+}
+
+static int
+ata_dbdma_stop(struct ata_request *request)
+{
+	struct ata_dbdma_channel *sc = device_get_softc(request->parent);
+
+	uint16_t status;
+	
+	status = dbdma_get_chan_status(sc->dbdma);
+
+	dbdma_pause(sc->dbdma);
+	sc->sc_ch.dma.flags &= ~ATA_DMA_ACTIVE;
+
+	if (status & DBDMA_STATUS_DEAD) {
+		device_printf(request->parent,"DBDMA dead, resetting "
+		    "channel...\n");
+		ata_dbdma_reset(request->parent);
+		return ATA_S_ERROR;
+	}
+
+	if (!(status & DBDMA_STATUS_RUN)) {
+		device_printf(request->parent,"DBDMA confused, stop called "
+		    "when channel is not running!\n");
+		return ATA_S_ERROR;
+	}
+
+	if (status & DBDMA_STATUS_ACTIVE) {
+		device_printf(request->parent,"DBDMA channel stopped "
+		    "prematurely\n");
+		return ATA_S_ERROR;
+	}
+	return 0;
+}
+
+static int
+ata_dbdma_load(struct ata_request *request, void *addr, int *entries)
+{
+	struct ata_channel *ch = device_get_softc(request->parent);
+	struct ata_device *atadev = device_get_softc(request->dev);
+	struct ata_dbdma_dmaload_args args;
+
+	int error;
+
+	args.sc = device_get_softc(request->parent);
+	args.write = !(request->flags & ATA_R_READ);
+
+	if (!request->bytecount) {
+		device_printf(request->dev,
+		    "FAILURE - zero length DMA transfer attempted\n");
+		return EIO;
+	}
+	if (((uintptr_t)(request->data) & (ch->dma.alignment - 1)) ||
+	    (request->bytecount & (ch->dma.alignment - 1))) {
+		device_printf(request->dev,
+		    "FAILURE - non aligned DMA transfer attempted\n");
+		return EIO;
+	}
+	if (request->bytecount > ch->dma.max_iosize) {
+		device_printf(request->dev,
+		    "FAILURE - oversized DMA transfer attempt %d > %d\n",
+		    request->bytecount, ch->dma.max_iosize);
+		return EIO;
+	}
+
+	request->dma = &ch->dma.slot[atadev->unit];
+
+	if ((error = bus_dmamap_load(request->dma->data_tag, 
+	    request->dma->data_map, request->data, request->bytecount,
+	    &ata_dbdma_setprd, &args, BUS_DMA_NOWAIT))) {
+		device_printf(request->dev, "FAILURE - load data\n");
+		goto error;
+	}
+
+	if (entries)
+		*entries = args.nsegs;
+
+	bus_dmamap_sync(request->dma->sg_tag, request->dma->sg_map,
+	    BUS_DMASYNC_PREWRITE);
+	bus_dmamap_sync(request->dma->data_tag, request->dma->data_map,
+	    (request->flags & ATA_R_READ) ?
+	    BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE);
+
+	return 0;
+
+error:
+	ch->dma.unload(request);
+	return EIO;
+}
+
+void
+ata_dbdma_dmainit(device_t dev)
+{
+	struct ata_dbdma_channel *sc = device_get_softc(dev);
+	int error;
+
+	error = dbdma_allocate_channel(sc->dbdma_regs, sc->dbdma_offset,
+	    bus_get_dma_tag(dev), 256, &sc->dbdma);
+
+	dbdma_set_wait_selector(sc->dbdma,1 << 7, 1 << 7);
+
+	dbdma_insert_stop(sc->dbdma,0);
+	sc->next_dma_slot=1;
+
+	ata_dmainit(dev);
+	sc->sc_ch.dma.start = ata_dbdma_start;
+	sc->sc_ch.dma.stop = ata_dbdma_stop;
+	sc->sc_ch.dma.load = ata_dbdma_load;
+	sc->sc_ch.dma.reset = ata_dbdma_reset;
+
+	sc->sc_ch.hw.status = ata_dbdma_status;
+
+	mtx_init(&sc->dbdma_mtx, "ATA DBDMA", NULL, MTX_DEF);
+}
+
diff --git a/sys/powerpc/powermac/ata_dbdma.h b/sys/powerpc/powermac/ata_dbdma.h
new file mode 100644
index 000000000000..61d068776359
--- /dev/null
+++ b/sys/powerpc/powermac/ata_dbdma.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright 2008 by Nathan Whitehorn. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef ATA_DBDMA_H
+#define ATA_DBDMA_H
+
+#include <sys/param.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+
+#include <machine/dbdma.h>
+
+struct ata_dbdma_channel {
+	struct ata_channel	sc_ch;
+
+	int			dbdma_rid;
+	struct resource		*dbdma_regs;
+	u_int			dbdma_offset;
+
+	dbdma_channel_t		*dbdma;
+	int			next_dma_slot;
+
+	struct mtx		dbdma_mtx;
+};
+
+void ata_dbdma_dmainit(device_t dev);
+
+#endif /* ATA_DBDMA_H */
+
diff --git a/sys/powerpc/powermac/ata_kauai.c b/sys/powerpc/powermac/ata_kauai.c
index 624f142b0f8c..058225196a81 100644
--- a/sys/powerpc/powermac/ata_kauai.c
+++ b/sys/powerpc/powermac/ata_kauai.c
@@ -50,11 +50,16 @@ __FBSDID("$FreeBSD$");
 #include <ata_if.h>
 
 #include <dev/ofw/openfirm.h>
+#include <powerpc/ofw/ofw_pci.h>
+#include <machine/intr_machdep.h>
 
 #include <dev/pci/pcivar.h>
 #include <dev/pci/pcireg.h>
 
+#include "ata_dbdma.h"
+
 #define  ATA_KAUAI_REGOFFSET	0x2000
+#define  ATA_KAUAI_DBDMAOFFSET	0x1000
 
 /*
  * Offset to alt-control register from base
@@ -66,16 +71,27 @@ __FBSDID("$FreeBSD$");
  */
 #define ATA_KAUAI_REGGAP        16
 
+/*
+ * PIO and DMA access registers
+ */
+#define PIO_CONFIG_REG	(ATA_KAUAI_REGOFFSET + 0x200)
+#define UDMA_CONFIG_REG	(ATA_KAUAI_REGOFFSET + 0x210)
+#define DMA_IRQ_REG	(ATA_KAUAI_REGOFFSET + 0x300)
+
+#define USE_DBDMA_IRQ	0
+
 /*
  * Define the kauai pci bus attachment.
  */
 static  int  ata_kauai_probe(device_t dev);
+static  int  ata_kauai_attach(device_t dev);
 static  void ata_kauai_setmode(device_t parent, device_t dev);
+static  int  ata_kauai_begin_transaction(struct ata_request *request);
 
 static device_method_t ata_kauai_methods[] = {
         /* Device interface */
 	DEVMETHOD(device_probe,		ata_kauai_probe),
-	DEVMETHOD(device_attach,	ata_attach),
+	DEVMETHOD(device_attach,	ata_kauai_attach),
 	DEVMETHOD(device_detach,	bus_generic_detach),
 	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
 	DEVMETHOD(device_suspend,	bus_generic_suspend),
@@ -86,10 +102,22 @@ static device_method_t ata_kauai_methods[] = {
 	{ 0, 0 }
 };
 
+struct ata_kauai_softc {
+	struct ata_dbdma_channel sc_ch;
+
+	struct resource *sc_memr;
+
+	int shasta;
+
+	uint32_t udmaconf[2];
+	uint32_t wdmaconf[2];
+	uint32_t pioconf[2];
+};
+
 static driver_t ata_kauai_driver = {
 	"ata",
 	ata_kauai_methods,
-	sizeof(struct ata_channel),
+	sizeof(struct ata_kauai_softc),
 };
 
 DRIVER_MODULE(ata, pci, ata_kauai_driver, ata_devclass, 0, 0);
@@ -105,17 +133,71 @@ static struct kauai_pci_dev {
         { 0x0033106b, "Uninorth2 Kauai ATA Controller" },
         { 0x003b106b, "Intrepid Kauai ATA Controller" },
         { 0x0043106b, "K2 Kauai ATA Controller" },
+        { 0x0050106b, "Shasta Kauai ATA Controller" },
         { 0x0069106b, "Intrepid-2 Kauai ATA Controller" },
         { 0, NULL }
 };
 
+/*
+ * IDE transfer timings
+ */
+#define KAUAI_PIO_MASK	0xff000fff
+#define KAUAI_DMA_MASK	0x00fff000
+#define KAUAI_UDMA_MASK	0x0000ffff
+
+static const u_int pio_timing_kauai[] = {
+	0x08000a92,	/* PIO0 */
+	0x0800060f,	/* PIO1 */
+	0x0800038b,	/* PIO2 */
+	0x05000249,	/* PIO3 */
+	0x04000148	/* PIO4 */
+};
+static const u_int pio_timing_shasta[] = {
+	0x0a000c97,	/* PIO0 */
+	0x07000712,	/* PIO1 */
+	0x040003cd,	/* PIO2 */
+	0x0400028b,	/* PIO3 */
+	0x0400010a	/* PIO4 */
+};
+
+static const u_int dma_timing_kauai[] = {
+        0x00618000,	/* WDMA0 */
+        0x00209000,	/* WDMA1 */
+        0x00148000	/* WDMA2 */
+};
+static const u_int dma_timing_shasta[] = {
+        0x00820800,	/* WDMA0 */
+        0x0028b000,	/* WDMA1 */
+        0x001ca000	/* WDMA2 */
+};
+
+static const u_int udma_timing_kauai[] = {
+        0x000070c1,	/* UDMA0 */
+        0x00005d81,	/* UDMA1 */
+        0x00004a61,	/* UDMA2 */
+        0x00003a51,	/* UDMA3 */
+        0x00002a31,	/* UDMA4 */
+        0x00002921	/* UDMA5 */
+};
+static const u_int udma_timing_shasta[] = {
+        0x00035901,	/* UDMA0 */
+        0x000348b1,	/* UDMA1 */
+        0x00033881,	/* UDMA2 */
+        0x00033861,	/* UDMA3 */
+        0x00033841,	/* UDMA4 */
+        0x00033031,	/* UDMA5 */
+        0x00033021	/* UDMA6 */
+};
+
 static int
 ata_kauai_probe(device_t dev)
 {
 	struct ata_channel *ch;
-	struct resource *mem;
+	struct ata_kauai_softc *sc;
 	u_long startp, countp;
 	u_int32_t devid;
+	phandle_t node;
+	char *compatstring = NULL;
 	int i, found, rid, status;
 
 	found = 0;
@@ -130,6 +212,18 @@ ata_kauai_probe(device_t dev)
 	if (!found)
 		return (ENXIO);
 
+	node = ofw_pci_find_node(dev);
+	sc = device_get_softc(dev);
+	bzero(sc, sizeof(struct ata_kauai_softc));
+	ch = &sc->sc_ch.sc_ch;
+
+	OF_getprop_alloc(node, "compatible", 1, (void **)&compatstring);
+	if (strcmp(compatstring,"shasta-ata") == 0)
+		sc->shasta = 1;
+
+	free(compatstring, M_OFWPROP);
+
+
 	/*
 	 * This device seems to ignore writes to the interrupt
 	 * config register, resulting in interrupt resources
@@ -140,33 +234,42 @@ ata_kauai_probe(device_t dev)
 	 */
 	status = bus_get_resource(dev, SYS_RES_IRQ, 0, &startp, &countp);
 	if (status == ENOENT) {
-		int irq;
+		int *irq;
+		phandle_t iparent;
+		int icells, nintr, i;
 
 		/*
-		 * Aargh, hideous hack until ofw pci intr routine is
-		 * exported
+		 * Horrible hack to handle Kauai devices that have their IRQs
+		 * set up in an utterly wrong way
 		 */
-		irq = 39; /* XXX */
-		bus_set_resource(dev, SYS_RES_IRQ, 0, irq, 1);
+		if (!sc->shasta)
+			bus_set_resource(dev, SYS_RES_IRQ, 0, 39, 1);
 
 		/*
-		 * Sanity check...
+		 * For the rest of the interrupts, and the main Shasta
+		 * interrupt, get the IRQs from firmware.
 		 */
-		status = bus_get_resource(dev, SYS_RES_IRQ, 0, &startp,
-		    &countp);
-		if (status == ENOENT ||
-		    startp != 39) {
-			printf("kauai irq not set!\n");
-			return (ENXIO);
+		if (OF_getprop(node, "interrupt-parent", &iparent, 
+		    sizeof(iparent)) == sizeof(iparent)) {
+			OF_getprop(iparent, "#interrupt-cells", &icells, 
+			    sizeof(icells)) ;
 		}
+
+		nintr = OF_getprop_alloc(node, "interrupts", sizeof(*irq),
+		    (void **)&irq);
+
+		for (i = 0; i < nintr; i += icells)
+			bus_set_resource(dev, SYS_RES_IRQ, 
+			    i/icells + !sc->shasta, irq[i], 1);
+
+		free(irq, M_OFWPROP);
 	}
 
-	ch = device_get_softc(dev);
-	bzero(ch, sizeof(struct ata_channel));
 
         rid = PCIR_BARS;
-	mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
-        if (mem == NULL) {
+	sc->sc_memr = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 
+	    RF_ACTIVE);
+        if (sc->sc_memr == NULL) {
                 device_printf(dev, "could not allocate memory\n");
                 return (ENXIO);
         }
@@ -175,10 +278,10 @@ ata_kauai_probe(device_t dev)
 	 * Set up the resource vectors
 	 */
         for (i = ATA_DATA; i <= ATA_COMMAND; i++) {
-                ch->r_io[i].res = mem;
+                ch->r_io[i].res = sc->sc_memr;
                 ch->r_io[i].offset = i*ATA_KAUAI_REGGAP + ATA_KAUAI_REGOFFSET;
         }
-        ch->r_io[ATA_CONTROL].res = mem;
+        ch->r_io[ATA_CONTROL].res = sc->sc_memr;
         ch->r_io[ATA_CONTROL].offset = ATA_KAUAI_ALTOFFSET;
 	ata_default_registers(dev);
 
@@ -189,11 +292,129 @@ ata_kauai_probe(device_t dev)
         return (ata_probe(dev));
 }
 
+#if USE_DBDMA_IRQ
+static int
+ata_kauai_dma_interrupt(struct ata_kauai_softc *sc)
+{
+	/* Clear the DMA interrupt bits */
+
+	bus_write_4(sc->sc_memr, DMA_IRQ_REG, 0x80000000);
+
+	return ata_interrupt(sc);
+}
+#endif
+
+static int
+ata_kauai_attach(device_t dev)
+{
+	struct ata_kauai_softc *sc = device_get_softc(dev);
+#if USE_DBDMA_IRQ
+	int dbdma_irq_rid = 1;
+	struct resource *dbdma_irq;
+	void *cookie;
+#endif
+
+	pci_enable_busmaster(dev);
+
+	/* Init DMA engine */
+
+	sc->sc_ch.dbdma_rid = 1;
+	sc->sc_ch.dbdma_regs = sc->sc_memr;
+	sc->sc_ch.dbdma_offset = ATA_KAUAI_DBDMAOFFSET;
+
+	ata_dbdma_dmainit(dev);
+
+#if USE_DBDMA_IRQ
+	/* Bind to DBDMA interrupt as well */
+	if ((dbdma_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ,
+	    &dbdma_irq_rid, RF_SHAREABLE | RF_ACTIVE)) != NULL) {
+		bus_setup_intr(dev, dbdma_irq, ATA_INTR_FLAGS, NULL,
+		    (driver_intr_t *)ata_kauai_dma_interrupt, sc,&cookie);
+	}
+#endif
+
+	/* Set up initial mode */
+	if (sc->shasta)
+		sc->pioconf[0] = sc->pioconf[1] = pio_timing_shasta[4];
+	else 
+		sc->pioconf[0] = sc->pioconf[1] = pio_timing_kauai[4];
+
+	sc->udmaconf[0] = sc->udmaconf[1] = 0;
+	sc->wdmaconf[0] = sc->wdmaconf[1] = 0;
+
+	bus_write_4(sc->sc_memr, PIO_CONFIG_REG, sc->pioconf[0]);
+
+	/* Magic FCR value from Apple */
+	bus_write_4(sc->sc_memr, 0, 0x00000007);
+
+	/* Set begin_transaction */
+	sc->sc_ch.sc_ch.hw.begin_transaction = ata_kauai_begin_transaction;
+
+	return ata_attach(dev);
+}
+
 static void
 ata_kauai_setmode(device_t parent, device_t dev)
 {
 	struct ata_device *atadev = device_get_softc(dev);
+	struct ata_kauai_softc *sc = device_get_softc(parent);
+	uint32_t mode;
 
-	/* TODO bang kauai speed register */
-	atadev->mode = ATA_PIO;
+	mode = ata_limit_mode(dev,atadev->mode, 
+	    (sc->shasta) ? ATA_UDMA6 : ATA_UDMA5);
+
+	if (ata_controlcmd(dev, ATA_SETFEATURES, ATA_SF_SETXFER, 0, mode))
+		return;
+
+	atadev->mode = mode;
+
+	if (sc->shasta) {
+		switch (mode & ATA_DMA_MASK) {
+		    case ATA_UDMA0:
+			sc->udmaconf[atadev->unit] 
+			    = udma_timing_shasta[mode & ATA_MODE_MASK];
+			break;
+		    case ATA_WDMA0:
+			sc->udmaconf[atadev->unit] = 0;
+			sc->wdmaconf[atadev->unit] 
+			    = dma_timing_shasta[mode & ATA_MODE_MASK];
+			break;
+		    default:
+			sc->pioconf[atadev->unit] 
+			    = pio_timing_shasta[(mode & ATA_MODE_MASK) - 
+			    ATA_PIO0];
+			break;
+		}
+	} else {
+		switch (mode & ATA_DMA_MASK) {
+		    case ATA_UDMA0:
+			sc->udmaconf[atadev->unit] 
+			    = udma_timing_kauai[mode & ATA_MODE_MASK];
+			break;
+		    case ATA_WDMA0:
+			sc->udmaconf[atadev->unit] = 0;
+			sc->wdmaconf[atadev->unit]
+			    = dma_timing_kauai[mode & ATA_MODE_MASK];
+			break;
+		    default:
+			sc->pioconf[atadev->unit] 
+			    = pio_timing_kauai[(mode & ATA_MODE_MASK)
+			    - ATA_PIO0];
+			break;
+		}
+	}
 }
+
+static int
+ata_kauai_begin_transaction(struct ata_request *request)
+{
+	struct ata_device *atadev = device_get_softc(request->dev);
+	struct ata_kauai_softc *sc = device_get_softc(request->parent);
+
+	bus_write_4(sc->sc_memr, UDMA_CONFIG_REG, sc->udmaconf[atadev->unit]);
+	bus_write_4(sc->sc_memr, PIO_CONFIG_REG, 
+	    sc->wdmaconf[atadev->unit] | sc->pioconf[atadev->unit]);
+
+	return ata_begin_transaction(request);
+}
+
diff --git a/sys/powerpc/powermac/ata_macio.c b/sys/powerpc/powermac/ata_macio.c
index f582a5ceef88..5d1e211ffb90 100644
--- a/sys/powerpc/powermac/ata_macio.c
+++ b/sys/powerpc/powermac/ata_macio.c
@@ -50,6 +50,8 @@
 
 #include <dev/ofw/ofw_bus.h>
 
+#include "ata_dbdma.h"
+
 /*
  * Offset to control registers from base
  */
@@ -60,26 +62,85 @@
  */
 #define ATA_MACIO_REGGAP	16
 
+/*
+ * Whether or not to bind to the DBDMA IRQ
+ */
+#define USE_DBDMA_IRQ		0
+
+/*
+ * Timing register
+ */
+#define ATA_MACIO_TIMINGREG	0x200
+
+#define ATA_TIME_TO_TICK(rev,time) howmany(time, (rev == 4) ? 15 : 30)
+#define PIO_REC_OFFSET 4
+#define PIO_REC_MIN 1
+#define PIO_ACT_MIN 1
+#define DMA_REC_OFFSET 1
+#define DMA_REC_MIN 1
+#define DMA_ACT_MIN 1
+
+struct ide_timings {
+	int cycle;      /* minimum cycle time [ns] */
+	int active;     /* minimum command active time [ns] */
+};
+
+struct ide_timings pio_timings[5] = {
+	{ 600, 180 },	/* PIO 0 */
+	{ 390, 150 },	/* PIO 1 */
+	{ 240, 105 },	/* PIO 2 */
+	{ 180,  90 },	/* PIO 3 */
+	{ 120,  75 }	/* PIO 4 */
+};
+
+static const struct ide_timings dma_timings[3] = {
+	{ 480, 240 },	/* WDMA 0 */
+	{ 165,  90 },	/* WDMA 1 */
+	{ 120,  75 }	/* WDMA 2 */
+};
+
+static const struct ide_timings udma_timings[5] = {
+        { 120, 180 },	/* UDMA 0 */
+        {  90, 150 },	/* UDMA 1 */
+        {  60, 120 },	/* UDMA 2 */
+        {  45,  90 },	/* UDMA 3 */
+        {  30,  90 }	/* UDMA 4 */
+};
+
 /*
  * Define the macio ata bus attachment.
  */
 static  int  ata_macio_probe(device_t dev);
 static  void ata_macio_setmode(device_t parent, device_t dev);
+static  int  ata_macio_attach(device_t dev);
+static  int  ata_macio_begin_transaction(struct ata_request *request);
 
 static device_method_t ata_macio_methods[] = {
         /* Device interface */
 	DEVMETHOD(device_probe,		ata_macio_probe),
-	DEVMETHOD(device_attach,        ata_attach),
+	DEVMETHOD(device_attach,        ata_macio_attach),
 
 	/* ATA interface */
 	DEVMETHOD(ata_setmode,		ata_macio_setmode),
 	{ 0, 0 }
 };
 
+struct ata_macio_softc {
+	struct ata_dbdma_channel sc_ch;
+
+	int rev;
+	int max_mode;
+	struct resource *sc_mem;
+
+	uint32_t udmaconf[2];
+	uint32_t wdmaconf[2];
+	uint32_t pioconf[2];
+};
+
 static driver_t ata_macio_driver = {
 	"ata",
 	ata_macio_methods,
-	sizeof(struct ata_channel),
+	sizeof(struct ata_macio_softc),
 };
 
 DRIVER_MODULE(ata, macio, ata_macio_driver, ata_devclass, 0, 0);
@@ -89,20 +150,31 @@ static int
 ata_macio_probe(device_t dev)
 {
 	const char *type = ofw_bus_get_type(dev);
+	const char *name = ofw_bus_get_name(dev);
+	struct ata_macio_softc *sc;
 	struct ata_channel *ch;
-	struct resource *mem;
 	int rid, i;
 
 	if (strcmp(type, "ata") != 0 &&
 	    strcmp(type, "ide") != 0)
 		return (ENXIO);
 
-	ch = device_get_softc(dev);
-	bzero(ch, sizeof(struct ata_channel));
+	sc = device_get_softc(dev);
+	bzero(sc, sizeof(struct ata_macio_softc));
+	ch = &sc->sc_ch.sc_ch;
+
+	if (strcmp(name,"ata-4") == 0) {
+		sc->rev = 4;
+		sc->max_mode = ATA_UDMA4;
+	} else {
+		sc->rev = 3;
+		sc->max_mode = ATA_WDMA2;
+	}
 
 	rid = 0;
-	mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
-	if (mem == NULL) {
+	sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, 
+	    RF_ACTIVE);
+	if (sc->sc_mem == NULL) {
 		device_printf(dev, "could not allocate memory\n");
 		return (ENXIO);
 	}
@@ -111,10 +183,10 @@ ata_macio_probe(device_t dev)
 	 * Set up the resource vectors
 	 */
 	for (i = ATA_DATA; i <= ATA_COMMAND; i++) {
-		ch->r_io[i].res = mem;
+		ch->r_io[i].res = sc->sc_mem;
 		ch->r_io[i].offset = i * ATA_MACIO_REGGAP;
 	}
-	ch->r_io[ATA_CONTROL].res = mem;
+	ch->r_io[ATA_CONTROL].res = sc->sc_mem;
 	ch->r_io[ATA_CONTROL].offset = ATA_MACIO_ALTOFFSET;
 	ata_default_registers(dev);
 
@@ -125,12 +197,143 @@ ata_macio_probe(device_t dev)
 	return (ata_probe(dev));
 }
 
+static int
+ata_macio_attach(device_t dev)
+{
+	struct ata_macio_softc *sc = device_get_softc(dev);
+	uint32_t timingreg;
+
+#if USE_DBDMA_IRQ
+	int dbdma_irq_rid = 1;
+	struct resource *dbdma_irq;
+	void *cookie;
+#endif
+
+	/* Init DMA engine */
+
+	sc->sc_ch.dbdma_rid = 1;
+	sc->sc_ch.dbdma_regs = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 
+	    &sc->sc_ch.dbdma_rid, RF_ACTIVE); 
+
+	ata_dbdma_dmainit(dev);
+
+	/* Configure initial timings */
+	timingreg = bus_read_4(sc->sc_mem, ATA_MACIO_TIMINGREG);
+	if (sc->rev == 4) {
+		sc->udmaconf[0] = sc->udmaconf[1] = timingreg & 0x1ff00000;
+		sc->wdmaconf[0] = sc->wdmaconf[1] = timingreg & 0x001ffc00;
+		sc->pioconf[0]  = sc->pioconf[1]  = timingreg & 0x000003ff;
+	} else {
+		sc->udmaconf[0] = sc->udmaconf[1] = 0;
+		sc->wdmaconf[0] = sc->wdmaconf[1] = timingreg & 0xfffff800;
+		sc->pioconf[0]  = sc->pioconf[1]  = timingreg & 0x000007ff;
+	}
+
+#if USE_DBDMA_IRQ
+	/* Bind to DBDMA interrupt as well */
+
+	if ((dbdma_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ,
+	    &dbdma_irq_rid, RF_SHAREABLE | RF_ACTIVE)) != NULL) {
+		bus_setup_intr(dev, dbdma_irq, ATA_INTR_FLAGS, NULL,
+			(driver_intr_t *)ata_interrupt, sc,&cookie);
+	} 
+#endif
+
+	/* Set begin_transaction */
+	sc->sc_ch.sc_ch.hw.begin_transaction = ata_macio_begin_transaction;
+
+	return ata_attach(dev);
+}
+
 static void
 ata_macio_setmode(device_t parent, device_t dev)
 {
 	struct ata_device *atadev = device_get_softc(dev);
+	struct ata_macio_softc *sc = device_get_softc(parent);
+	int mode = atadev->mode;
 
-	/* TODO bang macio speed register */
-	atadev->mode = ATA_PIO;
+	int min_cycle = 0, min_active = 0;
+        int cycle_tick = 0, act_tick = 0, inact_tick = 0, half_tick;
+
+	mode = ata_limit_mode(dev, mode, sc->max_mode);
+
+	if (ata_controlcmd(dev, ATA_SETFEATURES, ATA_SF_SETXFER, 0, mode))
+		return;
+
+	atadev->mode = mode;
+
+	if ((mode & ATA_DMA_MASK) == ATA_UDMA0) {
+		min_cycle = udma_timings[mode & ATA_MODE_MASK].cycle;
+		min_active = udma_timings[mode & ATA_MODE_MASK].active;
+	
+		cycle_tick = ATA_TIME_TO_TICK(sc->rev,min_cycle);
+		act_tick = ATA_TIME_TO_TICK(sc->rev,min_active);
+
+		/* mask: 0x1ff00000 */
+		sc->udmaconf[atadev->unit] =
+		    (cycle_tick << 21) | (act_tick << 25) | 0x100000;
+	} else if ((mode & ATA_DMA_MASK) == ATA_WDMA0) {
+		min_cycle = dma_timings[mode & ATA_MODE_MASK].cycle;
+		min_active = dma_timings[mode & ATA_MODE_MASK].active;
+	
+		cycle_tick = ATA_TIME_TO_TICK(sc->rev,min_cycle);
+		act_tick = ATA_TIME_TO_TICK(sc->rev,min_active);
+
+		if (sc->rev == 4) {
+			inact_tick = cycle_tick - act_tick;
+			/* mask: 0x001ffc00 */
+			sc->wdmaconf[atadev->unit] = 
+			    (act_tick << 10) | (inact_tick << 15);
+		} else {
+			inact_tick = cycle_tick - act_tick - DMA_REC_OFFSET;
+			if (inact_tick < DMA_REC_MIN)
+				inact_tick = DMA_REC_MIN;
+			half_tick = 0;  /* XXX */
+
+			/* mask: 0xfffff800 */
+			sc->wdmaconf[atadev->unit] = (half_tick << 21) 
+			    | (inact_tick << 16) | (act_tick << 11);
+		}
+	} else {
+		min_cycle = 
+		    pio_timings[(mode & ATA_MODE_MASK) - ATA_PIO0].cycle;
+		min_active = 
+		    pio_timings[(mode & ATA_MODE_MASK) - ATA_PIO0].active;
+	
+		cycle_tick = ATA_TIME_TO_TICK(sc->rev,min_cycle);
+		act_tick = ATA_TIME_TO_TICK(sc->rev,min_active);
+
+		if (sc->rev == 4) {
+			inact_tick = cycle_tick - act_tick;
+
+			/* mask: 0x000003ff */
+			sc->pioconf[atadev->unit] =
+			    (inact_tick << 5) | act_tick;
+		} else {
+			if (act_tick < PIO_ACT_MIN)
+				act_tick = PIO_ACT_MIN;
+
+			inact_tick = cycle_tick - act_tick - PIO_REC_OFFSET;
+			if (inact_tick < PIO_REC_MIN)
+				inact_tick = PIO_REC_MIN;
+
+			/* mask: 0x000007ff */
+			sc->pioconf[atadev->unit] = 
+			    (inact_tick << 5) | act_tick;
+		}
+	}
+}
+
+static int
+ata_macio_begin_transaction(struct ata_request *request)
+{
+	struct ata_device *atadev = device_get_softc(request->dev);
+	struct ata_macio_softc *sc = device_get_softc(request->parent);
+
+	bus_write_4(sc->sc_mem, ATA_MACIO_TIMINGREG, 
+	    sc->udmaconf[atadev->unit] | sc->wdmaconf[atadev->unit] 
+	    | sc->pioconf[atadev->unit]); 
+
+	return ata_begin_transaction(request);
 }