root/sys/arch/bebox/stand/boot/fd.c
/*      $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));
}