sys/arch/i386/stand/lib/isapnp.c
/*	$NetBSD: isapnp.c,v 1.5 2008/12/14 17:03:43 christos Exp $	 */

/*
 * Copyright (c) 1997
 *	Matthias Drochner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/*
 * minimal ISA PnP implementation: find adapter, return settings (1 IO and 1
 * DMA only for now)
 */

#include <sys/types.h>
#include <machine/pio.h>

#include <lib/libsa/stand.h>

#include <libi386.h>

#include "isapnpvar.h"

#define PNPADDR 0x0279
#define PNPWDATA 0x0a79
#define PNPRDATAMIN 0x0203
#define PNPRDATAMAX 0x03ff

enum {
	DATAPORT,
	ISOL,
	CONTROL,
	WAKE,
	RESDATA,
	RESSTAT,
	SETCSN,
	SETLDEV
};

#define MEMBASE 0x40
#define IOBASE 0x60
#define INTBASE 0x70
#define DMABASE 0x74

static int      pnpdataport;

static int
getiobase(int nr)
{
	unsigned short  iobase;

	outb(PNPADDR, SETLDEV);
	outb(PNPWDATA, 0);	/* subdev 0 */

	outb(PNPADDR, IOBASE + nr * 2);
	iobase = (inb(pnpdataport) << 8);
	outb(PNPADDR, IOBASE + nr * 2 + 1);
	iobase |= inb(pnpdataport);

	return iobase;
}

static int
getdmachan(int nr)
{
	unsigned char   dmachannel;

	outb(PNPADDR, SETLDEV);
	outb(PNPWDATA, 0);	/* subdev 0 */

	outb(PNPADDR, DMABASE + nr);
	dmachannel = inb(pnpdataport) & 0x07;

	return dmachannel;
}

struct cardid {
	unsigned char   eisaid[4];
	unsigned int    serial;
	unsigned char   crc;
};

/*
 do isolation, call pnpscanresc() in board config state
 */
static int
pnpisol(int csn)
{
	unsigned char   buf[9];
	int             i, j;
	struct cardid  *id;
	unsigned char   crc = 0x6a;

	/*
	 * do 72 pairs of reads from ISOL register all but 1 go to sleep
	 * state (ch. 3.3)
	 */
	outb(PNPADDR, ISOL);
	delay(1000);

	for (i = 0; i < 9; i++) {
		for (j = 0; j < 8; j++) {
			unsigned char   a, b;
			int             bitset;

			a = inb(pnpdataport);
			b = inb(pnpdataport);
			if ((a == 0x55) && (b == 0xaa))
				bitset = 1;
			else if ((a == 0xff) && (b == 0xff))
				bitset = 0;
			else
				return -1;	/* data port conflict */

			buf[i] = (buf[i] >> 1) | (bitset << 7);

			if (i < 8)	/* calc crc for first 8 bytes (app.
					 * B.2) */
				crc = (crc >> 1) |
				  ((bitset != ((crc & 1) == !(crc & 2))) << 7);

			delay(250);
		}
	}
	id = (struct cardid *) buf;

	if (id->crc != crc)
		return 0;	/* normal end */

	outb(PNPADDR, SETCSN);
	outb(PNPWDATA, csn);	/* set csn for winning card and put it to
				 * config state */

	return (id->eisaid[0] << 24) | (id->eisaid[1] << 16)
		| (id->eisaid[2] << 8) | (id->eisaid[3]);
}

static void
pnpisolreset(void)
{
	outb(PNPADDR, WAKE);
	outb(PNPWDATA, 0);	/* put all remaining cards to isolation state */
}

/*
 send initiation sequence (app. B.1)
 */
static void
pnpinit(void)
{
	int             i;
	unsigned char   key = 0x6a;

	outb(PNPADDR, 0);
	outb(PNPADDR, 0);

	for (i = 0; i < 32; i++) {
		outb(PNPADDR, key);
		key = (key >> 1) |
			(((key & 1) == !(key & 2)) << 7);
	}
}

int
isapnp_finddev(int id, int *iobase, int *dmachan)
{
	int             csn;

	outb(PNPADDR, CONTROL);
	outb(PNPWDATA, 2);	/* XXX force wait for key */

	/* scan all allowed data ports (ch. 3.1) */
	for (pnpdataport = PNPRDATAMIN; pnpdataport <= PNPRDATAMAX;
	     pnpdataport += 4) {
		int             res, found = 0;

		pnpinit();	/* initiation sequence */

		outb(PNPADDR, CONTROL);
		outb(PNPWDATA, 4);	/* CSN=0 - only these respond to
					 * WAKE[0] */

		outb(PNPADDR, WAKE);
		outb(PNPWDATA, 0);	/* put into isolation state */

		outb(PNPADDR, DATAPORT);
		outb(PNPWDATA, pnpdataport >> 2);	/* set READ_DATA port */

		csn = 0;
		do {
			res = pnpisol(++csn);

			if ((res) == id) {
				if (iobase)
					*iobase = getiobase(0);
				if (dmachan)
					*dmachan = getdmachan(0);
				found = 1;
			}
			pnpisolreset();
		} while ((res != 0) && (res != -1));

		outb(PNPADDR, CONTROL);
		outb(PNPWDATA, 2);	/* return to wait for key */

		if (csn > 1)	/* at least 1 board found */
			return !found;

		/* if no board found, try next dataport */
	}
	return -1;		/* nothing found */
}
