/* isacard.c: pnp ISA card resource list reader/kernel list builder module */
/*
 * $Header: /root/pnp/module/RCS/isacard.c,v 1.5 1996/06/12 18:56:34 root Exp $
 *
 * $Log: isacard.c,v $
 * Revision 1.5  1996/06/12  18:56:34  root
 * standard internal i/f & BIOS read/query
 *
 * Revision 1.4  1996/06/09  10:40:34  root
 * card configuration now done
 *
 * Revision 1.3  1996/06/04  21:20:54  root
 * EISA ID order normalized
 *
 * Revision 1.2  1996/06/02  14:07:23  root
 * current config read implemented on ISA cards
 *
 * Revision 1.1  1996/05/28  20:37:55  root
 * Read-only index version works
 *
 *
 */

/*
 * (c) Copyright 1996  D.W.Howells <dwh@nexor.co.uk>,
 */

#include <linux/config.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/sched.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <asm/dma.h>
#include <asm/io.h>
#include <asm/bitops.h>
#include "pnp_if.h"

int pnp_isa_read_cards(void);

static int pnp_isa_get_config(pnp_device *, pnp_config *);
static int pnp_isa_set_config(pnp_device *, const pnp_config *);
static int pnp_isa_set_active(pnp_device *, int);
static int pnp_isa_discover_next(void);

/* ISA card I/O interface */
pnp_interface pnp_isa_interface = {
    "ISA",
    &pnp_isa_get_config,
    &pnp_isa_set_config,
    &pnp_isa_set_active,
    NULL
};

/* card and device lists */
pnp_card *pnp_card_list = NULL;
pnp_device *pnp_device_list = NULL;
csn_t pnp_isa_last_csn = 0;

/* PnP read port address */
u_short pnp_isa_rdport;

/*****************************************************************************/
/* send the PnP initialization key */
static u_char pnp_isa_init_key[] = {
    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
};

/*****************************************************************************/
/* put all the cards into the 'sleep' state */
extern inline void pnp_isa_send_init_key(void)
{
    outsb(PNP_ADDRESS_PORT,pnp_isa_init_key,sizeof(pnp_isa_init_key));
}

/*****************************************************************************/
/* checksum a serial key */
extern inline u_char pnp_isa_serial_check(u_char *key)
{
    u_char chk, chk2;
    int loop, loop2;

    chk = 0x6A;

    for (loop2=0; loop2<=7; loop2++) {
	for (loop=0; loop<=7; loop++) {
	    chk2 = (test_bit(loop,key+loop2)) ? 0x01 : 0x00;
	    chk2 ^= chk;
	    chk >>= 1;
	    chk2 ^= chk;
	    chk &= 0x7F;
	    chk |= (chk2 << 7) & 0x80;
	}
    }
    return chk;

} /* end pnp_isa_serial_check() */

/*****************************************************************************/
/* discover next card by reading a serial key from it */
static int pnp_isa_discover_next()
{
    u_char ch, ch2, inbuf[9];
    int loop, loop2;

    /* send wake(0) to put all undiscovered cards in 'isolation' state */
    addrpnp(PNPREG_WAKE);
    outpnp(0);

    /* send set read_data port */
    addrpnp(PNPREG_SET_RD_DATA);
    outpnp(pnp_isa_rdport>>2);

    /* read the serial key */
    addrpnp(PNPREG_SERIAL_ISOLATION);
    udelay(750);
    for (loop2=0; loop2<=8; loop2++) {
	for (loop=0; loop<=7; loop++) {
	    /* read next pair 0xFFFF->0bit 0x55AA->1bit */
	    udelay(250);
	    ch = inpnp();
	    ch2 = inpnp();

	    if ((ch==0xFF && ch2!=0xFF) || (ch==0x55 && ch2!=0xAA))
		return -EIO;

	    if (ch==0xFF)
		clear_bit(loop,inbuf+loop2);
	    else
		set_bit(loop,inbuf+loop2);
	}
    }

    /* make sure it was correct */
    if (pnp_isa_serial_check(inbuf)!=inbuf[8])
	return -EIO;

    /* send a CSN to the card */
    pnp_isa_last_csn++;
    addrpnp(PNPREG_CARD_SELECT);
    outpnp(pnp_isa_last_csn);

    printk("card[%02x]: %08x%08x\n",
	   pnp_isa_last_csn,*(u_int*)inbuf,*(u_int*)(inbuf+4));

    return 0;
} /* end pnp_isa_discover_next() */

