#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/wrapper.h>
#include <linux/pci.h>
#include <linux/iobuf.h>
#include <asm/uaccess.h>

#include <dev/pci/sis1100_sc.h>

static ssize_t
_sis1100_read_dma(
    struct SIS1100_fdata* fd,
    u_int32_t addr,           /* VME or SDRAM address */
    int32_t am,               /* address modifier, not used if <0 */
    u_int32_t size,           /* datasize must be 4 for DMA but is not checked*/
    int space,                /* remote space (1,2: VME; 6: SDRAM) */
    int fifo_mode,
    size_t count,             /* bytes to be transferred */
    u_int8_t* data,           /* destination (user virtual address) */
    int* prot_error
    )
{
    struct SIS1100_softc* sc=fd->sc;
    int res, i, aborted=0;
    u_int32_t head, tmp;
    volatile struct plx9054_dmadesc* desclist=
                (struct plx9054_dmadesc*)sc->descbuf.cpu_addr;
    sigset_t oldset;
    struct kiobuf* iobuf=sc->iobuf;
    int err, offs;

    if (count>MAXSIZE_KIO) count=MAXSIZE_KIO;

    {
        u_int32_t val;
        val=plxreadreg(sc, DMACSR0_DMACSR1);
        if (!(val&0x10)) {
            printk(KERN_CRIT "sis1100_read_dma: DMACSR0=%04x\n", val);
            return -EIO;
        }
    }

    err=map_user_kiobuf(READ, iobuf, (unsigned long)data, count);
    if (err) {
        printk(KERN_WARNING "SIS1100[%d] map_user_kiobuf failed\n", sc->unit);
        return err;
    }

    offs=iobuf->offset;
    for (i=0; i<iobuf->nr_pages-1; i++) {
        desclist[i].pcistart=pci_map_page(sc->pcidev, iobuf->maplist[i], offs,
                PAGE_SIZE-offs, READ);
        desclist[i].localstart=0;
        desclist[i].size=PAGE_SIZE-offs;
        desclist[i].next=(sc->descbuf.dma_handle+
                (i+1)*sizeof(struct plx9054_dmadesc))|0x9;
        offs=0;
    }
    desclist[i].pcistart=pci_map_page(sc->pcidev, iobuf->maplist[i], offs,
            iobuf->length-i*PAGE_SIZE+iobuf->offset-offs, READ);
    desclist[i].localstart=0;
    desclist[i].size=iobuf->length-i*PAGE_SIZE+iobuf->offset-offs;
    desclist[i].next=0xa;

/* prepare PLX */
    plxwritereg(sc, DMACSR0_DMACSR1, 1<<3); /* clear irq */
    plxwritereg(sc, DMAMODE0, 0x43|(1<<7)|(1<<8)|(1<<9)|(1<<10)|(1<<11)|
        (1<<12)|(1<<14)|(1<<17));
    plxwritereg(sc, DMADPR0, sc->descbuf.dma_handle|1);

    tmp=plxreadreg(sc, BIGEND_LMISC_PROT_AREA);
    if (fd->big_endian)
        tmp|=(1<<7); /* big endian */
    else
        tmp&=~(1<<7); /* little endian */
    plxwritereg(sc, BIGEND_LMISC_PROT_AREA, tmp);

/* prepare add on logic */
    /* 4 Byte, local space 2, BT, EOT, start with t_adl */
    head=0x0f80A002|(space&0x3f)<<16;
    if (am>=0) {
        head|=0x800;
        sis1100writereg(sc, t_am, am);
    }
    if (fifo_mode) head|=0x4000;
    sis1100writereg(sc, t_hdr, head);
    wmb();
    sis1100writereg(sc, t_dal, count);

    sis1100writereg(sc, d0_bc, 0);
    sis1100writereg(sc, d0_bc_buf, 0);

    sis1100writereg(sc, p_balance, 0);

/* block signals */
    spin_lock_irq(&current->sigmask_lock);
    oldset = current->blocked;
    sigfillset(&current->blocked);
    sigdelset(&current->blocked, SIGKILL);
    /* dangerous, should be removed later */
    /*if (!sigismember(&oldset, SIGINT)) sigdelset(&current->blocked, SIGINT);*/
#if LINUX_VERSION_CODE < 0x20500
    recalc_sigpending(current);
#else
    recalc_sigpending_tsk(current);
#endif
    spin_unlock_irq(&current->sigmask_lock);

/* enable dma */
    plxwritereg(sc, DMACSR0_DMACSR1, 3);

/* enable irq */
    sc->got_irqs=0;
    sis1100_enable_irq(sc, plxirq_dma0, irq_synch_chg|irq_prot_l_err);

/* start transfer */
    mb();
    sis1100writereg(sc, t_adl, addr);
    wmb();

/* wait for dma */
    res=wait_event_interruptible(
	sc->sis1100_wait,
	(sc->got_irqs & (got_dma0|got_sync|got_l_err))
	);
    sis1100_disable_irq(sc, plxirq_dma0, irq_prot_l_err);

    if (sc->got_irqs&(got_dma0|got_l_err)) { /* transfer complete or error */
        count=sis1100readreg(sc, d0_bc);
        if (!(sc->got_irqs&got_dma0)) {
            u_int32_t val;
            val=plxreadreg(sc, DMACSR0_DMACSR1);
            if (!(val&0x10)) { /* DMA not stopped yet; abort it */
                sis1100writereg(sc, sr, sr_abort_dma);
                do {
                    val=plxreadreg(sc, DMACSR0_DMACSR1);
                } while (!(val&0x10));
            }
        }
    } else /*(res||(sc->got_irqs&(got_sync)))*/ { /* fatal */
        u_int32_t val;
        aborted=0x300;
        if (res) {
            printk(KERN_WARNING "SIS1100[%d] read_dma: interrupted\n", sc->unit);
            aborted|=1;
        }
        if (sc->got_irqs&got_sync) {
            printk(KERN_WARNING "SIS1100[%d] read_dma: synchronisation lost\n",
                    sc->unit);
            aborted|=2;
        }
        if (sc->got_irqs&got_xoff) {
            printk(KERN_CRIT "SIS1100[%d] read_dma: got xoff (irqs=0x%04x)\n",
                    sc->unit, sc->got_irqs);
            aborted|=4;
        }
        if (aborted==300) {
            printk(KERN_CRIT "SIS1100[%d] read_dma: got_irqs=0x%x\n",
                    sc->unit, sc->got_irqs);
        }
        val=plxreadreg(sc, DMACSR0_DMACSR1);
        if (!(val&0x10)) { /* DMA not stopped yet; abort it */
            sis1100writereg(sc, sr, sr_abort_dma);
            do {
                val=plxreadreg(sc, DMACSR0_DMACSR1);
            } while (!(val&0x10));
        }
    }

    plxwritereg(sc, DMACSR0_DMACSR1, 0);

    spin_lock_irq(&current->sigmask_lock);
    current->blocked = oldset;
#if LINUX_VERSION_CODE < 0x20500
    recalc_sigpending(current);
#else
    recalc_sigpending_tsk(current);
#endif
    spin_unlock_irq(&current->sigmask_lock);

    *prot_error=sis1100readreg(sc, prot_error);

    if (aborted) dump_glink_status(sc, "after abort", 1);
    if (aborted) *prot_error=aborted;
    if ((*prot_error!=0) && ((*prot_error&0x200)!=0x200)) count=-EIO;

    unmap_kiobuf(iobuf);

    return count;
}

ssize_t
sis1100_read_dma(
    struct SIS1100_fdata* fd,
    u_int32_t addr,           /* VME or SDRAM address */
    int32_t am,               /* address modifier, not used if <0 */
    u_int32_t size,           /* datasize; should be 4 */
    int space,                /* remote space (1,2: VME; 6: SDRAM) */
    int fifo_mode,
    size_t count,             /* bytes to be transferred */
    u_int8_t* data,           /* destination (user virtual address) */
    int* prot_err
    )
{
    struct SIS1100_softc* sc=fd->sc;
    ssize_t res=1, completed=0;

    *prot_err=0;

    if (!count) return 0;

    down(&sc->sem_hw);
    while (count && (res>0) && (*prot_err==0)) {
        res=_sis1100_read_dma(fd, addr, am, size, space, fifo_mode,
                count, data, prot_err);

        if (res>0) {
            if (!fifo_mode) addr+=res;
            data+=res;
            completed+=res;
            count-=res;
        }
    }
    up(&sc->sem_hw);

    if (completed)
        return completed;
    else
        return res;
}
