/*	$NetBSD: fd.c,v 1.12 2022/08/07 11:06:18 andvar Exp $	*/

/*-
 * Copyright (C) 1997-1998 Kazuki Sakamoto (sakamoto@NetBSD.org)
 * All rights reserved.
 *
 * Floppy Disk Drive standalone device driver
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Kazuki Sakamoto.
 * 4. 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.
 */

#include <sys/param.h>
#include <lib/libsa/stand.h>
#include "boot.h"

/*---------------------------------------------------------------------------*
 *			Floppy Disk Controller Define			     *
 *---------------------------------------------------------------------------*/
/* Floppy Disk Controller Registers */
int FDC_PORT[] = {				/* fdc base I/O port */
		0x3f0, /* primary */
		};
#define	FDC_DOR(x)	(FDC_PORT[x] + 0x2)	/* motor drive control bits */
#define	FDC_STATUS(x)	(FDC_PORT[x] + 0x4)	/* fdc main status register */
#define	FDC_DATA(x)	(FDC_PORT[x] + 0x5)	/* fdc data register */
#define	FDC_RATE(x)	(FDC_PORT[x] + 0x7)	/* transfer rate register */

#define	FDC_IRQ		6
#define	FD_DMA_CHAN	2

/* fdc main status register */
#define	RQM	  0x80	/* the host can transfer data if set */
#define	DIO	  0x40	/* direction of data transfer. write required if set */
#define	NON_DMA   0x20  /* fdc have date for transfer in non dma mode */
#define	CMD_BUSY  0x10	/* command busy if set */

/* fdc result status */
#define	ST0_IC_MASK	0xc0	/* interrupt code  00:normal terminate */
#define	ST1_EN		0x80	/* end of cylinder */

/* fdc digtal output register */
#define	DOR_DMAEN	0x08	/* DRQ, nDACK, TC and FINTR output enable */
#define	DOR_RESET	0x04	/* fdc software reset */

/* fdc command */
#define	CMD_RECALIBRATE	0x07	/* recalibrate */
#define	CMD_SENSE_INT	0x08	/* sense interrupt status */
#define	CMD_DRV_SENSE	0x04	/* sense drive status */
#define	CMD_SEEK	0x0f	/* seek */
#define	CMD_FORMAT	0x4d	/* format */
#define	CMD_READ	0x46	/* read e6 */
#define	CMD_WRITE	0xc5	/* write */
#define	CMD_VERIFY	0xf6	/* verify */
#define	CMD_READID	0x4a	/* readID */
#define	CMD_SPECIFY	0x03	/* specify */
#define	CMD_CONFIG	0x13	/* config */
#define	CMD_VERSION	0x10	/* version */

/* command specify value */
#define	SPECIFY1	((0x0d<<4)|0x0f)
#define	SPECIFY2	((0x01<<1)|0)	/* DMA MODE */

/* fdc result */
#define	STATUS_MAX	16	/* result status max number */
#define	RESULT_VERSION	0x90	/* enhanced controller */
#define	RESULT_SEEK	0x20	/* seek & recalibrate complete flag on status0 */

/*---------------------------------------------------------------------------*
 *			     Floppy Disk Type Define	 		     *
 *---------------------------------------------------------------------------*/
struct	fdd_type {
	int	seccount;	/* sector per track */
	int	secsize;	/* byte per sector (uPD765 parameter) */
	int	datalen;	/* data length */
	int	gap;		/* gap */
	int	gaplen;		/* gap length */
	int	cylinder;	/* track per media */
	int	maxseccount;	/* media max sector count */
	int	step;		/* seek step */
	int	rate;		/* drive rate (250 or 500kbps) */
	int	heads;		/* heads */
	int	f_gap;		/* format gap */
	int	mselect;	/* drive mode select */
	char	*type_name;	/* media type name */
};
typedef struct	fdd_type FDDTYPE;

#define	FDTYPE_MAX	5
FDDTYPE fdd_types[FDTYPE_MAX] = {
	{ 18,2,0xff,0x1b,0x54,80,2880,1,0,2,0x6c,0,"2HQ" }, /* 2HD (PC/AT) */
	{  8,3,0xff,0x35,0x74,77,1232,1,0,2,0x54,1,"2HD" }, /* 2HD (98) */
	{ 15,2,0xff,0x1b,0x54,80,2400,1,0,2,0x54,1,"2HC" }, /* 2HC */
	{  9,2,0xff,0x23,0x50,80,1440,1,2,2,0x50,1,"2DD9" },/* 2DD 9 sector */
	{  8,2,0xff,0x3a,0x50,80,1280,1,2,2,0x50,1,"2DD8" },/* 2DD 8 sector */
};

