shithub: purgatorio

ref: 7ae7351ace73a70cbbaf5a2da016005485d0d43b
dir: /os/pxa/dma.c/

View raw version
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"
#include	"io.h"

#define	DMAREGS	((Dmaregs*)PHYSDMA)
typedef struct Dmadesc Dmadesc;
typedef struct Dmaregs Dmaregs;

struct Dmadesc {
	ulong	ddadr;	/* next descriptor address (0 mod 16) */
	ulong	dsadr;	/* source address (0 mod 8 if external, 0 mod 4 internal) */
	ulong	dtadr;	/* target address (same) */
	ulong	dcmd;	/* command */
};

struct Dmaregs {
	ulong	dcsr[16];	/* control and status */
	uchar	pad0[0xF0-0x40];
	ulong	dint;	/* mask of interrupting channels: 0 is bit 0 */
	uchar	pad1[0x100-0xF4];
	ulong	drcmr[40];
	Dmadesc	chan[16];	/* offset 0x200 */
};

enum {
	/* dcsr */
	DcsRun=	1<<31,	/* start the channel */
	DcsNodesc=	1<<30,	/* set if channel is in no-descriptor fetch mode */
	DcsStopirq=	1<<29,	/* enable interrupt if channel is uninitialised or stopped */
	DcsReqpend=	1<<8,	/* channel has pending request */
	DcsStopstate=	1<<3,	/* channel is uninitialised or stopped */
	DcsEndintr=	1<<2,	/* transaction complete, length now 0 */
	DcsStartintr=	1<<1,	/* successful descriptor fetch */
	DcsBuserr=	1<<0,	/* bus error */

	/* drcmr */
	DmrValid=	1<<7,	/* mapped to channel given by bits 0-3 */
	DmrChan=	0xF,		/* channel number mask */

	/* ddadr */
	DdaStop=	1<<1,	/* =0, run channel; =1, stop channel after this descriptor */

	/* dcmd */
	DcmIncsrc=	1<<31,	/* increment source address after use */
	DcmIncdest=	1<<30,	/* increment destination address after use */
	DcmFlowsrc=	1<<29,	/* enable flow control on source */
	DcmFlowdest=	1<<28,	/* enable flow control on target */
	DcmStartirq=	1<<22,	/* interrupt when descriptor loaded (fetch mode) */
	DcmEndirq=	1<<21,	/* interrupt when transfer complete */
	DcmEndian=	1<<18,	/* must be zero (little endian) */
	DcmBurst8=	1<<16,	/* burst size in bytes */
	DcmBurst16=	2<<16,
	DcmBurst32=	3<<16,
	DcmWidth0=	0<<14,	/* width for external memory */
	DcmWidth1=	1<<14,	/* width of on-chip peripheral */
	DcmWidth2=	2<<14,
	DcmWidth4=	3<<14,
	DcmLength=	(1<<13)-1,

	Ndma=	16,		/* number of dma channels */
	MaxDMAbytes=	8192-1,	/* annoyingly small limit */
};

struct Dma {
	int	chan;
	Dmadesc*	desc;
	Dmadesc	stop;
	ulong	*csr;
	void	(*interrupt)(void*, ulong);
	void*	arg;
	Rendez	r;
	ulong	attrs;	/* transfer attributes: flow control, burst size, width */
};

static struct {
	Lock;
	ulong	avail;
	Dma	dma[Ndma];
} dmachans;

static	void	dmaintr(Ureg*, void*);

void
dmareset(void)
{
	int i;
	Dma *d;

	for(i=0; i<Ndma; i++){
		dmachans.avail |= 1<<i;
		d = &dmachans.dma[i];
		d->chan = i;
		d->csr = &DMAREGS->dcsr[i];
		d->desc = &DMAREGS->chan[i];
		d->stop.ddadr = (ulong)&d->stop | DdaStop;
		d->stop.dcmd = 0;
	}
	intrenable(IRQ, IRQdma, dmaintr, nil, "dma");
}