/*****************************************************************************/
/* discover and initialize the PnP cards in the system */
int pnp_isa_discover(void)
{
    u_short port_ok = 0;

    /* determine the read port to start on */
    pnp_isa_rdport = PNP_READ_DATA_PORT_FIRST;

    /* put all the cards into the 'sleep' state */
    pnp_isa_send_init_key();

    /* reset all CSN's */
    addrpnp(PNPREG_CONFIG_CONTROL);
    outpnp(0x04);

    /* step through the port range looking for a good port */
    while (pnp_isa_rdport<PNP_READ_DATA_PORT_LAST && pnp_isa_last_csn<0xFF) {
	/* make sure that the port isn't claimed */
	if (check_region(pnp_isa_rdport,1)<0) {
	    /* try a different port */
	    pnp_isa_rdport += 8;
	    continue;
	}

	/* try to read off each card on the current port */
	if (pnp_isa_discover_next()==0) {
	    port_ok = pnp_isa_rdport;	/* remember the port if it worked */
	    continue;
	}

	/* try a different port */
	if (port_ok!=0)
	    break;
	pnp_isa_rdport += 8;
    }

    /* return cards to wait-for-key state */
    addrpnp(PNPREG_CONFIG_CONTROL);
    outpnp(0x02);

    /* indicate whether any cards were found */
    if (port_ok==0)
	return -ENODEV;		/* no */

    /* set the read port to a good one */
    pnp_isa_rdport = port_ok;

    return 0;

} /* end pnp_isa_discover() */

/*****************************************************************************/
/* get a byte from the resource register (wait for status reg first) */
extern inline u_char pnp_isa_get_res_data(int *ok)
{
    int loop;

    if (!*ok)
	return 0;

    /* wait for status ready */
    addrpnp(PNPREG_STATUS);
    for (loop=32767; loop>0 && !(inpnp()&0x01); loop--)
	continue;

    if (loop<=0) {
	*ok = 0;
	return 0;
    }

    /* get byte */
    addrpnp(PNPREG_RESOURCE_DATA);
    return inpnp();
} /* end pnp_isa_get_res_data() */