int	fdsectors[] = {128, 256, 512, 1024, 2048, 4096};
#define	SECTOR_MAX	4096
#define	FDBLK	(fdsectors[un->un_type->secsize])

#define	START_CYL	0
#define	START_SECTOR	1

#define	DELAY(x)	delay(100000 * x)		/* about 100ms */
#define	INT_TIMEOUT	3000000

/*---------------------------------------------------------------------------*
 *			FDC Device Driver Define			     *
 *---------------------------------------------------------------------------*/
#define	CTLR_MAX	1
#define	UNIT_MAX	2

struct	fd_unit {
	int	ctlr;
	int	unit;
	u_int	un_flags;		/* unit status flag */
	int	stat[STATUS_MAX];	/* result code */
	FDDTYPE	*un_type;		/* floppy type (pointer) */
};
typedef	struct fd_unit FD_UNIT;
FD_UNIT	fd_unit[CTLR_MAX][UNIT_MAX];

/*
 *	un_flags flags
 */
#define	INT_ALIVE	0x00000001	/* Device is Alive and Available */
#define	INT_READY	0x00000002	/* Device is Ready */
#define	INT_BUSY	0x00000004	/* Device is busy */

/*---------------------------------------------------------------------------*
 *				Misc define				     *
 *---------------------------------------------------------------------------*/
#define	TIMEOUT		10000000
#define	ND_TIMEOUT	10000000

#define	SUCCESS		0
#define	FAIL		-1

/*
 *	function declaration
 */
int fdinit(FD_UNIT *);
int fdopen(struct open_file *, int, int);
int fdclose(struct open_file *);
int fdioctl(struct open_file *, u_long, void *);
int fdstrategy(void *, int, daddr_t, size_t, void *, size_t *);
int fdc_out(int, int);
int fdc_in(int, u_char *);
int fdc_intr_wait(void);
int fd_check(FD_UNIT *);
void motor_on(int, int);
void motor_off(int, int);
void fdReset(int);
void fdRecalibrate(int, int);
void fdSpecify(int);
void fdDriveStatus(int, int, int, int *);
int fdSeek(int, int, int);
int fdSenseInt(int, int *);
int fdReadWrite(FD_UNIT *, int, int, int, int, u_char *);
void irq_init(void);
int irq_polling(int, int);
void dma_setup(u_char *, int, int, int);
int dma_finished(int);

/*===========================================================================*
 *				   fdinit				     *
 *===========================================================================*/
int
fdinit(FD_UNIT *un)
{
	int ctlr = un->ctlr;
	u_char result;

#if 0
	irq_init();
#endif
	fdReset(ctlr);

	if (fdc_out(ctlr, CMD_VERSION) != SUCCESS) {  /* version check */
		printf ("fdc%d:fatal error: CMD_VERSION cmd fail\n", ctlr);
		return (FAIL);
	}
	if (fdc_in(ctlr, &result) != SUCCESS) {
		printf ("fdc%d:fatal error: CMD_VERSION exec fail\n", ctlr);
		return (FAIL);
	}
	if (result != (u_char)RESULT_VERSION) {
		printf ("fdc%d:fatal error: unknown version fdc\n", ctlr);
		return (FAIL);
	}

	un->un_flags = INT_ALIVE;
	return (SUCCESS);
}

/*===========================================================================*
 *				   fdopen				     *
 *===========================================================================*/
int
fdopen(struct open_file *f, int ctlr, int unit)
{
	FD_UNIT	*un;
	int *stat;

	if (ctlr >= CTLR_MAX)
		return (ENXIO);
	if (unit >= UNIT_MAX)
		return (ENXIO);
	un = &fd_unit[ctlr][unit];
	stat = un->stat;

	if (!(un->un_flags & INT_ALIVE)) {
		if (fdinit(un) != SUCCESS)
			return (ENXIO);
	}

	motor_on(ctlr, unit);

	fdRecalibrate(ctlr, unit);
	fdSenseInt(ctlr, stat);
	if (stat[1] != START_CYL) {
		printf("fdc%d: unit:%d recalibrate failed. status:0x%x cyl:%d\n",
			ctlr, unit, stat[0], stat[1]);
		motor_off(ctlr, unit);
		return (EIO);
	}

	if (fd_check(un) != SUCCESS)	/* research disk type */
		return (EIO);

	f->f_devdata = (void *)un;
	return (SUCCESS);
}

