/* $ZEL: sis1100_write_dma.c,v 1.12 2002/05/28 11:51:00 wuestner Exp $ */

#include "Copyright"

#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 <asm/io.h>
#include <linux/iobuf.h>
/*#include <linux/highmem.h>*/
#include <asm/uaccess.h>
/*#include <asm/scatterlist.h>*/

#include <dev/pci/sis1100var.h>
#include <dev/pci/sis3100_map.h>

static ssize_t
_sis1100_write_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,
    int count,                /* bytes to be transferred */
    const u_int8_t* data,     /* source (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;

    if ((addr^(addr+count))&0x80000000U) count=0x80000000U-addr;

    err=map_user_kiobuf(WRITE, iobuf, (unsigned long)data, count);
    if (err) {
        printk(KERN_INFO "map_user_kiobuf failed\n");
        return err;
    }

    offs=iobuf->offset;
    for (i=0; i<iobuf->nr_pages-1; i++) {
        desclist[i].pcistart=__pa(page_address(iobuf->maplist[i])+offs);
        desclist[i].localstart=addr&0x7fffffffU;
        if (!fifo_mode) desclist[i].localstart+=i*PAGE_SIZE;
        desclist[i].size=PAGE_SIZE-offs;
        desclist[i].next=(sc->descbuf.dma_handle+
                (i+1)*sizeof(struct plx9054_dmadesc))|0x1;
        offs=0;
    }
    desclist[i].pcistart=__pa(page_address(iobuf->maplist[i])+offs);
    desclist[i].localstart=addr&0x7fffffffU;
    if (!fifo_mode) desclist[i].localstart+=i*PAGE_SIZE;
    desclist[i].size=iobuf->length-i*PAGE_SIZE+iobuf->offset-offs;
    desclist[i].next=0x2;

/* prepare PLX */
    plxwritereg(sc, DMACSR0_DMACSR1, 1<<3); /* clear irq */
    plxwritereg(sc, DMAMODE0,
        0x43|(1<<7)|(1<<8)|(1<<9)|(1<<10)|(1<<14)|(1<<17)|
            (fifo_mode?(1<<11):0));
    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=0x0f80A402|(space&0x3f)<<16;
    if (am>=0) {
        head|=0x800;
        sis1100writereg(sc, d_am, am);
    }
    if (fifo_mode) head|=0x4000;
    sis1100writereg(sc, d_hdr, head);
    /*wmb();*/
    sis1100writereg(sc, d_adl, addr); /* only bit 31 is valid */

    sis1100writereg(sc, d_bc, count);

    sis1100writereg(sc, p_balance, 0);

    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);*/
    recalc_sigpending(current);
    spin_unlock_irq(&current->sigmask_lock);

/* enable irq */
    /* irq_synch_chg and irq_prot_l_err should always be enabled */
    sis1100_enable_irq(sc, 0,
        irq_prot_l_err|irq_synch_chg|irq_s_xoff|irq_prot_end);

/* start dma */
    sc->got_irqs=0;
    mb();
    plxwritereg(sc, DMACSR0_DMACSR1, 3);

/* wait for confirmation */
    res=wait_event_interruptible(
	sc->sis1100_wait,
	(sc->got_irqs & (got_end|got_xoff|got_sync|got_l_err))
	);

    if (sc->got_irqs&got_l_err) {
        printk(KERN_CRIT "SIS1100: irq_prot_l_err in write_dma, irqs=0x%04x\n",
            sc->got_irqs);
    }
    if (res|(sc->got_irqs&(got_sync|got_xoff))) {
        aborted=0x300;
        if (res) {
            printk(KERN_INFO "SIS1100[%d] write_dma: interrupted\n", sc->unit);
            aborted|=1;
        }
        if (sc->got_irqs&got_sync) {
            printk(KERN_WARNING "SIS1100[%d] write_dma: synchronisation lost\n",
                    sc->unit);
            aborted|=2;
        }
        if (sc->got_irqs&got_xoff) {
            printk(KERN_CRIT "SIS1100[%d] write_dma: got xoff (irqs=0x%04x)\n",
                    sc->unit, sc->got_irqs);
            aborted|=4;
        }
    }

    sis1100_disable_irq(sc, 0, irq_s_xoff|irq_prot_end);

    spin_lock_irq(&current->sigmask_lock);
    current->blocked = oldset;
    recalc_sigpending(current);
    spin_unlock_irq(&current->sigmask_lock);

    *prot_error=sis1100readreg(sc, prot_error);

    if (aborted) {
        *prot_error=aborted;
        count=-EIO;
    } else if (*prot_error) {
        if (*prot_error&0x200) {
            u_int32_t addr;
            head=0x0f000002;
            addr = (int)&((struct sis3100_reg*)(0))->dma_write_counter;
            sis1100writereg(sc, t_hdr, head);
            sis1100writereg(sc, t_adl, addr);
            do {
	        tmp=sis1100readreg(sc, prot_error);
            } while (tmp==0x005);
            if (tmp!=0) {
                 printk(KERN_WARNING "SIS1100[%d] write_dma: "
                    "read count after error: prot_error=0x%03x\n",
                    sc->unit, tmp);
                count=-EIO;
            } else {
                count=sis1100readreg(sc, tc_dal);
            }
        } else {
            count=-EIO;
        }
    }

    if (aborted) dump_glink_status(sc, "after abort", 1);

    unmap_kiobuf(iobuf);

    return count;
}

ssize_t
sis1100_write_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 */
    const u_int8_t* data,     /* source (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;

    if (!access_ok(VERIFY_READ, data, count)) return -EFAULT;

    down(&sc->sem_hw);
    while (count && (res>0) && (*prot_err==0)) {
        res=_sis1100_write_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;
}
