/*
*   DIVAPC C source
*
*   CPU.C.DMA    DMA emulation module
*
*   Versions
*
*   29-02-91 INH  Skeleton file
*   13-03-95 DAF  DMA emulator coding (GNR: Use Your Illusion II)
*   22-03-95 DAF  Made compile silently with latest IH sources
*   23-03-95 DAF  Added indirect call routine for RO RM calls.
*   23-03-95 DAF  Change which channel cascades
*   16-05-95 INH  Re-integrated with !PC v1.77
*   19-05-95 DAF  Corrected merging problems. Removed most debugging.
*   07-06-95 DAF  Attending to 16 bit DMA problems
*   10-06-95 DAF  Corrected 16 bit address calculation problems
*   13-06-95 DAF  Made undefined LS612 references not blat unknown
*		  memory!
*   14-06-95 DAF  Removed the wr8 and rd8 messages unless DEBUGing.
*/


/*

Questions and notes

*) 16 bit dma transfer routines must return a transfer length that
   is a multiple of 2. 08/6/95

*) The notify function is a single call given an address and a length.
   This can actually imply two tranfers with the appropriate cut in
   the carry between the 2nd and 3rd bytes (to reflect the LS612 value
   not being carried into). It is up to the registered notify function
   to deduce any implied wrap around. There can never be more one
   additional implied region for each notify call.

*) How often should the ls612 latches be read? na37 data sheet
   suggests whenever the address used for memory generates a carry
   from A7 to A8 (ie out of 1 LWB). Once the (*transfer) call.

*) How much emulation for the cascade channel? Do I refuse to use
   the 2nd controller if there isn't a channel of the 1st controller
   set into cascade mode? Presumably which channel gets used is
   pretty fixed as it corresponds to actual PCB wiring. Does the
   BIOS set up the particular cascade channel? DMA_DO_CASCADE controls
   whether we ignore the concept of cascading controllers or whether
   we emulate as fully as possible. The default is currently full
   emulation.

*) Our function call to simulate DREQ may not behave quite as the hardware
   does. If the user gets DREQ asserted and then software enables the
   relevant channel before the DREQ has been de-asserted, we might get
   things wrong. Ponder this. Maybe the first notify/transfer function back
   onto that channel means DMAAK . NA37 data sheet suggests not waiting
   for DMAAK after DREQ is going to make things go wrong anyway.

*) sw requests only work in block mode

*/

/*****************************************************************************/

#include "kernel.h"

#include "sys.h.stdtypes"
#include "sys.h.sys"
#include "sys.h.syss"
#include "sys.h.hrdstate"

#define COMPILING_DMA
#include "cpu.h.dma"
#include "cpu.h.dmadefs"
#include "cpu.h.cpu"

#include <setjmp.h>
#include <stdio.h>
#include <assert.h>

/*****************************************************************************/

#if 0
extern void my_backtrace(void)
{
#define PSR 0 /*xfc000003*/

    jmp_buf jbuf;
    _kernel_unwindblock *kub = (_kernel_unwindblock *) & jbuf;
    char *lang = "bogus";

    setjmp(jbuf);

    printf("My backtrace\n");

    fflush(NULL);

    while (1)
    {
        const fp = kub->fp;
        const sp = kub->sp;
        const pc = kub->pc /*& ~ PSR*/;
        const sl = kub->sl /*& ~ PSR*/;
        char *cp = _kernel_procname(pc);

        printf("fp %x, sp %x, pc %x, sl %x, lang %s, proc %s\n", fp, sp, pc, sl, lang, cp == NULL ? "*NULL*" : cp);

        if (_kernel_unwind(kub, & lang) <= 0)
            break;
    }

    printf("End of my backtrace\n");

#undef PSR
}
#endif

/*****************************************************************************/

/* Private data */

/* The third "controller" is to direct naff accesses */
/* to so that I don't get wierd addresses and then */
/* start crapping on other memory regions! 13/6/95 */

static DMA_Controller Controller [3];




#if DEBUG

static char * reg_name_table[20] =
{
    "ch0_address   ",       /* 0 */
    "ch0_count     ",       /* 1 */
    "ch1_address   ",       /* 2 */
    "ch1_count     ",       /* 3 */
    "ch2_address   ",       /* 4 */
    "ch2_count     ",       /* 5 */
    "ch3_address   ",       /* 6 */
    "ch3_count     ",       /* 7 */

    "command_status",       /* 8 */
    "request       ",       /* 9 */
    "mask_single   ",       /* 10 */
    "mode          ",       /* 11 */
    "reset_flipflop",       /* 12 */
    "master_temp   ",       /* 13 */
    "reset_mask    ",       /* 14 */
    "mask_all      ",       /* 15 */

    "chan 0 LS612  ",       /* 16 */
    "chan 1 LS612  ",       /* 17 */
    "chan 2 LS612  ",       /* 18 */
    "chan 3 LS612  "        /* 19 */
};

static char *regname(int n)
{
    if (n < 0 || n > 19)
        return "** UNKNOWN DMA REGISTER **";
    return reg_name_table[n];
}
#endif /* DEBUG */

/*****************************************************************************/

/* Dump out our state for debugging purposes */

#include <stdio.h>

#if DEBUG

