/* Pci/pcmcia code for wavelan.c */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "etherif.h"

#include "wavelan.h"

static int
wavelanpcmciareset(Ether *ether)
{
	int i;
	char *p;
	Ctlr *ctlr;

	if((ctlr = malloc(sizeof(Ctlr))) == nil)
		return -1;

	ilock(ctlr);
	ctlr->ctlrno = ether->ctlrno;

	if (ether->port==0)
		ether->port=WDfltIOB;
	ctlr->iob = ether->port;

	if (ether->irq==0)
		ether->irq=WDfltIRQ;

	if (ioalloc(ether->port,WIOLen,0,"wavelan")<0){
	//	print("#l%d: port 0x%lx in use\n",
	//			ether->ctlrno, ether->port);
		goto abort1;
	}

	/*
	 * If id= is specified, card must match.  Otherwise try generic.
	 */
	ctlr->slot = -1;
	for(i=0; i<ether->nopt; i++){
		if(cistrncmp(ether->opt[i], "id=", 3) == 0){
			if((ctlr->slot = pcmspecial(&ether->opt[i][3], ether)) < 0)
				goto abort;
			break;
		}
	}
	if(ctlr->slot == -1){
		for (i=0; wavenames[i]; i++)
			if((ctlr->slot = pcmspecial(wavenames[i], ether))>=0)
				break;
		if(!wavenames[i]){
			DEBUG("no wavelan found\n");
			goto abort;
		}
	}

	// DEBUG("#l%d: port=0x%lx irq=%ld\n",
	//		ether->ctlrno, ether->port, ether->irq);

	if(wavelanreset(ether, ctlr) < 0){
	abort:
		iofree(ether->port);
	abort1:
		iunlock(ctlr);
		free(ctlr);
		ether->ctlr = nil;
		return -1;
	}

	for(i = 0; i < ether->nopt; i++){
		if(p = strchr(ether->opt[i], '='))
			*p = ' ';
		w_option(ctlr, ether->opt[i], strlen(ether->opt[i]));
	}

	iunlock(ctlr);
	return 0;
}

static struct {
	int vid;
	int did;
} wavelanpci[] = {
	0x1260, 0x3873,	/* Intersil Prism2.5 */
	0x1737,	0x0019,	/* Linksys WPC-11 untested */
};

static Ctlr *ctlrhead, *ctlrtail;

static void
wavelanpciscan(void)
{
	int i;
	void *mem;
	Pcidev *p;
	Ctlr *ctlr;

	p = nil;
	while(p = pcimatch(p, 0, 0)){
		for(i=0; i<nelem(wavelanpci); i++)
			if(p->vid == wavelanpci[i].vid && p->did == wavelanpci[i].did)
				break;
		if(i==nelem(wavelanpci))
			continue;

		/*
		 * On the Prism, bar[0] is the memory-mapped register address (4KB),
		 */
		if(p->mem[0].size != 4096){
			print("wavelanpci: %.4ux %.4ux: unlikely mmio size\n", p->vid, p->did);
			continue;
		}

		ctlr = malloc(sizeof(Ctlr));
		ctlr->pcidev = p;
		mem = vmap(p->mem[0].bar&~0xF, p->mem[0].size);
		if(mem == nil){
			print("wavelanpci: %.4ux %.4ux: vmap 0x%.8lux %d failed\n", p->vid, p->did, p->mem[0].bar&~0xF, p->mem[0].size);
			free(ctlr);
			continue;
		}
		ctlr->mmb = mem;
		if(ctlrhead != nil)
			ctlrtail->next = ctlr;
		else
			ctlrhead = ctlr;
		ctlrtail = ctlr;
		pcisetbme(p);
	}
}

static int
wavelanpcireset(Ether *ether)
{
	int i;
	char *p;
	Ctlr *ctlr;

	if(ctlrhead == nil)
		wavelanpciscan();

	/*
	 * Allow plan9.ini to set vid, did?
	 */
	for(ctlr=ctlrhead; ctlr!=nil; ctlr=ctlr->next)
		if(ctlr->active == 0)
			break;
	if(ctlr == nil)
		return -1;

	ctlr->active = 1;
	ilock(ctlr);
	ether->irq = ctlr->pcidev->intl;
	ether->tbdf = ctlr->pcidev->tbdf;

	/*
	 * Really hard reset.
	 */
	csr_outs(ctlr, WR_PciCor, 0x0080);
	delay(250);
	csr_outs(ctlr, WR_PciCor, 0x0000);
	delay(500);
	for(i=0; i<2*10; i++){
		if(!(csr_ins(ctlr, WR_Cmd)&WCmdBusy))
			break;
		delay(100);
	}
	if(i >= 2*10)
		print("wavelan pci %.4ux %.4ux: reset timeout %.4ux\n",
			ctlr->pcidev->vid, ctlr->pcidev->did, csr_ins(ctlr, WR_Cmd));

	if(wavelanreset(ether, ctlr) < 0){
		iunlock(ctlr);
		ether->ctlr = nil;
		return -1;
	}

	for(i = 0; i < ether->nopt; i++){
		if(p = strchr(ether->opt[i], '='))
			*p = ' ';
		w_option(ctlr, ether->opt[i], strlen(ether->opt[i]));
	}
	iunlock(ctlr);
	return 0;
}
	
void
etherwavelanlink(void)
{
	addethercard("wavelan", wavelanpcmciareset);
	addethercard("wavelanpci", wavelanpcireset);
}