/*===========================================================================*
 *				   fdclose				     *
 *===========================================================================*/
int
fdclose(struct open_file *f)
{
	FD_UNIT *un = f->f_devdata;

	fdRecalibrate(un->ctlr, un->unit);
	fdSenseInt(un->ctlr, un->stat);
	motor_off(un->ctlr, un->unit);
	un->un_flags = 0;
	return (SUCCESS);
}

/*===========================================================================*
 *				   fdioctl				     *
 *===========================================================================*/
int
fdioctl(struct open_file *f, u_long cmd, void *arg)
{

	switch (cmd) {
	default:
		return (EIO);
	}

	return (SUCCESS);
}

/*===========================================================================*
 *				   fdstrategy				     *
 *===========================================================================*/
int
fdstrategy(void *devdata, int func, daddr_t blk, size_t size, void *buf,
	   size_t *rsize)
{
	int sectrac, cyl, head, sec;
	FD_UNIT *un = devdata;
	int ctlr = un->ctlr;
	int unit = un->unit;
	int *stat = un->stat;
	long blknum;
	int fd_skip = 0;
	u_char *cbuf = (u_char *)buf;

	if (un->un_flags & INT_BUSY) {
		return (ENXIO);
	}
	fdDriveStatus(ctlr, unit, 0, stat);

	sectrac = un->un_type->seccount;	/* sector per track */
	*rsize = 0;

	while (fd_skip < size) {
		blknum = (u_long)blk * DEV_BSIZE/FDBLK + fd_skip/FDBLK;
		cyl = blknum / (sectrac * 2);
		fdSeek(ctlr, unit, cyl);
		fdSenseInt(ctlr, stat);
		if (!(stat[0] & RESULT_SEEK)) {
			printf("fdc%d: unit:%d seek failed."
				"status:0x%x cyl:%d pcyl:%d\n",
				ctlr, unit, stat[0], cyl, stat[1]);
			goto bad;
		}

		sec = blknum % (sectrac * 2);
		head = sec / sectrac;
		sec = sec % sectrac + 1;

		if (fdReadWrite(un, func, cyl, head, sec, cbuf) == FAIL) {
			printf("fdc%d: unit%d fdReadWrite error [%s]\n",
			    ctlr, unit, (func==F_READ?"READ":"WRITE"));
			goto bad;
		}

		*rsize += FDBLK;
		cbuf += FDBLK;
		fd_skip += FDBLK;
	}
	return (SUCCESS);

bad:
	return (FAIL);
}

/*===========================================================================*
 *				   fd_check				     *
 *===========================================================================*/
/*
 *	this function is Check floppy disk Type
 */
int
fd_check(FD_UNIT *un)
{
	int ctlr = un->ctlr;
	int unit = un->unit;
	int *stat = un->stat;
	int type;
	static u_char sec_buff[SECTOR_MAX];

	un->un_type = (FDDTYPE *)FAIL;
	for (type = 0; type < FDTYPE_MAX; type++) {
		un->un_type = &fdd_types[type];

		/* try read start sector */
		outb(FDC_RATE(ctlr), un->un_type->rate);   /* rate set */
		fdSpecify(ctlr);
		fdSeek(ctlr, unit, START_CYL);
		fdSenseInt(ctlr, stat);
		if (!(stat[0] & RESULT_SEEK) || stat[1] != START_CYL) {
			printf("fdc%d: unit:%d seek failed. status:0x%x\n",
				ctlr, unit, stat[0]);
			goto bad;
		}
		if (fdReadWrite(un, F_READ,
		    START_CYL, 0, START_SECTOR, sec_buff) == FAIL) {
			continue;	/* bad disk type */
		}
		break;
	}
	if (un->un_type == (FDDTYPE *)FAIL) {
		printf("fdc%d: unit:%d check disk type failed.\n",
		ctlr, unit);
		goto bad;
	}
	return (SUCCESS);
bad:
	return (FAIL);
}