/*****************************************************************************/
/* read specified card's resource data */
static int pnp_isa_read_card(u_char *buffer,csn_t csn)
{
    pnp_card *card, **pcard;
    pnp_device *device, **pdevice;
    u_char serialkey[9], *ptr;
    int loop, ok = 1, count = 4000-2;

    /* find where to insert new card and device */
    for (pcard=&pnp_card_list; *pcard; pcard=&(*pcard)->pc_next) {;}
    for (pdevice=&pnp_device_list; *pdevice; pdevice=&(*pdevice)->pd_next) {;}

    /* send wake(csn) to access specified card */
    addrpnp(PNPREG_WAKE);
    outpnp(csn);

    /* read the card's serial key */
    ptr = serialkey;
    for (loop=0; loop<9 && ok; loop++) {
	*ptr = pnp_isa_get_res_data(&ok);
	ptr++;
    }

    /* read from the card till the end tag is found */
    ptr = buffer;
    while (ok && count>0) {
	*ptr = pnp_isa_get_res_data(&ok);
	count--;

	/* stop when the end tag is found */
	if (*ptr==0x79) {
	    ptr++;
	    *(ptr++) = pnp_isa_get_res_data(&ok);
	    break;
	}

	/* transfer large descriptors */
	if (*ptr&0x80) {
	    ptr++;
	    *(ptr++) = pnp_isa_get_res_data(&ok);
	    *(ptr++) = pnp_isa_get_res_data(&ok);
	    loop = *(u_short*)(ptr-2);
	    count -= 2 + loop;
	    if (!ok || count<0)
		break;
	    for (; ok && loop>0; loop--)
		*(ptr++) = pnp_isa_get_res_data(&ok);
	    continue;
	}

	/* transfer other small descriptors */
	loop = *ptr&0x07;
	count -= loop;
	if (!ok || count<0)
	    break;
	ptr++;
	for (; ok && loop>0; loop--)
	    *(ptr++) = pnp_isa_get_res_data(&ok);
    }

    if (!ok)
	return -EIO;

    /*=======================================================================*/
    /* allocate a card descriptor */
    card = (pnp_card *) kmalloc(sizeof(*card),GFP_KERNEL);
    if (!card)
	return -ENOMEM;

    /* make note of the card's interface method */
    card->pc_interface = &pnp_isa_interface;

    /* store the resource list */
    card->pc_resources = (u_char*) kmalloc(ptr-buffer,GFP_KERNEL);
    if (!card->pc_resources) {
	kfree(card);
	return -ENOMEM;
    }
    card->pc_ressize = ptr-buffer;
    memcpy(card->pc_resources,buffer,card->pc_ressize);

    /* extract stuff from the serial key */
    ((u_char*)&card->pc_id)[0] = serialkey[0];
    ((u_char*)&card->pc_id)[1] = serialkey[1];
    ((u_char*)&card->pc_id)[2] = serialkey[2];
    ((u_char*)&card->pc_id)[3] = serialkey[3];
    card->pc_unique = *(u_int*)(serialkey+4);

    card->pc_csn = csn;
    card->pc_ndev = 0;

    /* link the card in */
    card->pc_next = NULL;
    *pcard = card;
    pcard = &card->pc_next;

    /*=======================================================================*/
    /* allocate device descriptors */
    ptr = card->pc_resources;
    while (1) {
	/* stop when the end tag is found */
	if ((*ptr&0x78)==(PNPSTAG_END<<3))
	    break;

	/* create a new logical device node if one found */
	if ((*ptr&0x78)==(PNPSTAG_LOGICAL_DEV_ID<<3)) {
	    device = (pnp_device *) kmalloc(sizeof(*device),GFP_KERNEL);
	    if (!device)
		return -ENOMEM;

	    device->pd_card = card;
	    device->pd_driver = NULL;
	    ((u_char*)&device->pd_id)[0] = ptr[1];
	    ((u_char*)&device->pd_id)[1] = ptr[2];
	    ((u_char*)&device->pd_id)[2] = ptr[3];
	    ((u_char*)&device->pd_id)[3] = ptr[4];
	    device->pd_resoffset = ptr - card->pc_resources;
	    device->pd_dev = card->pc_ndev;
	    card->pc_ndev++;

	    /* link the device in */
	    device->pd_next = NULL;
	    *pdevice = device;
	    pdevice = &device->pd_next;

	    ptr += (*ptr&0x07) + 1;
	}

	/* skip large descriptors */
	if (*ptr&0x80) {
	    ptr++;
	    ptr += *(u_short*)ptr + 2;
	    continue;
	}

	/* skip small descriptors */
	ptr += (*ptr&0x07) + 1;
    }

    return 0;
} /* end pnp_isa_read_card() */

/*****************************************************************************/
/* read all the card data on the system */
int pnp_isa_read_cards(void)
{
    u_char *page;
    csn_t csn;
    int rt;

    /* acquire some space to use as a buffer */
    page = (u_char*) get_free_page(GFP_KERNEL);
    if (!page)
	return -ENOMEM;

    pnp_isa_send_init_key();

    /* set the card's read port */
    addrpnp(PNPREG_SET_RD_DATA);
    outpnp(pnp_isa_rdport>>2);

    rt = 0;
    for (csn=1; csn<=pnp_isa_last_csn; csn++) {
	rt = pnp_isa_read_card(page,csn);
	if (rt<0)
	    break;
    }

    /* return cards to wait-for-key state */
    addrpnp(PNPREG_CONFIG_CONTROL);
    outpnp(0x02);

    free_page((u_long)page);

    return rt;
} /* end pnp_isa_read_cards() */