static void ds1(DMA_Controller *dcp)
{
    static char *xfer_names[4] = { "Verify", "Write", "Read", "Illegal" };
    static char *mode_names[4] = { "Demand", "Single", "Block", "Cascade" };

#define cf(x)                           \
    if (dcp->cont_flags & x)            \
        printf("%-20s: SET\n", #x);     \
    else                                \
        printf("%-20s: CLEAR\n", #x);

#define df(x)                           \
    if (chp->chan_flags & x)            \
        printf("%-20s: SET\n", #x);     \
    else                                \
        printf("%-20s: CLEAR\n", #x);

    int i;


    printf("\nController: %d bits, temp %x, mode count %d\n\n", (1+DMA_CONTNUM(dcp))*8, dcp->temp_reg, dcp->mode_count );

    cf(DMA_MEMMEM_COPY  )
    cf(DMA_CHANNEL0_HOLD)
    cf(DMA_DISABLED     )
    cf(DMA_FLIP_FLOP    )
    cf(DMA_16BIT        )

    for (i = 0; i < 4; i++)
    {
        DMA_Channel *chp = & dcp->chan[i];

        printf("\nChannel %d\n", i);

        printf("Address base %04x, current %04x, Count base %04x, current %04x\n",
            chp->addr_base, chp->addr_curr, chp->count_base, chp->count_curr);

        printf("Transfer: %s\n", xfer_names [chp->chan_flags & 3] );

        if (chp->chan_flags & DMA_AUTO_INITIALISE)
            printf("Auto initialise is active for this channel\n");
        else
            printf("No auto initialise\n");

        if (chp->chan_flags & DMA_AUTO_DEC)
            printf("Address decrements\n");
        else
            printf("Address increments\n");

        printf("Mode: %s\n", mode_names [ (chp->chan_flags >> 6) & 3 ] );

        df(DMA_REQUEST)
        df(DMA_MASK)
        df(DMA_PENDING)
        df(DMA_TC_REACHED)
        df(DMA_ACTIVE)

        printf("Current LS612 %04x, last %04x\n",
            (chp->chan_flags & DMA_LS612_MASK) >> DMA_LS612_SHIFT, chp->last_ls612);
    }

}

static void dump_state(char *msg)
{
    printf("%s\n", msg);


    ds1( & Controller[0] );
    ds1( & Controller[1] );

    printf("\n*****************************************************************************\n\n");

    fflush(stdout);
}

#endif /* DEBUG */

/*****************************************************************************/

/* Actions performed whenever a reset is performed              */
/* Reset performs the following:                                */
/*                                                              */
/* address register        unchanged                            */
/* count register          unchanged                            */
/* mode control register   all bits cleared                     */
/* device control register all bits cleared                     */
/* status register         all bits cleared                     */
/* request register        all bits cleared                     */
/* mask register           all bits set (all channels masked)   */
/* temporary register      all bits cleared                     */
/* Also keep track of whether 8 bit or 16 bit controller        */

static void reset_controller(DMA_Controller *dcp)
{
    int i;

    dcp->cont_flags = DMA_RESET_CONT_FLAGS | (DMA_CONTNUM(dcp) == 1 ? DMA_16BIT : 0);
    dcp->temp_reg = DMA_RESET_TEMP_REG;
    dcp->mode_count = DMA_RESET_MODE_COUNT;

    for (i = 0; i < 4; i++)
    {
        dcp->chan[i].chan_flags = DMA_RESET_CHAN_FLAGS;
    }
}

/* Extra reset actions that only occur on power up type resets */

static void init_controller(DMA_Controller *dcp)
{
    int i;

    for (i = 0; i < 4; i++)
    {
        dcp->chan[i].addr_curr = 0;
        dcp->chan[i].addr_base = 0;
        dcp->chan[i].count_curr = 0;
        dcp->chan[i].count_base = 0;
    }

    reset_controller(dcp);
}


/****************************************************************************/
/* Examine each channel on the controller supplied and schedule HOLD,       */
/* through CPU_BusRequest, for each channel that is (sufficiently) active,  */
/* specifying the range of memory addresses that are going to be affected.  */
/* We note the value of the LS612 latch used for invalidating the memory    */
/* cache so that we can realise when we have been grossly tricked. If       */
/* this area proves problematical, complete cache flushing should be used   */
/* for all CPU_BusRequest calls. For each channel desiring attention, a     */
/* CPU_BusRequest call is issued. This is potentially excessive.            */
/****************************************************************************/

static void check_actions(DMA_Controller *dcp)
{
    uint i;
    const int scale = (dcp->cont_flags & DMA_16BIT) ? 2 : 1;

    if ( dcp->cont_flags & DMA_DISABLED )
    {
#if DEBUG
        SYS_trace("Controller %d is disabled, so no follow-on actions", DMA_CONTNUM(dcp) );
#endif
        return;
    }

#if DMA_DO_CASCADE

    if ( dcp == & Controller [ DMA_BIOS_SLAVE ]
    && ( Controller[DMA_BIOS_MASTER].chan[DMA_BIOS_CASCADE].chan_flags & DMA_CASCADE_MODE ) !=  DMA_CASCADE_MODE )
    {
#if DEBUG
        if (DMA_TRACE_CASCADE)
        {
            SYS_trace("Access to slave DMA controller prevented by inactive BIOS cascade");
        }
#endif
        return;
    }

#endif

    for (i = 0; i < 4; i++)      /* Over each channel */
    {
        DMA_Channel *chp = & dcp->chan[i];
        const uint ul = chp->chan_flags;

        if ( (ul & DMA_REQUEST) != 0 || ( ul & (DMA_MASK | DMA_PENDING) ) == DMA_PENDING )
        {
#if ! DMA_PARTIAL_CACHE

            /* First and last span all possible memory */
            uint first = 0u, last = 0xffffffffu;

#else /* ! DMA_PARTIAL_CODE */

#define THISPAGE        (ul & dma_mask)
#define NEXTPAGE        (THISPAGE + page_size)
#define LASTPAGE        (THISPAGE - page_size)

            const uint dma_mask = scale == 2 ? DMA_LS612_MASK16 : DMA_LS612_MASK8;
            const uint page_size = scale == 2 ? 0x20000 : 0x10000;
            const uint masked_ls612 = ul & dma_mask;

            uint first, last, base, end;
            const uint effcount = (chp->count_curr + 1) * scale;

            base = masked_ls612 | (chp->addr_curr * scale),
            end  = masked_ls612 | (chp->addr_curr * scale);

            if (ul & DMA_AUTO_DEC)
            {
                end -= effcount;

                if ( (end & dma_mask) == LASTPAGE )
                {
                    /* Fairly crude */
                    first = THISPAGE;
                    last = NEXTPAGE - 1;
                }
                else
                {
                    first = end;
                    last = base;
                }
            }
            else
            {
                end += effcount;

                if ( (end & dma_mask) == NEXTPAGE )
                {
                    /* Fairly crude */
                    first = THISPAGE;
                    last = NEXTPAGE - 1;
                }
                else
                {
                    first = base;
                    last = end;
                }
            }

#if DEBUG
            if (DMA_TRACE_ACTION)
                SYS_trace
                (
                    "check_actions #%d:%d: 0x%04x %c 0x%04x gives 0x%08x .. 0x%08x",
                    DMA_CONTNUM(dcp),
                    i,
                    chp->addr_curr * scale,
                    (ul & DMA_AUTO_DEC) ? '-' : '+',
                    effcount,
                    first,
                    last
                );
#endif

#undef THISPAGE
#undef NEXTPAGE
#undef LASTPAGE

#endif

            switch ( ul & DMA_ILLEGAL_TRANSFER /* 3 << 2 */ )
            {
                case DMA_VERIFY_TRANSFER:
                    SYS_trace("Verify transfer mode");
                    break;

                case DMA_ILLEGAL_TRANSFER:
                    SYS_trace("Illegal transfer mode");
                    break;

                /******************************/

                case DMA_WRITE_TRANSFER:
                {
                    DMA_handler *handp = & SYS_State.DMAhandlers[i + 4 * DMA_CONTNUM(dcp)];

#if DMA_paranoid
                    chp->last_ls612 = (BYTE) ( ul >> DMA_LS612_SHIFT );
#endif /* DMA_paranoid */

                    CPU_BusRequest(DMAMODE_WR, first, last);

#if DEBUG
                    SYS_trace("write notify >>>> r12=%x to %p args %08x, %08x, %08x",
                        handp->R12_val, handp->NotifyFn, (ul & DMA_MODE_MASK) >> 2, 
                        (chp->addr_curr * scale) | masked_ls612, effcount);
#endif
                    if (handp->R12_val == 0)
                    {
                        /* User mode/direct call */
                        (*handp->NotifyFn)
                            (
                                (ul & DMA_MODE_MASK) >> 2,
                                (chp->addr_curr * scale) | masked_ls612,
                                effcount
                            );
                    }
                    else
                    {
                        /* Module/supervisor indirect call */
                        _kernel_swi_regs r;

                        r.r[0] = (int) ( (ul & DMA_MODE_MASK) >> 2 );
                        r.r[1] = (int) ( (chp->addr_curr * scale) | masked_ls612 );
                        r.r[2] = (int) ( effcount );

                        SYSs_CallViaR12( &r, ( int ) handp->NotifyFn, handp->R12_val );
                    }

#if DEBUG
                    SYS_trace("write notify <<<<");
#endif
                }
                break;

                /******************************/


                case DMA_READ_TRANSFER:
                {
                    DMA_handler *handp = & SYS_State.DMAhandlers[i + 4 * DMA_CONTNUM(dcp)];

#if DMA_paranoid
                    chp->last_ls612 = (BYTE) ( ul >> DMA_LS612_SHIFT );
#endif /* DMA_paranoid */

                    CPU_BusRequest(DMAMODE_RD, first, last);

#if DEBUG
                    SYS_trace("read notify >>>> r12=%x to %p args %08x, %08x, %08x",
                        handp->R12_val, handp->NotifyFn, (ul & DMA_MODE_MASK) >> 2, 
                        (chp->addr_curr * scale) | masked_ls612, effcount);
#endif
                    if (handp->R12_val == 0)
                    {
                        /* User mode/direct call */
                        (*handp->NotifyFn)
                            (
                                (ul & DMA_MODE_MASK) >> 2,
                                (chp->addr_curr * scale) | masked_ls612,
                                effcount
                            );
                    }
                    else
                    {
                        /* Module/supervisor indirect call */
                        _kernel_swi_regs r;

                        r.r[0] = (int) ( (ul & DMA_MODE_MASK) >> 2 );
                        r.r[1] = (int) ( (chp->addr_curr * scale) | masked_ls612 );
                        r.r[2] = (int) ( effcount );

                        SYSs_CallViaR12( &r, ( int ) handp->NotifyFn, handp->R12_val );
                    }
#if DEBUG
                    SYS_trace("read notify <<<<");
#endif
                }
                break;

            }   /* switch on channel mode */
        }   /* if something to do for this channel */
    }   /* for each channel */

}

/*****************************************************************************/
/*
 *    void (*DMA_Request) ( int n );
 *
 *   This  is  a  function provided by the DMA emulation,  which  is
 *   called  to indicate that a device wants to start a transfer  on
 *   DMA  channel  n. It is not a strict emulation of  wiggling  the
 *   equivalent  DREQ  line,  in that it  is  not  affected  by  DMA
 *   transfer mode.
 *
 *   On  receiving this call, the DMA emulation will typically  look
 *   to  see  if the channel is unmasked and ready. If so,  it  will
 *   call CPU_BusRequest(), and return.
 *
 *   The  device  may call DMA_Request() more than once  before  the
 *   corresponding  DMA  acknowledge is  generated;  this  will  not
 *   'queue up' multiple acknowledges. It is suggested that the  DMA
 *   emulation  should  not  worry  about  this,  and  simply   call
 *   CPU_BusRequest() every time, provided the channel is  unmasked.
 *   CPU_BusRequest() merely sets a flag or a callback; it is not  a
 *   time-consuming routine.
 *
 *       The DMA_Request() function will typically be called from  a
 *       memory  or  I/O  handler routine in the device  module;  it
 *       should  not  be  called asynchronously (e.g.  from  an  ARM
 *       interrupt  service routine). I will be adding a  'callback'
 *       facility to !PC, to allow asynchronous routines to give !PC
 *       a  function to be called the next time it is 'safe'  to  do
 *       so.  When this is done, DMA_Request() may be called  either
 *       by a handler or on a callback.
 *
 *  Note that a DMAreq can arrive after the cannel has been disabled. If this
 *  happens then the request will hang around for ever until the channel is
 *  re-enabled at which point data will be transferred again before a new notify
 *  is sent. This can mess things up so at the moment we disallow DMAreqs if the
 *  channel is disabled. However the state of the Dreq line (which DMAreqs
 *  correspond to) should be reflected in the status register even if the channel
 *  is adisabled so this will now be wrong.... Is this a problem? We could have
 *  two flags - one for transfer pending which goes off when the channel is
 *  disabled, and another for the DREQ line status. 
 * 
 */

static void DMA_Request_fn(int chann)
{
    DMA_Controller *dcp = & Controller [chann >> 2];

#if DEBUG
    SYS_trace("dma request fn >>>> chan %d", chann);
#endif

    if (chann < 0 || chann > 7)
    {
        /*SYS_error( true, "DREQ: DMA emulation request for invalid channel %d", chann);*/
        return;
    }

    if (dcp->cont_flags & DMA_DISABLED)
    {
        /*SYS_trace("DREQ: Use of DMA_Request when controller is disabled for channel %d", chann);*/
    }
    else
    {
        dcp->chan[chann & 3].chan_flags |= DMA_PENDING;
        /*SYS_trace("DREQ: Set pending action flag for channel %d", chann);*/
    }

    check_actions(dcp);

#if DEBUG
    SYS_trace("dma request fn <<<< chan %d", chann);
#endif
}


/* Perform regular transfer actions */

static void do_normal_channel(DMA_Controller *dcp, int chann)
{
    const int scale = (dcp->cont_flags & DMA_16BIT) ? 2 : 1;

    DMA_Channel *chp = &dcp->chan[chann];
    int left = (chp->count_curr + 1) * ( (dcp->cont_flags & DMA_16BIT) ? 2 : 1 );
    bool done = false;
    bool wrapped = false;
    bool reschedule = false;

    if ( (chp->chan_flags & DMA_TRANSFER_MASK) == DMA_ILLEGAL_TRANSFER)
    {
        /*SYS_trace("Illegal DMA transfer mode");*/
        chp->chan_flags &= ~ ( DMA_PENDING | DMA_REQUEST );
        return;
    }

    do
    {
        /* Read LS612 afresh each loop iteration. */
        const uint flags = chp->chan_flags;
        const uint dma_mask = scale == 2 ? DMA_LS612_MASK16 : DMA_LS612_MASK8;
        const uint page_size = scale == 2 ? 0x20000 : 0x10000;
        const uint masked_ls612 = flags & dma_mask;
        const int dir = (flags & DMA_AUTO_DEC) ? -1 : 1;
        const uint w16 = chp->addr_curr * scale;
        const int PCaddr = masked_ls612 | w16;
        int this = left;
        int ndone;
        uint magic_rc;
        bool TCflag = false;
        BYTE *ARMaddr = CPU_PCtoARMaddr( PCaddr );
        DMA_handler *handp = & SYS_State.DMAhandlers[chann + 4 * DMA_CONTNUM(dcp)];

        if (dir < 0)
        {
            if ( w16 - this /*< 0*/ >= page_size )
            {
                this = w16 + 1;
            }

            this = CPU_PCtoARMcheck( PCaddr, - this);
        }
        else
        {
            if (w16 + this > page_size)
            {
                this = page_size - w16;
            }

            this = CPU_PCtoARMcheck( PCaddr, this);
        }

        if ( (flags & DMA_TRANSFER_MASK) == DMA_VERIFY_TRANSFER )
        {
            /*SYS_trace("DMA: Verify causing automatic completion");*/
            magic_rc = XFER_TC_BIT | this;
        }
        else
        {
#if DEBUG
            SYS_trace("transfer fn >>>>");
#endif
            if (handp->R12_val == 0)
            {
                magic_rc = (*handp->TransferFn)
                           (
                               (flags & DMA_MODE_MASK) >> DMA_MODE_SHIFT,
                               PCaddr,
                               this,
                               ARMaddr
                           );
            }
            else
            {
                _kernel_swi_regs r;
                r.r[0] = (int) ( (flags & DMA_MODE_MASK) >> DMA_MODE_SHIFT );
                r.r[1] = (int) ( PCaddr );
                r.r[2] = (int) ( this );
                r.r[3] = (int) ( ARMaddr );

                magic_rc = SYSs_CallViaR12( &r, ( int ) handp->TransferFn, handp->R12_val );
            }

#if DEBUG
            SYS_trace("transfer fn <<<< with %x", magic_rc);
#endif
        }

        ndone = (int) (magic_rc & XFER_LEN_MASK);
        TCflag = (magic_rc & XFER_TC_BIT) ? 1 : 0;

        if ( (chp->chan_flags & DMA_TRANSFER_MASK) != DMA_VERIFY_TRANSFER )
        {

            /* Address last byte of transferred region. */
            /* Might be wrong for reverse 16 bit dma transfers! */

            dcp->temp_reg = * (ARMaddr + (dir * ndone) - dir);
            left -= ndone;

            chp->count_curr -= ndone / scale;
            chp->addr_curr += (dir * ndone) / scale;

            /*SYS_trace("set temp to %x from %p", dcp->temp_reg, (ARMaddr + (dir * this) - dir) );*/

            if (left <= 0 )
                done = TCflag = wrapped = true;

            if (ndone < this)
              done = true;
        }

        if (wrapped || TCflag)
        {
            chp->chan_flags |= DMA_TC_REACHED;
            chp->chan_flags &= ~ ( DMA_PENDING | DMA_REQUEST );
        }
    } while (! done);

    if (wrapped)
    {
        if ( chp->chan_flags & DMA_AUTO_INITIALISE )
        {
            chp->addr_curr = chp->addr_base;
            chp->count_curr = chp->count_base;
            reschedule = true;
#if DEBUG
            SYS_trace("auto initialised causing reschedule");
#endif
        }
    }

    if (reschedule)
    {
        check_actions(dcp);
    }
}


static void do_held0_channel(DMA_Controller *dcp, int chann)
{
    NotUsed(dcp);
    NotUsed(chann);
    SYS_trace("do_held0_channel - not written yet");
}

static void do_memmem_channel(DMA_Controller *dcp, int chann)
{
    NotUsed(dcp);
    NotUsed(chann);
    SYS_trace("do_memmem_channel - not written yet!");
}

/*
Perform whatever transfers can be performed for the specified controller.
Prioritisation is hardwired as channels 0..3. Cascading is enabled if we
are checking for it.
*/

static void do_controller(DMA_Controller *dcp)
{
    int i;

    if (dcp->cont_flags & DMA_DISABLED)
    {
        /*SYS_trace("do_controller %d - disabled", DMA_CONTNUM(dcp) );*/
        return;
    }

    for (i = 0; i < 4; i++)
    {
        DMA_Channel *chp = & dcp->chan[i];
        const uint ul = chp->chan_flags;

        if ( (ul & DMA_REQUEST) != 0 || ( ul & (DMA_MASK | DMA_PENDING) ) == DMA_PENDING )
        {
            if (i == 0 && (ul & DMA_MEMMEM_COPY) != 0)
            {
                do_memmem_channel(dcp, i);
                /* Skip channel 1 */
                i++;
            }
            else if (i == 0 && (ul & DMA_CHANNEL0_HOLD) != 0 )
            {
                do_held0_channel(dcp, i);
            }
            else
            {
                do_normal_channel(dcp, i);
            }
        }
    }
}

/*****************************************************************************/
/*
 * System calls here when the HOLD line has been asserted and HLDA has been
 * returned. We are now free to use the bus for 'DMA' actions.
 */

extern void DMA_BusAck(void)
{
    do_controller( & Controller [DMA_BIOS_MASTER] );

#if DMA_DO_CASCADE
    if ( ( Controller[DMA_BIOS_MASTER].chan[DMA_BIOS_CASCADE].chan_flags & DMA_CASCADE_MODE ) ==  DMA_CASCADE_MODE )
        do_controller( & Controller [DMA_BIOS_SLAVE] );
#else
    do_controller( & Controller [DMA_BIOS_SLAVE] );
#endif
}

/*****************************************************************************/

static void dummy_NotifyFn(uint mode, uint PCaddr, uint len)
{
    SYS_trace("\n\n#### dummy DMA NotifyFn(0x%x, 0x%x, 0x%x)", mode, PCaddr, len);
}

static uint dummy_TransferFn(uint mode, uint PCaddr, uint len, BYTE *ARMaddr)
{
    SYS_trace("\n\n#### dummy DMA TransferFn(0x%0x, 0x%x, 0x%x, 0x%p)", mode, PCaddr, len, ARMaddr);

    return len;
}

/*****************************************************************************/


static int rd8(int addr, DMA_Controller *dcp)
{
#define chfl(z)      ( dcp->chan[ (z) & 3 ].chan_flags )

    unsigned int x;     /* Avoid hassles from >> - unsigned int would be safer! */

#if DEBUG
    if ( DMA_RDTRACE & ( 1 << addr) )
    {
        SYS_trace("rd8(%s, &Controller[%d])", regname(addr), dcp - & Controller[0]);
    }
#endif

    switch (addr)
    {
        /* Read current address with flip-flop action */

        case DMA_ch0_address   : /* 0 */
        case DMA_ch1_address   : /* 2 */
        case DMA_ch2_address   : /* 4 */
        case DMA_ch3_address   : /* 6 */
            x = (addr >> 1) & 3;
            x = dcp->chan[x].addr_curr;
            x = ( dcp->cont_flags & DMA_FLIP_FLOP ) ? x >> 8 : x & 0xff;
            dcp->cont_flags ^= DMA_FLIP_FLOP;
            break;

        /* Read current counter with flip-flop action */

        case DMA_ch0_count     : /* 1 */
        case DMA_ch1_count     : /* 3 */
        case DMA_ch2_count     : /* 5 */
        case DMA_ch3_count     : /* 7 */
            x = (addr >> 1) & 3;
            x = dcp->chan[x].count_curr;
            x = ( dcp->cont_flags & DMA_FLIP_FLOP ) ? x >> 8 : x & 0xff;
            dcp->cont_flags ^= DMA_FLIP_FLOP;
            break;

        /* Read status register */

        case DMA_command_status: /* 8 */
            x = ( (chfl(0) & DMA_TC_REACHED) ?   1 : 0 ) |
                ( (chfl(1) & DMA_TC_REACHED) ?   2 : 0 ) |
                ( (chfl(2) & DMA_TC_REACHED) ?   4 : 0 ) |
                ( (chfl(3) & DMA_TC_REACHED) ?   8 : 0 ) |
                ( (chfl(0) & DMA_PENDING)    ?  16 : 0 ) |
                ( (chfl(1) & DMA_PENDING)    ?  32 : 0 ) |
                ( (chfl(2) & DMA_PENDING)    ?  64 : 0 ) |
                ( (chfl(3) & DMA_PENDING)    ? 128 : 0 ) ;
            {
                int i;
                for (i = 0; i < 4; i++)
                {
#if DEBUG
                    if ( dcp->chan[i].chan_flags & DMA_TC_REACHED )
                        SYS_trace("Clearing TC for channel %d", i);
#endif
                    dcp->chan[i].chan_flags &= ~ DMA_TC_REACHED;
                }
            }
            break;

        /* Read request register */

        case DMA_request       : /* 9 */
            x = 0xf0                                |
                ( (chfl(0) & DMA_REQUEST) ? 1 : 0 ) |
                ( (chfl(1) & DMA_REQUEST) ? 2 : 0 ) |
                ( (chfl(2) & DMA_REQUEST) ? 4 : 0 ) |
                ( (chfl(3) & DMA_REQUEST) ? 8 : 0 ) ;
            break;

        /* Read command register */

        case DMA_mask_single   : /* 10 */
            SYS_trace("#### Read of DMA command register?");
            x=0xFF;
            break;

        /* Reading the mode register involves an internal counter that steps around */
        /* the mode register value for each channel. This counter is set to zero by */
        /* a Clear Mode Register Counter command.                                   */

        case DMA_mode          : /* 11 */
            x = ( chfl( dcp->mode_count ) & DMA_MODE_MASK ) |
                ( DMA_undef & 3 );
            dcp->mode_count += 1; /* Avoid side effects */
            break;

        /* Set byte pointer flip flop */

        case DMA_reset_flipflop: /* 12 */
            dcp->cont_flags |= DMA_FLIP_FLOP;
            x = DMA_undef & 0xff;
            break;

        /* Read temporary register */

        case DMA_master_temp   : /* 13 */
            x = dcp->temp_reg;
            break;

        /* Clear mode register counter */

        case DMA_reset_mask    : /* 14 */
            dcp->mode_count = 0;
            x = DMA_undef & 0xff;
            break;

        /* Read all DMA request mask register bits */

        case DMA_mask_all      : /* 15 */
            x = 0xf0                               |
                ( ( chfl(0) & DMA_MASK ) ? 1 : 0 ) |
                ( ( chfl(1) & DMA_MASK ) ? 2 : 0 ) |
                ( ( chfl(2) & DMA_MASK ) ? 4 : 0 ) |
                ( ( chfl(3) & DMA_MASK ) ? 8 : 0 ) ;
            break;

        /* Read the LS612 latch for a particular channel */

        case 16:                /* LS612 for channel 0/4 */
        case 17:                /* LS612 for channel 1/5 */
        case 18:                /* LS612 for channel 2/6 */
        case 19:                /* LS612 for channel 3/7 */
            x = ( DMA_LS612_MASK >> DMA_LS612_SHIFT ) & ( chfl(addr) >> DMA_LS612_SHIFT );
            break;

        default:
#if DEBUG
            SYS_trace("dma: rd8: addr 0x%x is bad", addr);
#endif

            x = 0;      /* or -1? */
            break;
    }

#if DEBUG
    if ( DMA_RDTRACE & (1 << addr) )
    {
        SYS_trace("rd8 returns 0x%02x", x);
    }
#endif

    /* Have treated this as unsigned during the function as it's more */
    /* controllable. Return it back to int here - although assumung that */
    /* only the bottom byte is actually significant. */

    return (int) x;

#undef chfl
}

/*****************************************************************************/


static void wr8(int addr, int data, DMA_Controller *dcp)
{
#define chfl(z)      ( dcp->chan[ (z) & 3 ].chan_flags )

#if DEBUG
    if ( DMA_WRTRACE & ( 1 << addr) )
    {
        SYS_trace("wr8(%s, 0x%02x, & Controller[%d]", regname(addr), data, dcp - & Controller[0] );
    }
#endif

    data &= 0xff;

    switch (addr)
    {
        /* Write current address */

        case DMA_ch0_address   : /* 0 */
        case DMA_ch1_address   : /* 2 */
        case DMA_ch2_address   : /* 4 */
        case DMA_ch3_address   : /* 6 */
            {
                DMA_Channel *chanp = & dcp->chan[ (addr >> 1) & 3 ];

                if ( dcp->cont_flags & DMA_FLIP_FLOP )
                {
                    chanp->addr_curr = (chanp->addr_curr & 0xff) | (data << 8);
                    chanp->addr_base = (chanp->addr_base & 0xff) | (data << 8);
                }
                else
                {
                    chanp->addr_curr = (chanp->addr_curr & 0xff00) | data;
                    chanp->addr_base = (chanp->addr_base & 0xff00) | data;
                }

                dcp->cont_flags ^= DMA_FLIP_FLOP;
            }
            break;

        /* Write current counter */

        case DMA_ch0_count     : /* 1 */
        case DMA_ch1_count     : /* 3 */
        case DMA_ch2_count     : /* 5 */
        case DMA_ch3_count     : /* 7 */
            {
                DMA_Channel *chanp = & dcp->chan[ (addr >> 1) & 3 ];

                if ( dcp->cont_flags & DMA_FLIP_FLOP )
                {
                    chanp->count_curr = (chanp->count_curr & 0xff) | (data << 8);
                    chanp->count_base = (chanp->count_base & 0xff) | (data << 8);
                }
                else
                {
                    chanp->count_curr = (chanp->count_curr & 0xff00) | data;
                    chanp->count_base = (chanp->count_base & 0xff00) | data;
                }

                dcp->cont_flags ^= DMA_FLIP_FLOP;
            }
            break;

        /* Write command register */

        case DMA_command_status: /* 8 */
            dcp->cont_flags = (dcp->cont_flags & ~ DMA_COMMAND_MASK) | (data & DMA_COMMAND_MASK);
            check_actions(dcp);
            break;

        /* Write request register */

        case DMA_request       : /* 9 */
            {
                uint *ulp = & dcp->chan[ data & 3 ].chan_flags;
                * ulp = ( * ulp & ~ DMA_REQUEST ) |
                        ( (data & 4) ? DMA_REQUEST : 0 );
                check_actions(dcp);
            }
            break;

        /* Write single bit DMA request mask register */

        case DMA_mask_single   : /* 10 */
            {
                uint *ulp = & dcp->chan[ data & 3 ].chan_flags;
                * ulp = ( * ulp & ~ DMA_MASK ) |
                        ( (data & 4) ? DMA_MASK : 0 );
                check_actions(dcp);
            }
            break;

        /* Write mode register */

        case DMA_mode          : /* 11 */
            {
                uint *ulp = & dcp->chan[ data & 3 ].chan_flags;
                * ulp = ( * ulp & ~ DMA_MODE_MASK ) |
                        ( data & DMA_MODE_MASK );
            }
            break;

        /* Clear byte pointer flip flop */

        case DMA_reset_flipflop: /* 12 */
            dcp->cont_flags &= ~ DMA_FLIP_FLOP;
            break;

        /* Master clear */

        case DMA_master_temp   : /* 13 */
            reset_controller(dcp);
            break;

        /* Clear all DMA request mask register bits */

        case DMA_reset_mask    : /* 14 */
            {
                int i;

                for (i = 0; i < 4; i++)
                {
                    dcp->chan[i].chan_flags &= ~ DMA_MASK;
                }

                check_actions(dcp);
            }
            break;

        /* Write all DMA request mask register bits */

        case DMA_mask_all      : /* 15 */
            {
                int i;

                for (i = 0; i < 4; i++)
                {
                    unsigned int *ulp = & dcp->chan[i].chan_flags;
                    * ulp = ( * ulp & ~ DMA_MASK ) |
                            ( (data & (1 << i)) ? DMA_MASK : 0 );
                }

                check_actions(dcp);
            }
            break;

        /* Write the LS612 latch for a channel */

        case 16:                /* LS612 for channel 0 */
        case 17:                /* LS612 for channel 1 */
        case 18:                /* LS612 for channel 2 */
        case 19:                /* LS612 for channel 3 */
            {
                uint *ulp = & dcp->chan[ addr & 3 ].chan_flags;
                * ulp = ( * ulp & ~ DMA_LS612_MASK ) |
                        ( ( data << DMA_LS612_SHIFT) & DMA_LS612_MASK );
            }

            break;

        default:
#if DEBUG
            SYS_trace("WARNING: undefined wr8 action in DMA emulation");
            SYS_trace("addr %x, data %x, controller %d", addr, data, (dcp->cont_flags & DMA_16BIT) ? 16 : 8);
#endif
          break;
    }

#if DEBUG
    if ( DMA_WRTRACE & ( 1 << addr) )
    {
        SYS_trace("wr8 leaving");
    }
#endif

#undef chfl
}

/*****************************************************************************/

/* 16 bit accesses are macro'd back onto 8 bit accesses */

static int rd16(int addr, DMA_Controller *dcp)
{
    /* Ordered */
    int x;

    /*SYS_trace("rd16(%x, %d)", addr, DMA_CONTNUM(dcp));*/

    x = rd8( addr, dcp );
    return x + ( rd8( addr + 1, dcp ) << 8 );
}

static void wr16(int addr, int data, DMA_Controller *dcp)
{
    /*SYS_trace("wr16(%x, %x, %d)", addr, data, DMA_CONTNUM(dcp));*/
    wr8(addr, data & 0xff, dcp);
    wr8(addr + 1, data >> 8, dcp);
}

/*****************************************************************************/

#define BOGUS       -1
#define BASE        16

/* 0xf?? indicates a naff access */

static struct { int addr, cont; } ls612_table[16] =
{
    {   0xf01   ,   2       },
    {   BASE + 2,   0       },
    {   BASE + 3,   0       },
    {   BASE + 1,   0       },

    {   0xf02   ,   2       },
    {   0xf03   ,   2       },
    {   0xf04   ,   2       },
    {   BASE + 0,   0       },

    {   0xf05   ,   2       },
    {   BASE + 2,   1       },
    {   BASE + 3,   1       },
    {   BASE + 1,   1       },

    {   0xf06   ,   2       },
    {   0xf07   ,   2       },
    {   0xf08   ,   2       },
    {   BASE + 0,   1       }
};


#define addr1(a)        ( (a) & 0xf )
#define addr2(a)        ( ((a) >> 1) & 0xf )
#define addrL(a)        ( ((a) & 3) | 16 )
#define cont1(a)        & Controller [0]
#define cont2(a)        & Controller [1]
#define contL(a)        & Controller [ ((a) >> 2) & 1 ]

static int DMA1_Rd8 (int addr) { return rd8 ( addr1(addr), cont1(addr) ); }
static int DMA1_Rd16(int addr) { return rd16( addr1(addr), cont1(addr) ); }
static int DMA2_Rd8 (int addr) { return rd8 ( addr2(addr), cont2(addr) ); }
static int DMA2_Rd16(int addr) { return rd16( addr2(addr), cont2(addr) ); }

static int DMAL_Rd8(int addr)
{
    return rd8(     ls612_table[ addr & 0xf ].addr,
                    & Controller [ ls612_table[ addr & 0xf ].cont ] );
}

static int DMAL_Rd16(int addr)
{
    return rd16(    ls612_table[ addr & 0xf ].addr,
                    & Controller [ ls612_table[ addr & 0xf ].cont ] );
}

static void DMA1_Wr8 (int addr, int data) { wr8 ( addr1(addr), data, cont1(addr) ); }
static void DMA1_Wr16(int addr, int data) { wr16( addr1(addr), data, cont1(addr) ); }
static void DMA2_Wr8 (int addr, int data) { wr8 ( addr2(addr), data, cont2(addr) ); }
static void DMA2_Wr16(int addr, int data) { wr16( addr2(addr), data, cont2(addr) ); }


static void DMAL_Wr8 (int addr, int data)
{
    wr8 ( ls612_table[ addr & 0xf ].addr , data, & Controller [ ls612_table[ addr & 0xf ].cont ] );
}

static void DMAL_Wr16 (int addr, int data)
{
    wr16 ( ls612_table[ addr & 0xf ].addr , data, & Controller [ ls612_table[ addr & 0xf ].cont ] );
}


#undef addr1
#undef addr2
#undef addrL
#undef cont1
#undef cont2
#undef cont1L

/*****************************************************************************/

extern void DMA_Init ( void )
{
    int i;

    init_controller(& Controller[0] );
    init_controller(& Controller[1] );

    /* Emulated device request into DMA emulator */

    SYS_State.DMA_Request = & DMA_Request_fn;
    SYS_State.DMAsAvailable = 0xff;     /* 8 channels of dma */

    /* Default handlers for when not in use */

    SYS_State.NullDMAhandler.NotifyFn = & dummy_NotifyFn;
    SYS_State.NullDMAhandler.TransferFn = & dummy_TransferFn;
    SYS_State.NullDMAhandler.R12_val = 0;

    for (i = 0; i < SYS_nDMAslots; i++)
    {
        SYS_State.DMAhandlers[i] = SYS_State.NullDMAhandler;
    }

    /* Controller 1, LS612 latches, controller 2 */

    SYS_registerIO (0x000, 0x01f, DMA1_Rd8, DMA1_Rd16, DMA1_Wr8, DMA1_Wr16, DMA_r12_value);
    SYS_registerIO (0x080, 0x08f, DMAL_Rd8, DMAL_Rd16, DMAL_Wr8, DMAL_Wr16, DMA_r12_value);
    SYS_registerIO (0x0c0, 0x0df, DMA2_Rd8, DMA2_Rd16, DMA2_Wr8, DMA2_Wr16, DMA_r12_value);
}


/*****************************************************************************/

/* eof cpu/c/dma */
