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

/*-
* Copyright (C) 1997-1998 Kazuki Sakamoto ([email protected])
* 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));
}