/*
 * ---------------------------------------------------------------------------
 * Plug'n'play routines
 * ---------------------------------------------------------------------------
 */

#include "wlapi.h"
#include "wlpnp.h"

static unsigned char INIT_KEY_TABLE[34]={
    0x00,0x00,
    0x6A,0xB5,0xDA,0xED,0xF6,0xFB,0x7D,0xBE,
    0xDF,0x6F,0x37,0x1B,0x0D,0x86,0xC3,0x61,
    0xB0,0x58,0x2C,0x16,0x8B,0x45,0xA2,0xD1,
    0xE8,0x74,0x3A,0x9D,0xCE,0xE7,0x73,0x39
};

BOOL CompareCheckSum(unsigned char Data[])
{
    unsigned char i;
    unsigned char checksum = 0x6A;

    for (i = 0; i < 8; i++) {
        unsigned char bit;
        unsigned char ch;

        ch = Data[i];
        for (bit = 0; bit < 8; bit++) {
             unsigned char temp = (checksum^(checksum>>1)^ch) & 0x01;
             checksum >>= 1;
             ch >>= 1;
             checksum += temp << 7;
        }
    }
        
    return (checksum == Data[8]);
}

void GetResourceData(unsigned char ResourceData[], int nPort,int nCSN)
{
    int i;

    /* Wake [CSN] */
    outb(0x03, PNP_ADDR_PORT);
    outb(nCSN, PNP_WR_PORT);

    /* set read data port */
    outb(0x00, PNP_ADDR_PORT);
    outb(nPort>>2, PNP_WR_PORT);

    /* Read Resource Data */
    outb(0x04, PNP_ADDR_PORT);
    NOPLOOP(100);

    for (i=0; i<9 ; i++) {
        NOPLOOP(100);
        ResourceData[i] = InB(nPort);
    }
}

void GetSerialID(unsigned char SerialID[], int nPnPRdPort)
{
    int i,j;
    unsigned char temp1,temp2;

    /* Wake [0] */
    outb(0x03, PNP_ADDR_PORT);
    outb(0x00, PNP_WR_PORT);

    /* Set Read Data Port */
    outb(0x00, PNP_ADDR_PORT);
    outb(nPnPRdPort>>2, PNP_WR_PORT);

    /* Read 72 pair from serial isolation port */
    outb(0x01, PNP_ADDR_PORT);

    for (i=0; i<9; i++) {
        SerialID[i] = 0;        /* by fox */
        for (j=0; j<8 ; j++) {
            SerialID[i] >>= 1 ;

            temp1 = InB(nPnPRdPort);
            NOPLOOP(100);

            temp2 = InB(nPnPRdPort);
            if (temp1==0x55 && temp2==0xAA) SerialID[i]|=0x80;
            NOPLOOP(100);
        }
    }
}

/*
 * Return PnP DataPort, or 0 if failed
 */
int Find_ReadDataPort(void)
{
    unsigned char buf[9];
    int nPort;

    for (nPort = 0x203; nPort < 0x300; nPort += 4) {
        GetResourceData(buf, nPort, 1);
        if (CompareCheckSum(buf)) {
            printk("wlpnp.c: Found Data port = 0x%3.3x, by resource data\n", nPort);
            return nPort;
        }
    }

    for (nPort = 0x203; nPort <= 0x300; nPort += 4) {
        GetSerialID(buf,nPort);
        if (CompareCheckSum(buf)) {
            printk("wlpnp.c: Found Data port = 0x%3.3x, by serial ID\n", nPort);
            return nPort;
        }
    }

    return 0;
}

/*
 * Return Total CSN found
 */
int Find_TotalCSN(int nPort)
{
    unsigned char buf[9];
    int i;

    for (i = 1; i < DEV_NUM_MAX; i++) {
        GetResourceData(buf, nPort, i);
        if (!CompareCheckSum(buf)) {
            printk("wlpnp.c: Total %d CSN found\n", i-1);
            return i-1;
        }
    }
    return i;
}

void WL_GetDeviceInfo(int CSN,int nPnPReadPort, DWORD *pulBaseAddr, BYTE *pcIRQ)
{
    unsigned int iobase, irq;
    
    /* Wake [CSN] */
    outb(0x03,  PNP_ADDR_PORT);
    outb(CSN,   PNP_WR_PORT);
    outb(0x60,  PNP_ADDR_PORT);    /* IO Port Base [15:8] */
    iobase = (int) InB(nPnPReadPort);
    iobase <<= 8;
    outb(0x61, PNP_ADDR_PORT);    /* IO Port Base [7:0] */
    iobase += InB(nPnPReadPort);
    outb(0x70, PNP_ADDR_PORT);    /* IRQ Level */
    irq = (int) InB(nPnPReadPort);

    *pcIRQ          = irq;
    *pulBaseAddr    = iobase;
}