/*****************************************************************************/
/* mem32 port ranges */
static u_short mem32port[4] = {
    PNPREG_MEM32_0, PNPREG_MEM32_1, PNPREG_MEM32_2, PNPREG_MEM32_3
};

/* read a configuration record from a card */
static int pnp_isa_get_config(pnp_device *device, pnp_config *rec)
{
    u_short loop;

    /* wake up appropriate card first */
    pnp_isa_send_init_key();
    addrpnp(PNPREG_SET_RD_DATA);		/* set read port */
    outpnp(pnp_isa_rdport>>2);
    addrpnp(PNPREG_WAKE);    			/* send wake(csn) */
    outpnp(device->pd_card->pc_csn);
    addrpnp(PNPREG_LOGICAL_DEVICE_SELECT);	/* select device */
    outpnp(device->pd_dev);

    /* get active register */
    addrpnp(PNPREG_ACTIVATE);
    rec->pc_active = inpnp();

    /* read IRQ's first */
    rec->pc_s.irq = 0;
    for (loop=0; loop<2; loop++) {
	addrpnp(PNPREG_IRQ_0+loop*2+0);		/* get IRQ num */
	rec->pc_irq[loop].num = inpnp();
	addrpnp(PNPREG_IRQ_0+loop*2+1);		/* get IRQ type */
	rec->pc_irq[loop].type = inpnp();
	if (rec->pc_irq[loop].num)		/* note if used */
	    set_bit(loop,&rec->pc_s.irq);
    }

    /* read DMA's next */
    rec->pc_s.dma = 0;
    for (loop=0; loop<2; loop++) {
	addrpnp(PNPREG_DMA_0+loop*1+0);		/* get DMA chan */
	rec->pc_dma[loop].chan = inpnp();
	if (rec->pc_dma[loop].chan!=4)
	    set_bit(loop,&rec->pc_s.dma);	/* note if used */
    }

    /* then IO port ranges */
    rec->pc_s.io = 0;
    for (loop=0; loop<8; loop++) {
	addrpnp(PNPREG_IO_0+loop*2+0);		/* get IO port base */
	rec->pc_io[loop].base = inpnp()<<8;
	addrpnp(PNPREG_IO_0+loop*2+1);
	rec->pc_io[loop].base += inpnp();
	if (rec->pc_io[loop].base)
	    set_bit(loop,&rec->pc_s.io);	/* note if used */
    }

    /* followed by MEM ranges */
    rec->pc_s.mem = 0;
    for (loop=0; loop<4; loop++) {
	addrpnp(PNPREG_MEM_0+loop*8+0);		/* get MEM base */
	(u_int)rec->pc_mem[loop].base = inpnp()<<16;
	addrpnp(PNPREG_MEM_0+loop*8+1);
	(u_int)rec->pc_mem[loop].base += inpnp()<<8;
	addrpnp(PNPREG_MEM_0+loop*8+2);		/* get MEM control */
	rec->pc_mem[loop].type = inpnp();
	addrpnp(PNPREG_MEM_0+loop*8+3);		/* get MEM top */
	(u_int)rec->pc_mem[loop].top = inpnp()<<16;
	addrpnp(PNPREG_MEM_0+loop*8+4);
	(u_int)rec->pc_mem[loop].top += inpnp()<<8;
	if (rec->pc_mem[loop].base) {
	    set_bit(loop,&rec->pc_s.mem);	/* note if used */
	    if (!(rec->pc_mem[loop].type&0x01))
		(u_int)rec->pc_mem[loop].top +=
		    (u_int)rec->pc_mem[loop].base + 1;
	    rec->pc_mem[loop].type >>= 1;
	}
    }

    /* and finally MEM32 ranges */
    for (loop=0; loop<4; loop++) {
	if (test_bit(loop,&rec->pc_s.mem))	/* can't mix 24-bit & 32-bit */
	    continue;
	addrpnp(mem32port[loop]+0);		/* get MEM base */
	(u_int)rec->pc_mem[loop].base = inpnp()<<24;
	addrpnp(mem32port[loop]+1);
	(u_int)rec->pc_mem[loop].base = inpnp()<<16;
	addrpnp(mem32port[loop]+2);
	(u_int)rec->pc_mem[loop].base = inpnp()<<8;
	addrpnp(mem32port[loop]+3);
	(u_int)rec->pc_mem[loop].base += inpnp();
	addrpnp(mem32port[loop]+4);		/* get MEM control */
	rec->pc_mem[loop].type = inpnp();
	addrpnp(mem32port[loop]+5);		/* get MEM top */
	(u_int)rec->pc_mem[loop].top = inpnp()<<24;
	addrpnp(mem32port[loop]+6);
	(u_int)rec->pc_mem[loop].top += inpnp()<<16;
	addrpnp(mem32port[loop]+7);
	(u_int)rec->pc_mem[loop].top += inpnp()<<8;
	addrpnp(mem32port[loop]+8);
	(u_int)rec->pc_mem[loop].top += inpnp();
	if (rec->pc_mem[loop].base) {
	    set_bit(loop,&rec->pc_s.mem);	/* note if used */
	    if (!(rec->pc_mem[loop].type&0x01))
		(u_int)rec->pc_mem[loop].top += 
		    (u_int)rec->pc_mem[loop].base + 1;
	    rec->pc_mem[loop].type >>= 1;
	}
    }

    /* return cards to wait-for-key state */
    addrpnp(PNPREG_CONFIG_CONTROL);
    outpnp(0x02);

    return 0;
} /* end pnp_isa_get_config() */

