/* $ZEL: sis1100_irq.c,v 1.16 2002/05/29 15:54:24 wuestner Exp $ */

#include "Copyright"

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

#include <dev/pci/sis1100var.h>

#undef DEBUG_IRQ

int
sis1100_get_irq(struct SIS1100_softc* sc,
    u_int32_t* plx_mask, u_int32_t* sis_mask)
{
    *plx_mask=plxreadreg(sc, INTCSR);
    *sis_mask=sis1100readreg(sc, cr);
    return 0;
}

int
sis1100_enable_irq(struct SIS1100_softc* sc,
    u_int32_t plx_mask, u_int32_t sis_mask)
{
    unsigned long flags;
#ifdef DEBUG_IRQ
    printk(KERN_INFO "sis1100_enable_irq(plx=0x%08x, sis=0x%04x)\n",
    	plx_mask, sis_mask);
#endif
    if (plx_mask) {
        spin_lock_irqsave(&sc->lock_intcsr, flags);
        plxwritereg(sc, INTCSR, plxreadreg(sc, INTCSR)|plx_mask);
        spin_unlock_irqrestore(&sc->lock_intcsr, flags);
    }

    if (sis_mask) {
        sis_mask&=sis1100_all_irq;
/* clear pending irqs */
        sis1100writereg(sc, sr, sis_mask);
/* enable irqs */
        sis1100writereg(sc, cr, sis_mask);
    }

#ifdef DEBUG_IRQ
    {
    u_int32_t  intcsr, control;
    
    intcsr=plxreadreg(sc, INTCSR);
    control=sis1100readreg(sc, cr);
    printk(KERN_INFO "sis1100_enable_irq     0x%08x      0x%04x)\n",
            intcsr, control);
    }
#endif
    return 0;
}

int
sis1100_disable_irq(struct SIS1100_softc* sc,
    u_int32_t plx_mask, u_int32_t sis_mask)
{
    unsigned long flags;
#ifdef DEBUG_IRQ
    printk(KERN_INFO "sis1100_disable_irq(plx=0x%08x, sis=0x%04x)\n",
    	plx_mask, sis_mask);
#endif
    if (plx_mask) {
        spin_lock_irqsave(&sc->lock_intcsr, flags);
        plxwritereg(sc, INTCSR, plxreadreg(sc, INTCSR)&~plx_mask);
        spin_unlock_irqrestore(&sc->lock_intcsr, flags);
    }

    if (sis_mask) sis1100writereg(sc, cr, (sis_mask&sis1100_all_irq)<<16);

#ifdef DEBUG_IRQ
    {
    u_int32_t  intcsr, control;
    
    intcsr=plxreadreg(sc, INTCSR);
    control=sis1100readreg(sc, cr);
    printk(KERN_INFO "sis1100_disable_irq     0x%08x      0x%04x)\n",
            intcsr, control);
    }
#endif
    return 0;
}

/*Doorbell | Local | DMA0 | DMA1 */
#define HANDLED_IRQS (0x2000|0x8000|0x200000|0x400000)

