#include "opt_m68k_arch.h"
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: dma.c,v 1.50 2026/03/29 14:23:45 thorpej Exp $");
#include <machine/hp300spu.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <uvm/uvm_extern.h>
#include <machine/bus.h>
#include <m68k/cacheops.h>
#include <hp300/dev/intiovar.h>
#include <hp300/dev/dmareg.h>
#include <hp300/dev/dmavar.h>
#define DMAMAXIO (MAXPHYS/PAGE_SIZE+1)
struct dma_chain {
int dc_count;
char *dc_addr;
};
struct dma_channel {
struct dmaqueue *dm_job;
struct dmadevice *dm_hwaddr;
struct dmaBdevice *dm_Bhwaddr;
char dm_flags;
u_short dm_cmd;
int dm_cur;
int dm_last;
struct dma_chain dm_chain[DMAMAXIO];
};
struct dma_softc {
device_t sc_dev;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
struct dmareg *sc_dmareg;
struct dma_channel sc_chan[NDMACHAN];
TAILQ_HEAD(, dmaqueue) sc_queue;
struct callout sc_debug_ch;
char sc_type;
int sc_ipl;
void *sc_ih;
};
#define DMA_B 0
#define DMA_C 1
#define DMAF_PCFLUSH 0x01
#define DMAF_VCFLUSH 0x02
#define DMAF_NOINTR 0x04
static int dmamatch(device_t, cfdata_t, void *);
static void dmaattach(device_t, device_t, void *);
CFATTACH_DECL_NEW(dma, sizeof(struct dma_softc),
dmamatch, dmaattach, NULL, NULL);
static int dmaintr(void *);
#ifdef DEBUG
int dmadebug = 0;
#define DDB_WORD 0x01
#define DDB_LWORD 0x02
#define DDB_FOLLOW 0x04
#define DDB_IO 0x08
static void dmatimeout(void *);
int dmatimo[NDMACHAN];
long dmahits[NDMACHAN];
long dmamisses[NDMACHAN];
long dmabyte[NDMACHAN];
long dmaword[NDMACHAN];
long dmalword[NDMACHAN];
#endif
static struct dma_softc *dma_softc;
static int
dmamatch(device_t parent, cfdata_t cf, void *aux)
{
struct intio_attach_args *ia = aux;
static int dmafound = 0;
if (strcmp("dma", ia->ia_modname) != 0 || dmafound)
return 0;
dmafound = 1;
return 1;
}
static void
dmaattach(device_t parent, device_t self, void *aux)
{
struct dma_softc *sc = device_private(self);
struct intio_attach_args *ia = aux;
struct dma_channel *dc;
struct dmareg *dma;
int i;
char rev;
sc->sc_dev = self;
dma_softc = sc;
sc->sc_bst = ia->ia_bst;
if (bus_space_map(sc->sc_bst, ia->ia_iobase, INTIO_DEVSIZE, 0,
&sc->sc_bsh)) {
aprint_error(": can't map registers\n");
return;
}
dma = bus_space_vaddr(sc->sc_bst, sc->sc_bsh);
sc->sc_dmareg = dma;
if (hp300_bus_space_probe(sc->sc_bst, sc->sc_bsh, DMA_ID2, 1) == 0) {
rev = 'B';
#if !defined(HP320)
aprint_normal("\n");
panic("%s: DMA card requires hp320 support", __func__);
#endif
} else
rev = dma->dma_id[2];
sc->sc_type = (rev == 'B') ? DMA_B : DMA_C;
TAILQ_INIT(&sc->sc_queue);
callout_init(&sc->sc_debug_ch, 0);
for (i = 0; i < NDMACHAN; i++) {
dc = &sc->sc_chan[i];
dc->dm_job = NULL;
switch (i) {
case 0:
dc->dm_hwaddr = &dma->dma_chan0;
dc->dm_Bhwaddr = &dma->dma_Bchan0;
break;
case 1:
dc->dm_hwaddr = &dma->dma_chan1;
dc->dm_Bhwaddr = &dma->dma_Bchan1;
break;
default:
aprint_normal("\n");
panic("%s: more than 2 channels?", __func__);
}
}
#ifdef DEBUG
callout_reset(&sc->sc_debug_ch, 30 * hz, dmatimeout, sc);
#endif
aprint_normal(": 98620%c, 2 channels, %d-bit DMA\n",
rev, (rev == 'B') ? 16 : 32);
sc->sc_ih = NULL;
}
void
dmaupdateipl(int ipl)
{
struct dma_softc *sc = dma_softc;
if (sc->sc_ih != NULL && sc->sc_ipl == ipl) {
return;
}
if (sc->sc_ih != NULL) {
intr_disestablish(sc->sc_ih);
}
if ((sc->sc_ipl = ipl) == 0) {
return;
}
sc->sc_ih = intr_establish(dmaintr, sc, sc->sc_ipl, ISRPRI_BIO);
if (sc->sc_type == DMA_B && sc->sc_ipl != 3) {
aprint_error_dev(sc->sc_dev,
"WARNING: IPL set to %d on maybe-rev. A card!\n",
sc->sc_ipl);
}
}
int
dmareq(struct dmaqueue *dq)
{
struct dma_softc *sc = dma_softc;
int i, chan, s;
#if 1
s = splhigh();
#else
s = splbio();
#endif
chan = dq->dq_chan;
for (i = NDMACHAN - 1; i >= 0; i--) {
if ((chan & (1 << i)) == 0)
continue;
if (sc->sc_chan[i].dm_job != NULL)
continue;
sc->sc_chan[i].dm_job = dq;
dq->dq_chan = i;
splx(s);
return 1;
}
TAILQ_INSERT_TAIL(&sc->sc_queue, dq, dq_list);
splx(s);
return 0;
}
void
dmafree(struct dmaqueue *dq)
{
int unit = dq->dq_chan;
struct dma_softc *sc = dma_softc;
struct dma_channel *dc = &sc->sc_chan[unit];
struct dmaqueue *dn;
int chan, s;
#if 1
s = splhigh();
#else
s = splbio();
#endif
#ifdef DEBUG
dmatimo[unit] = 0;
#endif
DMA_CLEAR(dc);
#if defined(M68K_EC_PAC) || defined(M68040)
if (dc->dm_flags & DMAF_PCFLUSH) {
PCIA();
dc->dm_flags &= ~DMAF_PCFLUSH;
}
#endif
#if defined(M68K_EC_VAC)
if (dc->dm_flags & DMAF_VCFLUSH) {
DCIS();
dc->dm_flags &= ~DMAF_VCFLUSH;
}
#endif
dc->dm_job = NULL;
chan = 1 << unit;
for (dn = TAILQ_FIRST(&sc->sc_queue); dn != NULL;
dn = TAILQ_NEXT(dn, dq_list)) {
if (dn->dq_chan & chan) {
TAILQ_REMOVE(&sc->sc_queue, dn, dq_list);
dc->dm_job = dn;
dn->dq_chan = dq->dq_chan;
splx(s);
(*dn->dq_start)(dn->dq_softc);
return;
}
}
splx(s);
}
void
dmago(int unit, char *addr, int count, int flags)
{
struct dma_softc *sc = dma_softc;
struct dma_channel *dc = &sc->sc_chan[unit];
char *dmaend = NULL;
int seg, tcount;
if (count > MAXPHYS)
panic("dmago: count > MAXPHYS");
#if defined(HP320)
if (sc->sc_type == DMA_B && (flags & DMAGO_LWORD))
panic("dmago: no can do 32-bit DMA");
#endif
#ifdef DEBUG
if (dmadebug & DDB_FOLLOW)
printf("dmago(%d, %p, %x, %x)\n",
unit, addr, count, flags);
if (flags & DMAGO_LWORD)
dmalword[unit]++;
else if (flags & DMAGO_WORD)
dmaword[unit]++;
else
dmabyte[unit]++;
#endif
for (seg = 0; count > 0; seg++) {
dc->dm_chain[seg].dc_addr = (char *) kvtop(addr);
#if defined(M68040)
if (cputype == CPU_68040)
DCFP((paddr_t)dc->dm_chain[seg].dc_addr);
#endif
if (count < (tcount = PAGE_SIZE - ((int)addr & PGOFSET)))
tcount = count;
dc->dm_chain[seg].dc_count = tcount;
addr += tcount;
count -= tcount;
if (flags & DMAGO_LWORD)
tcount >>= 2;
else if (flags & DMAGO_WORD)
tcount >>= 1;
if (dc->dm_chain[seg].dc_addr == dmaend
#if defined(HP320)
&& (sc->sc_type != DMA_B ||
dc->dm_chain[seg - 1].dc_count + tcount <= 65536)
#endif
) {
#ifdef DEBUG
dmahits[unit]++;
#endif
dmaend += dc->dm_chain[seg].dc_count;
dc->dm_chain[--seg].dc_count += tcount;
} else {
#ifdef DEBUG
dmamisses[unit]++;
#endif
dmaend = dc->dm_chain[seg].dc_addr +
dc->dm_chain[seg].dc_count;
dc->dm_chain[seg].dc_count = tcount;
}
}
dc->dm_cur = 0;
dc->dm_last = --seg;
dc->dm_flags = 0;
dc->dm_cmd = DMA_ENAB | DMA_IPL(sc->sc_ipl) | DMA_START;
if ((flags & DMAGO_READ) == 0)
dc->dm_cmd |= DMA_WRT;
if (flags & DMAGO_LWORD)
dc->dm_cmd |= DMA_LWORD;
else if (flags & DMAGO_WORD)
dc->dm_cmd |= DMA_WORD;
if (flags & DMAGO_PRI)
dc->dm_cmd |= DMA_PRI;
#if defined(M68040)
if (cputype == CPU_68040 && (flags & DMAGO_READ))
dc->dm_flags |= DMAF_PCFLUSH;
#endif
#if defined(M68K_EC_PAC)
if (ectype == EC_PHYS && (flags & DMAGO_READ))
dc->dm_flags |= DMAF_PCFLUSH;
#endif
#if defined(M68K_EC_VAC)
if (ectype == EC_VIRT && (flags & DMAGO_READ))
dc->dm_flags |= DMAF_VCFLUSH;
#endif
if (flags & DMAGO_NOINT) {
if (dc->dm_cur == dc->dm_last)
dc->dm_cmd &= ~DMA_ENAB;
else
dc->dm_flags |= DMAF_NOINTR;
}
#ifdef DEBUG
if (dmadebug & DDB_IO) {
if (((dmadebug&DDB_WORD) && (dc->dm_cmd&DMA_WORD)) ||
((dmadebug&DDB_LWORD) && (dc->dm_cmd&DMA_LWORD))) {
printf("dmago: cmd %x, flags %x\n",
dc->dm_cmd, dc->dm_flags);
for (seg = 0; seg <= dc->dm_last; seg++)
printf(" %d: %d@%p\n", seg,
dc->dm_chain[seg].dc_count,
dc->dm_chain[seg].dc_addr);
}
}
dmatimo[unit] = 1;
#endif
DMA_ARM(sc, dc);
}
void
dmastop(int unit)
{
struct dma_softc *sc = dma_softc;
struct dma_channel *dc = &sc->sc_chan[unit];
#ifdef DEBUG
if (dmadebug & DDB_FOLLOW)
printf("dmastop(%d)\n", unit);
dmatimo[unit] = 0;
#endif
DMA_CLEAR(dc);
#if defined(M68K_EC_PAC) || defined(M68040)
if (dc->dm_flags & DMAF_PCFLUSH) {
PCIA();
dc->dm_flags &= ~DMAF_PCFLUSH;
}
#endif
#if defined(M68K_EC_VAC)
if (dc->dm_flags & DMAF_VCFLUSH) {
DCIS();
dc->dm_flags &= ~DMAF_VCFLUSH;
}
#endif
if (dc->dm_job != NULL)
(*dc->dm_job->dq_done)(dc->dm_job->dq_softc);
}
static int
dmaintr(void *arg)
{
struct dma_softc *sc = arg;
struct dma_channel *dc;
int i, stat;
int found = 0;
#ifdef DEBUG
if (dmadebug & DDB_FOLLOW)
printf("dmaintr\n");
#endif
for (i = 0; i < NDMACHAN; i++) {
dc = &sc->sc_chan[i];
stat = DMA_STAT(dc);
if ((stat & DMA_INTR) == 0)
continue;
found++;
#ifdef DEBUG
if (dmadebug & DDB_IO) {
if (((dmadebug&DDB_WORD) && (dc->dm_cmd&DMA_WORD)) ||
((dmadebug&DDB_LWORD) && (dc->dm_cmd&DMA_LWORD)))
printf("dmaintr: flags %x unit %d stat %x "
"next %d\n",
dc->dm_flags, i, stat, dc->dm_cur + 1);
}
if (stat & DMA_ARMED)
printf("dma channel %d: intr when armed\n", i);
#endif
dc->dm_cur++;
if (dc->dm_cur <= dc->dm_last) {
#ifdef DEBUG
dmatimo[i] = 1;
#endif
if (dc->dm_cur == dc->dm_last &&
(dc->dm_flags & DMAF_NOINTR))
dc->dm_cmd &= ~DMA_ENAB;
DMA_CLEAR(dc);
DMA_ARM(sc, dc);
} else
dmastop(i);
}
return found;
}
#ifdef DEBUG
static void
dmatimeout(void *arg)
{
int i, s;
struct dma_softc *sc = arg;
for (i = 0; i < NDMACHAN; i++) {
s = splbio();
if (dmatimo[i]) {
if (dmatimo[i] > 1)
printf("dma channel %d timeout #%d\n",
i, dmatimo[i]-1);
dmatimo[i]++;
}
splx(s);
}
callout_reset(&sc->sc_debug_ch, 30 * hz, dmatimeout, sc);
}
#endif