/*
 * for FDC routines.
 */
/*===========================================================================*
 *				fdc_out					     *
 *===========================================================================*/
int
fdc_out(int ctlr, int cmd)
{
	volatile int status;
	int time_out;

	time_out = TIMEOUT;
	while (((status = inb(FDC_STATUS(ctlr))) & (RQM | DIO))
		!= (RQM | 0) && time_out-- > 0);
	if (time_out <= 0) {
		printf("fdc_out: timeout  status = 0x%x\n", status);
		return (FAIL);
	}

	outb(FDC_DATA(ctlr), cmd);

	return (SUCCESS);
}

/*===========================================================================*
 *				fdc_in					     *
 *===========================================================================*/
int
fdc_in(int ctlr, u_char *data)
{
	volatile int status;
	int time_out;

	time_out = TIMEOUT;
	while ((status = inb(FDC_STATUS(ctlr)) & (RQM | DIO))
	    != (RQM | DIO) && time_out-- > 0) {
		if (status == RQM) {
			printf("fdc_in:error:ready for output\n");
			return (FAIL);
		}
	}

	if (time_out <= 0) {
		printf("fdc_in:input ready timeout\n");
		return (FAIL);
	}

	if (data) *data = (u_char)inb(FDC_DATA(ctlr));

	return (SUCCESS);
}

/*===========================================================================*
 *                              fdc_intr_wait                                *
 *===========================================================================*/
int
fdc_intr_wait(void)
{

	return (irq_polling(FDC_IRQ, INT_TIMEOUT));	/* wait interrupt */
}

/*===========================================================================*
 *		   	     fdc command function	 	    	     *
 *===========================================================================*/
void
motor_on(int ctlr, int unit)
{

	outb(FDC_DOR(ctlr), DOR_RESET | DOR_DMAEN | unit
		| (1 << (unit + 4)));	/* reset & unit motor on */
	DELAY(1);		/* wait 100msec */
}

void
motor_off(int ctlr, int unit)
{

	outb(FDC_DOR(ctlr), DOR_RESET);		/* reset & motor off */
	if (fdc_intr_wait() == FAIL)		/* wait interrupt */
		printf("fdc: motor off failed.\n");
}

void
fdReset(int ctlr)
{

	outb(FDC_DOR(ctlr), 0); /* fdc reset */
	DELAY(3);
	outb(FDC_DOR(ctlr), DOR_RESET);
	DELAY(8);
}

void
fdRecalibrate(int ctlr, int unit)
{

	fdc_out(ctlr, CMD_RECALIBRATE);
	fdc_out(ctlr, unit);

	if (fdc_intr_wait() == FAIL)   /* wait interrupt */
		printf("fdc: recalibrate Timeout\n");
}

void
fdSpecify(int ctlr)
{

	fdc_out(ctlr, CMD_SPECIFY);
	fdc_out(ctlr, SPECIFY1);
	fdc_out(ctlr, SPECIFY2);
}

void
fdDriveStatus(int ctlr, register int unit, register int head,
	      register int *stat)
{
	u_char result;

	fdc_out(ctlr, CMD_DRV_SENSE);
	fdc_out(ctlr, (head << 2) | unit);
	fdc_in(ctlr, &result);
	*stat = (int)(result & 0xff);
}

int
fdSeek(int ctlr, int unit, int cyl)
{
	int ret_val = 0;

	fdc_out(ctlr, CMD_SEEK);
	fdc_out(ctlr, unit);
	fdc_out(ctlr, cyl);

        if (fdc_intr_wait() == FAIL) {		/* wait interrupt */
		printf("fdc: fdSeek Timeout\n");
		ret_val = FAIL;
	}

	return(ret_val);
}

int
fdSenseInt(int ctlr, int *stat)
{
	u_char result;

	fdc_out(ctlr, CMD_SENSE_INT);

	fdc_in(ctlr, &result);
	*stat++ = (int)(result & 0xff);
	fdc_in(ctlr, &result);
	*stat++ = (int)(result & 0xff);

	return (0);
}