void SetupPnPCard(int CSN, int irq, int IOBASE)
{

    /* set CSN = totalCSN */
    outb(0x06, PNP_ADDR_PORT);
    outb(CSN, PNP_WR_PORT);

    /* Wake [CSN], set the card to config state */
    outb(0x03, PNP_ADDR_PORT);
    outb(CSN, PNP_WR_PORT);

    /* Set IRQ, IOBASE */
    /* IO PORT Address[15:8] */
    outb(0x60, PNP_ADDR_PORT);    
    outb((unsigned char) (IOBASE>>8)&0xFF, PNP_WR_PORT);

    /* IO PORT Address[7:0] */    
    outb(0x61, PNP_ADDR_PORT);    
    outb((unsigned char)IOBASE&0xFF, PNP_WR_PORT);
    
    /* IRQ Level */
    outb(0x70, PNP_ADDR_PORT);    
    outb((unsigned char) irq, PNP_WR_PORT);
    
    /* interrupt Type */
    outb(0x71, PNP_ADDR_PORT);    
    outb(0x02,PNP_WR_PORT);
    
    /* Active Logic Device */
    outb(0x30, PNP_ADDR_PORT);    
    outb(0x01, PNP_WR_PORT);
}

BOOL WL_PnpProbe(int nCardIndex, DWORD *pulBaseAddr, BYTE *pcIRQ)
{
    BOOL    bResult;
    int     nPnPRdPort;
    int     nSetWL;
    int     i;
    int     nTotalCSN;
    BYTE    buf[9];

    /* Return all cards to wait for key state */
    outb(0x02, PNP_ADDR_PORT);
    NOPLOOP(100);
    outb(0x02, PNP_WR_PORT);
    NOPLOOP(100);

    /* Set all card to sleep state */
    for (i = 0; i < 34; i++) {
        outb(INIT_KEY_TABLE[i], PNP_ADDR_PORT);
        NOPLOOP(100);
    }

    /* Find ReadDataPort first */
    nPnPRdPort = Find_ReadDataPort();
    if (nPnPRdPort == 0) {
        printk("wlpnp.c: Maybe no WL2420 card on your PC or no free I/O port between\n");
        printk("         0x203 and 0x300.\n");
        return FALSE;
    }

    /* get total CSN and display all setup-card information */
    nTotalCSN = Find_TotalCSN(nPnPRdPort);

    bResult = FALSE;
    nSetWL = 0;
    for (i = 1; i <= nTotalCSN; i++) {
        GetResourceData(buf, nPnPRdPort, i);
        printk("wlpnp.c: Find an CSN ID :");
        TraceBuf(buf, sizeof(buf));
        if (*((long*)buf) == WL_VENDOR_ID) {
            nSetWL++;
            
            if (nSetWL == nCardIndex) {
                WL_GetDeviceInfo(i, nPnPRdPort, pulBaseAddr, pcIRQ);
                bResult = TRUE;
                break;
            }
        }
    }
    
    /* Return all cards to wait for key state */
    outb(0x02, PNP_ADDR_PORT);
    NOPLOOP(100);
    outb(0x02, PNP_WR_PORT);
    NOPLOOP(100);

    return bResult;
}

void WL_PnpSetNonInitCard(DWORD ulBaseAddr, BYTE cIRQ)
{
    int     nPnPRdPort;
    int     nTotalCSN;
    int     i;
    BYTE    buf[9];

    /* Return all cards to wait for key state */
    outb(0x02, PNP_ADDR_PORT);
    NOPLOOP(100);
    outb(0x02, PNP_WR_PORT);
    NOPLOOP(100);

    /* Set all card to sleep state */
    for (i = 0; i < 34; i++) {
        outb(INIT_KEY_TABLE[i], PNP_ADDR_PORT);
        NOPLOOP(100);
    }

    /* Find ReadDataPort first */
    nPnPRdPort = Find_ReadDataPort();
    if (nPnPRdPort == 0) {
        printk("wlpnp.c: Maybe no WL2420 card on your PC or no free I/O port between\n");
        printk("         0x203 and 0x300.\n");
        return;
    }

    /* get total CSN and display all setup-card information */
    nTotalCSN = Find_TotalCSN(nPnPRdPort);

    /* Finding NOTSET WL2420, and open the non-opend card */
    for (i = 0; i < DEV_NUM_MAX; i++) {
        GetSerialID(buf, nPnPRdPort);

        printk("wlpnp.c: Find an ID :");
        TraceBuf(buf, sizeof(buf));

        if (!CompareCheckSum(buf))
            break;

        if (*(long*)buf == WL_VENDOR_ID){
            printk("wlpnp.c: Find a non-initialled WL24xx card, set IRQ=%d, IO=0x%3.3x\n",
                    (int) cIRQ, (int) ulBaseAddr);
            
            nTotalCSN++;
            SetupPnPCard(nTotalCSN, cIRQ, ulBaseAddr);
            break;
        } 
        else {
            /*  not wl2420 card */
            nTotalCSN++;
            /* set CSN to the isolation card */
            outb(0x06, PNP_ADDR_PORT);
            NOPLOOP(100);
            outb(nTotalCSN, PNP_WR_PORT);
            NOPLOOP(100);
        }
    }

    /* Return all cards to wait for key state */
    outb(0x02, PNP_ADDR_PORT);
    NOPLOOP(100);
    outb(0x02, PNP_WR_PORT);
    NOPLOOP(100);
}