/*
 * allocate a DMA channel, reset it, and configure it for the given device
 */
Dma*
dmasetup(int owner, void (*interrupt)(void*, ulong), void *arg, ulong attrs)
{
	Dma *d;
	Dmadesc *dc;
	int i;

	ilock(&dmachans);
	for(i=0; (dmachans.avail & (1<<i)) == 0; i++)
		if(i >= Ndma){
			iunlock(&dmachans);
			return nil;
		}
	dmachans.avail &= ~(1<<i);
	iunlock(&dmachans);

	d = &dmachans.dma[i];
	d->owner = owner;
	d->interrupt = interrupt;
	d->arg = arg;
	d->attrs = attrs;
	dc = d->desc;
	dc->ddadr = (ulong)&d->stop | DdaStop;	/* empty list */
	dc->dcmd = 0;
	*d->csr = DcsEndintr | DcsStartintr | DcsBuserr;	/* clear status, stopped */
	DMAREGS->drcmr[owner] = DmrValid | i;
	return d;
}

void
dmafree(Dma *dma)
{
	dmastop(dma);
	DMAREGS->drcmr[d->owner] = 0;
	ilock(&dmachans);
	dmachans.avail |= 1<<dma->chan;
	dma->interrupt = nil;
	iunlock(&dmachans);
}

/*
 * simple dma transfer on a channel, using `no fetch descriptor' mode.
 * virtual buffer addresses are assumed to refer to contiguous physical addresses.
 */
int
dmastart(Dma *dma, void *from, void *to, int nbytes)
{
	Dmadesc *dc;

	if((ulong)nbytes > MaxDMAbytes)
		panic("dmastart");
	if((*dma->csr & DcsStopstate) == 0)
		return 0;	/* busy */
	dc = dma->desc;
	dc->ddadr = DdaStop;
	dc->dsadr = PADDR(from);
	dc->dtadr = PADDR(to);
	dc->dcmd = dma->attrs | DcmEndirq | nbytes;
	*dma->csr = DcsRun | DcsNodesc | DcsEndintr | DcsStartintr | DcsBuserr;
	return 1;
}

/*
 * stop dma on a channel
 */
void
dmastop(Dma *dma)
{
	*dma->csr = 0;
	while((*dma->csr & DcsStopstate) == 0)
		;
	*dma->csr = DcsStopstate;
}

/*
 * return nonzero if there was a memory error during DMA,
 * and clear the error state
 */
int
dmaerror(Dma *dma)
{
	ulong e;

	e = *dma->csr & DcsBuserr;
	*dma->csr |= e;
	return e;
}

/*
 * return nonzero if the DMA channel is not busy
 */
int
dmaidle(Dma *d)
{
	return (*d->csr & DcsStopstate) == 0;
}

static int
dmaidlep(void *a)
{
	return dmaidle((Dma*)a);
}

void
dmawait(Dma *d)
{
	while(!dmaidle(d))
		sleep(&d->r, dmaidlep, d);
}

/*
 * this interface really only copes with one buffer at once
 */
static void
dmaintr(Ureg*, void*)
{
	Dma *d;
	Dmaregs *dr;
	int i;
	ulong s, csr;

	dr = DMAREGS;
	s = dr->dint;
	dr->dint = s;
	for(i=0; i<Ndma && s != 0; i++)
		if(s & (1<<i)){
			d = &dmachans.dma[i];
			csr = *d->csr;
			if(csr & DcsBuserr)
				iprint("DMA error, chan %d status #%8.8lux\n", d->chan, csr);
			*d->csr = csr & (DcsRun | DcsNodesc | DcsEndintr | DcsStartintr | DcsBuserr);
			if(d->interrupt != nil)
				d->interrupt(d->arg, csr);
			else
				wakeup(&d->r);
		}
}