int
fdReadWrite(FD_UNIT *un, int func, int cyl, int head, int sec, u_char *adrs)
{
	int i;
	int ctlr = un->ctlr;
	int unit = un->unit;
	int *stat = un->stat;
	u_char result;

#if 0
printf("%s:", (func == F_READ ? "READ" : "WRITE"));
printf("cyl = %d", cyl);
printf("head = %d", head);
printf("sec = %d", sec);
printf("secsize = %d", un->un_type->secsize);
printf("seccount = %d", un->un_type->seccount);
printf("gap = %d", un->un_type->gap);
printf("datalen = %d\n", un->un_type->datalen);
#endif

	dma_setup(adrs, FDBLK, func, FD_DMA_CHAN);
	fdc_out(ctlr, (func == F_READ ? CMD_READ : CMD_WRITE));
	fdc_out(ctlr, (head<<2) | unit);
	fdc_out(ctlr, cyl);			/* cyl */
	fdc_out(ctlr, head);			/* head */
	fdc_out(ctlr, sec);			/* sec */
	fdc_out(ctlr, un->un_type->secsize);	/* secsize */
	fdc_out(ctlr, un->un_type->seccount);	/* EOT (end of track) */
	fdc_out(ctlr, un->un_type->gap);	/* GAP3 */
	fdc_out(ctlr, un->un_type->datalen);	/* DTL (data length) */

	if (fdc_intr_wait() == FAIL) {  /* wait interrupt */
		printf("fdc: DMA transfer Timeout\n");
		return (FAIL);
	}

	for (i = 0; i < 7; i++) {
		fdc_in(ctlr, &result);
		stat[i] = (int)(result & 0xff);
	}
	if (stat[0] & ST0_IC_MASK) {	/* not normal terminate */
		if ((stat[1] & ~ST1_EN) || stat[2])
		goto bad;
	}
	if (!dma_finished(FD_DMA_CHAN)) {
		printf("DMA not finished\n");
		goto bad;
	}
	return (SUCCESS);

bad:
	printf("       func: %s\n", (func == F_READ ? "F_READ" : "F_WRITE"));
	printf("	st0 = 0x%x\n", stat[0]);
	printf("	st1 = 0x%x\n", stat[1]);
	printf("	st2 = 0x%x\n", stat[2]);
	printf("	  c = 0x%x\n", stat[3]);
	printf("	  h = 0x%x\n", stat[4]);
	printf("	  r = 0x%x\n", stat[5]);
	printf("	  n = 0x%x\n", stat[6]);
	return (FAIL);
}

/*-----------------------------------------------------------------------
 * Interrupt Controller Operation Functions
 *-----------------------------------------------------------------------
 */

/* 8259A interrupt controller register */
#define	INT_CTL0	0x20
#define	INT_CTL1	0x21
#define	INT2_CTL0	0xA0
#define	INT2_CTL1	0xA1

#define	CASCADE_IRQ	2

#define	ICW1_AT		0x11    /* edge triggered, cascade, need ICW4 */
#define	ICW4_AT		0x01    /* not SFNM, not buffered, normal EOI, 8086 */
#define	OCW3_PL		0x0e	/* polling mode */
#define	OCW2_CLEAR	0x20	/* interrupt clear */

/*
 * IRC programing sequence
 *
 * after reset
 * 1.	ICW1 (write port:INT_CTL0 data:bit4=1)
 * 2.	ICW2 (write port:INT_CTL1)
 * 3.	ICW3 (write port:INT_CTL1)
 * 4.	ICW4 (write port:INT_CTL1)
 *
 * after ICW
 *	OCW1 (write port:INT_CTL1)
 *	OCW2 (write port:INT_CTL0 data:bit3=0,bit4=0)
 *	OCW3 (write port:INT_CTL0 data:bit3=1,bit4=0)
 *
 *	IMR  (read port:INT_CTL1)
 *	IRR  (read port:INT_CTL0)	OCW3(bit1=1,bit0=0)
 *	ISR  (read port:INT_CTL0)	OCW3(bit1=1,bit0=1)
 *	PL   (read port:INT_CTL0)	OCW3(bit2=1,bit1=1)
 */

u_int INT_MASK;
u_int INT2_MASK;

/*===========================================================================*
 *                             irq initialize                                *
 *===========================================================================*/