/*****************************************************************************/
/* store a configuration record into a card */
static int pnp_isa_set_config(pnp_device *device, const pnp_config *rec)
{
    u_short loop;

    /* wake up appropriate card first */
    pnp_isa_send_init_key();
    addrpnp(PNPREG_SET_RD_DATA);		/* set read port */
    outpnp(pnp_isa_rdport>>2);
    addrpnp(PNPREG_WAKE);    			/* send wake(csn) */
    outpnp(device->pd_card->pc_csn);
    addrpnp(PNPREG_LOGICAL_DEVICE_SELECT);	/* select device */
    outpnp(device->pd_dev);

    /* set IRQ's first */
    for (loop=0; loop<2; loop++) {
	addrpnp(PNPREG_IRQ_0+loop*2+0);		/* set IRQ num */
	if (test_bit(loop,&rec->pc_s.irq))
	    outpnp(rec->pc_irq[loop].num);
	else
	    outpnp(0);				/* disable */
	
	addrpnp(PNPREG_IRQ_0+loop*2+1);		/* set IRQ type */
	outpnp(rec->pc_irq[loop].type);
    }

    /* set DMA's next */
    for (loop=0; loop<2; loop++) {
	addrpnp(PNPREG_DMA_0+loop*1+0);		/* get DMA chan */
	if (test_bit(loop,&rec->pc_s.dma))
	    outpnp(rec->pc_dma[loop].chan);
	else
	    outpnp(4);
    }

    /* then IO port ranges */
    for (loop=0; loop<8; loop++) {
	u_short iop;

	if (test_bit(loop,&rec->pc_s.io))
	    iop = rec->pc_io[loop].base;
	else
	    iop = 0;
	addrpnp(PNPREG_IO_0+loop*2+0);		/* set IO port base */
	outpnp((iop>>8)&0xFF);
	addrpnp(PNPREG_IO_0+loop*2+1);
	outpnp(iop&0xFF);
    }

    if (!rec->pc_memtype) {
	/* followed by MEM ranges */
	for (loop=0; loop<4; loop++) {
	    u_char type;
	    u_int base;
	    if (test_bit(loop,&rec->pc_s.mem))
		base = (u_int)rec->pc_mem[loop].base;
	    else
		base = 0;
	    addrpnp(PNPREG_MEM_0+loop*8+0);		/* set MEM base */
	    outpnp((base>>16)&0xFF);
	    addrpnp(PNPREG_MEM_0+loop*8+1);
	    outpnp((base>>8)&0xFF);

	    addrpnp(PNPREG_MEM_0+loop*8+2);		/* get MEM control */
	    type = inpnp() & 0x01;
	    type |= rec->pc_mem[loop].type << 1;
	    addrpnp(PNPREG_MEM_0+loop*8+2);		/* set MEM control */
	    outpnp(type);

	    base = (u_int)rec->pc_mem[loop].top;
	    if (!(type&0x01))
		base = ~(base - (u_int)rec->pc_mem[loop].base - 1);
	    addrpnp(PNPREG_MEM_0+loop*8+3);	/* set MEM top/range len */
	    outpnp((base>>16)&0xFF);
	    addrpnp(PNPREG_MEM_0+loop*8+4);
	    outpnp((base>>8)&0xFF);
	}
    }
    else {
	/* and finally MEM32 ranges */
	for (loop=0; loop<4; loop++) {
	    u_char type;
	    u_int base;
	    if (test_bit(loop,&rec->pc_s.mem))
		base = (u_int)rec->pc_mem[loop].base;
	    else
		base = 0;
	    addrpnp(mem32port[loop]+0);		/* set MEM32 base */
	    outpnp((base<<24)&0xFF);
	    addrpnp(mem32port[loop]+1);
	    outpnp((base<<16)&0xFF);
	    addrpnp(mem32port[loop]+2);
	    outpnp((base<<8)&0xFF);
	    addrpnp(mem32port[loop]+3);
	    outpnp(base&0xFF);

	    addrpnp(mem32port[loop]+4);		/* get MEM32 control */
	    type = inpnp() & 0x01;
	    type |= rec->pc_mem[loop].type << 1;
	    addrpnp(mem32port[loop]+4);		/* set MEM32 control */
	    outpnp(type);

	    base = (u_int)rec->pc_mem[loop].top;
	    if (!(type&0x01))
		base = ~(base - (u_int)rec->pc_mem[loop].base - 1);
	    addrpnp(mem32port[loop]+5);		/* set MEM top/range len */
	    outpnp((base<<24)&0xFF);
	    addrpnp(mem32port[loop]+6);
	    outpnp((base<<16)&0xFF);
	    addrpnp(mem32port[loop]+7);
	    outpnp((base<<8)&0xFF);
	    addrpnp(mem32port[loop]+8);
	    outpnp(base&0xFF);
	}
    }

    /* return cards to wait-for-key state */
    addrpnp(PNPREG_CONFIG_CONTROL);
    outpnp(0x02);

    return 0;
} /* end pnp_isa_set_config() */

/*****************************************************************************/
/* set card-device activation status */
static int pnp_isa_set_active(pnp_device *device, int activate)
{
    /* wake up appropriate card first */
    pnp_isa_send_init_key();
    addrpnp(PNPREG_SET_RD_DATA);		/* set read port */
    outpnp(pnp_isa_rdport>>2);
    addrpnp(PNPREG_WAKE);    			/* send wake(csn) */
    outpnp(device->pd_card->pc_csn);
    addrpnp(PNPREG_LOGICAL_DEVICE_SELECT);	/* select device */
    outpnp(device->pd_dev);

    /* set active register */
    addrpnp(PNPREG_ACTIVATE);
    outpnp(activate?0x01:0x00);

    /* return cards to wait-for-key state */
    addrpnp(PNPREG_CONFIG_CONTROL);
    outpnp(0x02);

    return 0;
} /* end pnp_isa_set_active() */