int
SIS1100_intr(struct SIS1100_softc* sc)
{
    unsigned long flags;
    u_int32_t intcsr;
    int local=0;
    int schedule_vme_irq_task=0;

    intcsr=plxreadreg(sc, INTCSR);
    if (!(intcsr & HANDLED_IRQS)) return 0;

#ifdef DEBUG_IRQ
    printk(KERN_INFO "SIS1100[%d]: intcsr=0x%08x\n", intcsr);
#endif
    if (intcsr&0x2000) { /* Doorbell Interrupt (== VME IRQ) */
        u_int32_t help=plxreadreg(sc, L2PDBELL);
        /*sc->doorbell|=plxreadreg(sc, L2PDBELL);*/
        sc->doorbell|=help;
        /*plxwritereg(sc, L2PDBELL, sc->doorbell);*/
        plxwritereg(sc, L2PDBELL, help);
#ifdef DEBUG_IRQ
        printk(KERN_INFO "help=%08x shadow=%08x\n", help, sc->doorbell);
#endif
        schedule_vme_irq_task=1;
    }
    if (intcsr&0x8000) { /* local Interrupt */
#ifdef DEBUG_IRQ
        printk(KERN_INFO "SIS1100[%d]: local IRQ\n", sc->unit);
#endif
        local=1;
    }
    if (intcsr&0x200000) { /* DMA0 Interrupt */
#ifdef DEBUG_IRQ
        printk(KERN_INFO "SIS1100[%d]: DMA0 IRQ\n", sc->unit);
#endif
        spin_lock_irqsave(&sc->lock_intcsr, flags);
        plxwritereg(sc, INTCSR, intcsr&~(1<<18));
        spin_unlock_irqrestore(&sc->lock_intcsr, flags);
        sc->got_irqs|=got_dma0;
        wake_up_interruptible(&sc->sis1100_wait);
    }
    if (intcsr&0x400000) { /* DMA1 Interrupt */
#ifdef DEBUG_IRQ
        printk(KERN_INFO "SIS1100[%d]: DMA1 IRQ\n", sc->unit);
#endif
        spin_lock_irqsave(&sc->lock_intcsr, flags);
        plxwritereg(sc, INTCSR, intcsr&~(1<<19));
        spin_unlock_irqrestore(&sc->lock_intcsr, flags);
        sc->got_irqs|=got_dma1;
        wake_up_interruptible(&sc->sis1100_wait);
    }
    if (local) {
        u_int32_t status;

        status=sis1100readreg(sc, sr);

#ifdef DEBUG_IRQ
        printk(KERN_INFO "SIS1100[%d]:     status=0x%08x\n", sc->unit, status);
#endif
        if (status&irq_synch_chg) {
            sis1100_disable_irq(sc, 0, irq_synch_chg|
                irq_reset_req|irq_prot_end|irq_prot_l_err);

            sc->got_irqs|=got_sync;
            wake_up_interruptible(&sc->sis1100_wait);

            if ((status&3)==3) {
                printk(KERN_WARNING "----------------------\n");
                printk(KERN_WARNING "SIS1100[%d] %s: link is UP\n",
                        sc->unit, sc->pcidev->slot_name);
            } else {
                printk(KERN_WARNING "------------------------\n");
                printk(KERN_WARNING "SIS1100[%d] %s: link is DOWN\n",
                    sc->unit, sc->pcidev->slot_name);
                sc->old_remote_ok=sc->remote_ok;
                sc->remote_ok=0;
                schedule_vme_irq_task=1;
            }
            printk(KERN_INFO "SIS1100[%d] status =0x%08x\n", sc->unit, status);
            printk(KERN_INFO "SIS1100[%d] opt_csr=0x%08x\n",
                    sc->unit, sis1100readreg(sc, opt_csr));
            mod_timer(&sc->link_up_timer, jiffies+HZ);
        }
        if (status&irq_inh_chg)
                        printk(KERN_ALERT "SIS1100[%d]: INH_CHG\n", sc->unit);
        if (status&irq_sema_chg)
                        printk(KERN_ALERT "SIS1100[%d]: SEMA_CHG\n", sc->unit);
        if (status&irq_rec_violation)
                        printk(KERN_ALERT "SIS1100[%d]: REC_VIOLATION\n", sc->unit);
        if (status&irq_reset_req)
                        printk(KERN_ALERT "SIS1100[%d]: RESET_REQ\n", sc->unit);
        if (status&irq_dma_eot) {
#ifdef DEBUG_IRQ
            printk(KERN_INFO "SIS1100[%d]: DMA_EOT\n", sc->unit);
#endif
            sc->got_irqs|=got_eot;
            wake_up_interruptible(&sc->sis1100_wait);
        }
        if (status&irq_mbx0) printk(KERN_INFO "SIS1100[%d]: MBX0\n", sc->unit);
        if (status&irq_s_xoff) {
            printk(KERN_INFO "SIS1100[%d]: S_XOFF\n", sc->unit);
            printk(KERN_INFO "SIS1100[%d]: status=0x%08x\n", sc->unit, status);
            sc->got_irqs|=got_eot;
            wake_up_interruptible(&sc->sis1100_wait);
        }
        if (status&irq_lemo_in_chg) {
                printk(KERN_INFO "SIS1100[%d]: LEMO_IN_CHG, status=0x%08x\n",
                        sc->unit, status);
            sc->doorbell|=(status<<4)&0x30000;
            schedule_vme_irq_task=1;
        }
        if (status&irq_prot_end) {
#ifdef DEBUG_IRQ
            printk(KERN_INFO "SIS1100[%d]: PROT_END\n", sc->unit);
#endif
            sc->got_irqs|=got_end;
            wake_up_interruptible(&sc->sis1100_wait);
        }
        if (status&irq_prot_l_err) {
            /*printk(KERN_INFO "SIS1100[%d]: PROT_L_ERR\n", sc->unit);*/
            sc->got_irqs|=got_l_err;
            wake_up_interruptible(&sc->sis1100_wait);
        }
        sis1100writereg(sc, sr, status);
    }
    if (schedule_vme_irq_task) {
        if (!schedule_task(&sc->vme_irq_task))
            printk(KERN_WARNING "SIS1100[%d]: vme_irq_handler not scheduled\n",
                sc->unit);
    }
    return 1;
}