void
irq_init(void)
{
	outb(INT_CTL0, ICW1_AT);		/* ICW1 */
	outb(INT_CTL1, 0);			/* ICW2 for master */
	outb(INT_CTL1, (1 << CASCADE_IRQ));	/* ICW3 tells slaves */
	outb(INT_CTL1, ICW4_AT);		/* ICW4 */

	outb(INT_CTL1, (INT_MASK = ~(1 << CASCADE_IRQ)));
				/* IRQ mask(exclusive of cascade) */

	outb(INT2_CTL0, ICW1_AT);		/* ICW1 */
	outb(INT2_CTL1, 8); 			/* ICW2 for slave */
	outb(INT2_CTL1, CASCADE_IRQ);		/* ICW3 is slave nr */
	outb(INT2_CTL1, ICW4_AT);		/* ICW4 */

	outb(INT2_CTL1, (INT2_MASK = ~0));	/* IRQ 8-15 mask */
}

/*===========================================================================*
 *                           irq polling check                               *
 *===========================================================================*/
int
irq_polling(int irq_no, int timeout)
{
	int	irc_no;
	int	data;
	int	ret;

	if (irq_no > 8) irc_no = 1;
		else irc_no = 0;

	outb(irc_no ? INT2_CTL1 : INT_CTL1, ~(1 << (irq_no >> (irc_no * 3))));

	while (--timeout > 0) {
		outb(irc_no ? INT2_CTL0 : INT_CTL0, OCW3_PL);
						/* set polling mode */
		data = inb(irc_no ? INT2_CTL0 : INT_CTL0);
		if (data & 0x80) {	/* if interrupt request */
			if ((irq_no >> (irc_no * 3)) == (data & 0x7)) {
				ret = SUCCESS;
				break;
			}
		}
	}
	if (!timeout) ret = FAIL;

	if (irc_no) {				/* interrupt clear */
		outb(INT2_CTL0, OCW2_CLEAR | (irq_no >> 3));
		outb(INT_CTL0, OCW2_CLEAR | CASCADE_IRQ);
	} else {
		outb(INT_CTL0, OCW2_CLEAR | irq_no);
	}

	outb(INT_CTL1, INT_MASK);
	outb(INT2_CTL1, INT2_MASK);

	return (ret);
}

/*---------------------------------------------------------------------------*
 *			DMA Controller Define			 	     *
 *---------------------------------------------------------------------------*/
/* DMA Controller Registers */
#define	DMA_ADDR	0x004    /* port for low 16 bits of DMA address */
#define	DMA_LTOP	0x081    /* port for top low 8bit DMA addr(ch2) */
#define	DMA_HTOP	0x481    /* port for top high 8bit DMA addr(ch2) */
#define	DMA_COUNT	0x005    /* port for DMA count (count =  bytes - 1) */
#define	DMA_DEVCON	0x008    /* DMA device control register */
#define	DMA_SR		0x008    /* DMA status register */
#define	DMA_RESET	0x00D    /* DMA software reset register */
#define	DMA_FLIPFLOP	0x00C    /* DMA byte pointer flip-flop */
#define	DMA_MODE	0x00B    /* DMA mode port */
#define	DMA_INIT	0x00A    /* DMA init port */

#define	DMA_RESET_VAL	0x06
/* DMA channel commands. */
#define	DMA_READ	0x46    /* DMA read opcode */
#define	DMA_WRITE	0x4A    /* DMA write opcode */

/*===========================================================================*
 *				dma_setup				     *
 *===========================================================================*/
void
dma_setup(u_char *buf, int size, int func, int chan)
{
	u_long pbuf = local_to_PCI((u_long)buf);

#if 0
	outb(DMA_RESET, 0);
	DELAY(1);
	outb(DMA_DEVCON, 0x00);
	outb(DMA_INIT, DMA_RESET_VAL);	/* reset the dma controller */
#endif
	outb(DMA_MODE, func == F_READ ? DMA_READ : DMA_WRITE);
	outb(DMA_FLIPFLOP, 0);		/* write anything to reset it */

	outb(DMA_ADDR, (int)pbuf >>  0);
	outb(DMA_ADDR, (int)pbuf >>  8);
	outb(DMA_LTOP, (int)pbuf >> 16);
	outb(DMA_HTOP, (int)pbuf >> 24);

	outb(DMA_COUNT, (size - 1) >> 0);
	outb(DMA_COUNT, (size - 1) >> 8);
	outb(DMA_INIT, chan);		/* some sort of enable */
}

int
dma_finished(int chan)
{

	return ((inb(DMA_SR) & 0x0f) == (1 << chan));
}