/*-
 * Copyright (c) 1997 Nicolas Souchu
 * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 *
 *	$Id: ppc.c,v 1.2 1997/09/01 02:08:41 bde Exp $
 *
 */
#include "ppc.h"

#if NPPC > 0

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/malloc.h>

#include <machine/clock.h>

#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/pmap.h>

#include <i386/isa/isa_device.h>

#include <dev/ppbus/ppbconf.h>
#include <i386/isa/ppcreg.h>

static int	ppcprobe(struct isa_device *);
static int	ppcattach(struct isa_device *);

struct isa_driver ppcdriver = {
	ppcprobe, ppcattach, "ppc"
};

static struct ppc_data *ppcdata[NPPC];
static int nppc = 0;

static char *ppc_types[] = {
	"SMC", "SMC FDC37C665GT", "SMC FDC37C666GT",
	"NatSemi", "PC87332", "PC87306",
	"Intel 82091AA", "Generic", 0
};

static char *ppc_modes[] = {
	"AUTODETECT", "NIBBLE", "PS/2", "EPP", "ECP+EPP", "ECP+PS/2", "ECP",
	"UNKNOWN", 0
};

static char *ppc_epp_protocol[] = { " (EPP 1.9)", " (EPP 1.7)", 0 };

/*
 * BIOS printer list - used by BIOS probe.
 */
#define	BIOS_PPC_PORTS	0x408
#define	BIOS_PORTS	(short *)(KERNBASE+BIOS_PPC_PORTS)
#define	BIOS_MAX_PPC	4

/*
 * All these functions are default actions for IN/OUT operations.
 * They may be redefined if needed.
 */
static void ppc_outsb_epp(int unit, char *addr, int cnt) {
	outsb(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); }
static void ppc_outsw_epp(int unit, char *addr, int cnt) {
	outsw(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); }
static void ppc_outsl_epp(int unit, char *addr, int cnt) {
	outsl(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); }
static void ppc_insb_epp(int unit, char *addr, int cnt) {
	insb(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); }
static void ppc_insw_epp(int unit, char *addr, int cnt) {
	insw(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); }
static void ppc_insl_epp(int unit, char *addr, int cnt) {
	insl(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); }

static char ppc_rdtr(int unit) { return r_dtr(ppcdata[unit]); }
static char ppc_rstr(int unit) { return r_str(ppcdata[unit]); }
static char ppc_rctr(int unit) { return r_ctr(ppcdata[unit]); }
static char ppc_repp(int unit) { return r_epp(ppcdata[unit]); }
static char ppc_recr(int unit) { return r_ecr(ppcdata[unit]); }
static char ppc_rfifo(int unit) { return r_fifo(ppcdata[unit]); }

static void ppc_wdtr(int unit, char byte) { w_dtr(ppcdata[unit], byte); }
static void ppc_wstr(int unit, char byte) { w_str(ppcdata[unit], byte); }
static void ppc_wctr(int unit, char byte) { w_ctr(ppcdata[unit], byte); }
static void ppc_wepp(int unit, char byte) { w_epp(ppcdata[unit], byte); }
static void ppc_wecr(int unit, char byte) { w_ecr(ppcdata[unit], byte); }
static void ppc_wfifo(int unit, char byte) { w_fifo(ppcdata[unit], byte); }

static void ppc_reset_epp_timeout(int);
static void ppc_ecp_sync(int);

static struct ppb_adapter ppc_adapter = {

	0,	/* no intr handler, filled by chipset dependent code */

	ppc_reset_epp_timeout, ppc_ecp_sync,

	ppc_outsb_epp, ppc_outsw_epp, ppc_outsl_epp,
	ppc_insb_epp, ppc_insw_epp, ppc_insl_epp,

	ppc_rdtr, ppc_rstr, ppc_rctr, ppc_repp, ppc_recr, ppc_rfifo,
	ppc_wdtr, ppc_wstr, ppc_wctr, ppc_wepp, ppc_wecr, ppc_wfifo
};

/*
 * ppc_ecp_sync()		XXX
 */
static void
ppc_ecp_sync(int unit) {

	struct ppc_data *ppc = ppcdata[unit];
	int i, r;

	r = r_ecr(ppc);
	if ((r & 0xe0) != 0x80)
		return;

	for (i = 0; i < 100; i++) {
		r = r_ecr(ppc);
		if (r & 0x1)
			return;
		DELAY(100);
	}

	printf("ppc: ECP sync failed as data still " \
		"present in FIFO.\n");

	return;
}

void
ppcintr(int unit)
{
	/* call directly upper code */
	ppb_intr(&ppcdata[unit]->ppc_link);

	return;
}

/*
 * ppc_pc873xx_detect
 *
 * Probe for a Natsemi PC873xx-family part.
 *
 * References in this function are to the National Semiconductor
 * PC87332 datasheet TL/C/11930, May 1995 revision.
 */
static int pc873xx_basetab[] = {0x0398, 0x026e, 0x015c, 0x002e, 0};
static int pc873xx_porttab[] = {0x0378, 0x03bc, 0x0278, 0};

static int
ppc_pc873xx_detect(struct ppc_data *ppc)
{
    static int	index = 0;
    int		base, idport;
    int		val, mode;
    
    while ((idport = pc873xx_basetab[index++])) {
	
	/* XXX should check first to see if this location is already claimed */

	/*
	 * Pull the 873xx through the power-on ID cycle (2.2,1.).  We can't use this
	 * to locate the chip as it may already have been used by the BIOS.
	 */
	(void)inb(idport); (void)inb(idport); (void)inb(idport); (void)inb(idport);

	/*
	 * Read the SID byte.  Possible values are :
	 *
	 * 0001xxxx	PC87332
	 * 01110xxx	PC87306
	 */
	outb(idport, PC873_SID);
	val = inb(idport + 1);
	if ((val & 0xf0) == 0x10) {
	    ppc->ppc_type = NS_PC87332;
	} else if ((val & 0xf8) == 0x70) {
	    ppc->ppc_type = NS_PC87306;
	} else {
	    if (bootverbose && (val != 0xff))
		printf("PC873xx probe at 0x%x got unknown ID 0x%x\n", idport, val);
	    continue ;		/* not recognised */
	}
	
	/*
	 * We think we have one.  Is it enabled and where we want it to be?	 
	 */
	outb(idport, PC873_FER);
	val = inb(idport + 1);
	if (!(val & PC873_PPENABLE)) {
	    if (bootverbose)
		printf("PC873xx parallel port disabled\n");
	    continue;
	}
	outb(idport, PC873_FAR);
	val = inb(idport + 1) & 0x3;
	/* XXX we should create a driver instance for every port found */
	if (pc873xx_porttab[val] != ppc->ppc_base) {
	    if (bootverbose)
		printf("PC873xx at 0x%x not for driver at port 0x%x\n",
		       pc873xx_porttab[val], ppc->ppc_base);
	    continue;
	}
	
	/* 
	 * This is the port we want.  Can we dink with it to improve
	 * our chances?
	 */
	outb(idport, PC873_PTR);
	val = inb(idport + 1);
	if (val & PC873_CFGLOCK) {
	    if (bootverbose)
		printf("PC873xx locked\n");

	    /* work out what mode we're in */
	    mode = PPB_NIBBLE;		/* worst case */
	    
	    outb(idport, PC873_PCR);
	    val = inb(idport + 1);
	    if ((val & PC873_EPPEN) && (val & PC873_EPP19)) {
		outb(idport, PC873_PTR);
		val = inb(idport + 1);
		if (!(val & PC873_EPPRDIR)) {
		    mode = PPB_EPP;	/* As we would have done it anwyay */
		}
	    } else if ((val & PC873_ECPEN) && (val & PC873_ECPCLK)) {
		mode = PPB_PS2;		/* tolerable alternative */
	    }	    
	} else {
	    if (bootverbose)
		printf("PC873xx unlocked, ");

#if 0	/* broken */
	    /*
	     * Frob the zero-wait-state option if possible; it causes
	     * unreliable operation.
	     */
	    outb(idport, PC873_FCR);
	    val = inb(idport + 1);
	    if ((ppc->ppc_type == NS_PC87306) ||	/* we are a '306 */
		!(val & PC873_ZWSPWDN)) {		/* or pin _is_ ZWS */
		val &= ~PC873_ZWS;
		outb(idport + 1, val);			/* must disable ZWS */
		outb(idport + 1, val);
		
		if (bootverbose)
		    printf("ZWS %s, ", (val & PC873_ZWS) ? "enabled" : "disabled");
	    }

#endif
	    if (bootverbose)
		printf("reconfiguring for ");
	    
	    /* 
	     * if the chip is at 0x3bc, we can't use EPP as there's no room
	     * for the extra registers.
	     *
	     * XXX should we use ECP mode always and use the EPP submode?
	     */
	    if (ppc->ppc_base != 0x3bc) {
		if (bootverbose)
		    printf("EPP 1.9\n");
		
		/* configure for EPP 1.9 operation XXX should be configurable */
		outb(idport, PC873_PCR);
		val = inb(idport + 1);
		val &= ~(PC873_ECPEN | PC873_ECPCLK);	/* disable ECP */
		val |= (PC873_EPPEN | PC873_EPP19);	/* enable EPP */
		outb(idport + 1, val);
		outb(idport + 1, val);

		/* enable automatic direction turnover */
		outb(idport, PC873_PTR);
		val = inb(idport + 1);
		val &= ~PC873_EPPRDIR;			/* disable "regular" direction change */
		outb(idport + 1, val);
		outb(idport + 1, val);

		/* we are an EPP-32 port */
		mode = PPB_EPP;
	    } else {
		if (bootverbose)
		    printf("ECP\n");
		
		/* configure as an ECP port to get bidirectional operation for now */
		outb(idport, PC873_PCR);
		outb(idport + 1, inb(idport + 1) | PC873_ECPEN | PC873_ECPCLK);

		/* we look like a PS/2 port */
		mode = PPB_PS2;
	    }
	}
	return(mode);
    }
    return(0);
}

static int
ppc_detect_ps2(struct ppc_data *ppc)
{
	char save_control, r;

	save_control = r_ctr(ppc);

	/* Try PS/2 mode */
	w_ctr(ppc, 0xec);
	w_dtr(ppc, 0x55);

	/* needed if in ECP mode */
	if (ppc->ppc_mode == PPB_ECP)
		w_ctr(ppc, PCD | 0xec);
	r = r_dtr(ppc);

	if (r != (char) 0xff) {
		if (r != (char) 0x55)
			return 0;

		w_dtr(ppc, 0xaa);
		r = r_dtr(ppc);
		if (r != (char) 0xaa)
			return 0;

		return (PPB_NIBBLE);
	} else
		w_ctr(ppc, save_control);

	return (PPB_PS2);
}

/*
 * ppc_smc37c66xgt_detect
 *
 * SMC FDC37C66xGT configuration.
 */
static int
ppc_smc37c66xgt_detect(struct ppc_data *ppc, int mode)
{
	int s, i;
	char r;
	int retry = 0;		/* boolean */
	int type = -1;
	int csr = SMC66x_CSR;	/* initial value is 0x3F0 */

	int port_address[] = { -1 /* disabled */ , 0x3bc, 0x378, 0x278 };


#define cio csr+1	/* config IO port is either 0x3F1 or 0x371 */

	/*
	 * Detection: enter configuration mode and read CRD register.
	 */
	 
	s = splhigh();
	outb(csr, SMC665_iCODE);
	outb(csr, SMC665_iCODE);
	splx(s);

	outb(csr, 0xd);
	if (inb(cio) == 0x65) {
		type = SMC_37C665GT;
		goto config;
	}

	for (i = 0; i < 2; i++) {
		s = splhigh();
		outb(csr, SMC666_iCODE);
		outb(csr, SMC666_iCODE);
		splx(s);

		outb(csr, 0xd);
		if (inb(cio) == 0x66) {
			type = SMC_37C666GT;
			break;
		}

		/* Another chance, CSR may be hard-configured to be at 0x370 */
		csr = SMC666_CSR;
	}

config:
	/*
	 * If chipset not found, do not continue.
	 */
	if (type == -1)
		return (0);

	/* select CR1 */
	outb(csr, 0x1);

	/* read the port's address: bits 0 and 1 of CR1 */
	r = inb(cio) & SMC_CR1_ADDR;
	if (port_address[r] != ppc->ppc_base)
		return (0);

	ppc->ppc_type = type;

	/*
	 * CR1 and CR4 registers bits 3 and 0/1 for mode configuration
	 * If SPP mode is detected, try to set ECP+EPP mode end retry
	 * detection to verify.
	 */

retry:
	/* select CR1 register */
	outb(csr, 0x1);

	if (!mode) {
		/* autodetect mode */

		/* 666GT chipset is hardwired to an extended mode */
		if (type == SMC_37C666GT)
			mode = PPB_ECP_EPP;

		else if ((inb(cio) & SMC_CR1_MODE) == 0) {
			/* already in extended parallel port mode, read CR4 */
			outb(csr, 0x4);
			r = (inb(cio) & SMC_CR4_EMODE);

			switch (r) {
			case SMC_SPP:
				/* let's detect NIBBLE or PS/2 later */
				break;

			case SMC_EPPSPP:
				mode = PPB_EPP;
				break;

			case SMC_ECP:
				/*
				 * Yet, don't know what to do with it! 	XXX
				 * So, consider ECP mode as PS/2.
				 * (see configuration later).
				 */
				mode = PPB_ECP;
				break;

			case SMC_ECPEPP:
				mode = PPB_ECP_EPP;
				break;
			}
		}
	} else {
		/* mode forced */

		/* 666GT chipset is hardwired to an extended mode */
		if (type == SMC_37C666GT)
			goto end_detect;

		r = inb(cio);
		if (mode == PPB_NIBBLE || mode == PPB_PS2) {
			/* do not use ECP when the mode is forced to SPP */
			outb(cio, r | SMC_CR1_MODE);
		} else {
			/* an extended mode is selected */
			outb(cio, r & ~SMC_CR1_MODE);

			/* read CR4 register and reset mode field */
			outb(csr, 0x4);
			r = inb(cio) & ~SMC_CR4_EMODE;

			switch (mode) {
			case PPB_EPP:
				outb(cio, r | SMC_EPPSPP);
				break;

			case PPB_ECP:
			case PPB_ECP_PS2:
				outb(cio, r | SMC_ECP);
				break;

			case PPB_ECP_EPP:
				outb(cio, r | SMC_ECPEPP);
				break;

			default:
				printf("ppc: unknown mode (%d)\n",
					mode);
				return (0);
			}
		}
	}

end_detect:
	if (PPB_IS_EPP(mode)) {
		/* select CR4 */
		outb(csr, 0x4);
		r = inb(cio);

		/*
		 * Set the EPP protocol...
		 * Low=EPP 1.9 (1284 standard) and High=EPP 1.7
		 * ...then check the result.
		 */
		if (ppc->ppc_epp == EPP_1_9)
			outb(cio, (r & ~SMC_CR4_EPPTYPE));

		else
			outb(cio, (r | SMC_CR4_EPPTYPE));
	}

	/* end config mode */
	outb(csr, 0xaa);

	/*
	 * Write 100 to the mode bits and disable DMA, enable intr.
	 */
	if (mode == PPB_ECP_EPP)
		w_ecr(ppc, 0x80);

	/*
	 * Write 001 to the mode bits and disable DMA, enable intr.
	 */
	if (mode == PPB_ECP)
		w_ecr(ppc, 0x20);

	if (PPB_IS_EPP(mode)) {
		/*
		 * Try to reset EPP timeout bit.
		 * If it fails, try PS/2 and NIBBLE modes.
		 */
		ppc_reset_epp_timeout(ppc->ppc_unit);

		r = r_str(ppc);
		if (!(r & TIMEOUT))
			return (mode);
	} else {
		if (mode)
			return (mode);
	}

	/* detect PS/2 or NIBBLE mode */
	return (ppc_detect_ps2(ppc));
}

static int
ppc_check_ecpepp_timeout(struct ppc_data *ppc)
{
	char r;

	ppc_reset_epp_timeout(ppc->ppc_unit);

	r = r_str(ppc);
	if (!(r & TIMEOUT)) {
		return (PPB_ECP_EPP);
	}

	/* If EPP timeout bit is not reset, DON'T use EPP */
	w_ecr(ppc, 0x20);

	return (PPB_ECP_PS2);
}

/*
 * ppc_generic_detect
 */
static int
ppc_generic_detect(struct ppc_data *ppc, int mode)
{
	char save_control, r;

	/* don't know what to do here */
	if (mode)
		return (mode);

	/* try to reset EPP timeout bit */
	ppc_reset_epp_timeout(ppc->ppc_unit);

	r = r_str(ppc);
	if (!(r & TIMEOUT)) {
		return (PPB_EPP);
	}

	/* Now check for ECP */
	w_ecr(ppc, 0x20);
	r = r_ecr(ppc);
	if ((r & 0xe0) == 0x20) {
		/* Search for SMC style EPP+ECP mode */
		w_ecr(ppc, 0x80);

		return (ppc_check_ecpepp_timeout(ppc));
	}

	return (ppc_detect_ps2(ppc));
}

/*
 * ppc_detect()
 *
 * mode is the mode suggested at boot
 */
static int
ppc_detect(struct ppc_data *ppc, int mode) {

	if (!ppc->ppc_mode && (ppc->ppc_mode = ppc_pc873xx_detect(ppc)))
		goto end_detect;

	if (!ppc->ppc_mode && (ppc->ppc_mode =
				ppc_smc37c66xgt_detect(ppc, mode)))
		goto end_detect;

	if (!ppc->ppc_mode && (ppc->ppc_mode = ppc_generic_detect(ppc, mode)))
		goto end_detect;

	printf("ppc: port not present at 0x%x.\n", ppc->ppc_base);
	return (PPC_ENOPORT);

end_detect:

	return (0);
}

/*
 * EPP timeout, according to the PC87332 manual
 * Semantics of clearing EPP timeout bit.
 * PC87332	- reading SPP_STR does it...
 * SMC		- write 1 to EPP timeout bit			XXX
 * Others	- (???) write 0 to EPP timeout bit
 */
static void
ppc_reset_epp_timeout(int unit)
{
	struct ppc_data *ppc = ppcdata[unit];
	register char r;

	r = r_str(ppc);
	w_str(ppc, r | 0x1);
	w_str(ppc, r & 0xfe);

	return;
}

static int
ppcprobe(struct isa_device *dvp)
{
	static short next_bios_ppc = 0;
	struct ppc_data *ppc;
	int error;

	/*
	 * If port not specified, use bios list.
	 */
	if(dvp->id_iobase < 0) {
		if((next_bios_ppc < BIOS_MAX_PPC) &&
				(*(BIOS_PORTS+next_bios_ppc) != 0) ) {
			dvp->id_iobase = *(BIOS_PORTS+next_bios_ppc++);
		} else
			return (0);
	}

	/*
	 * Port was explicitly specified.
	 * This allows probing of ports unknown to the BIOS.
	 */

	/*
	 * Allocate the ppc_data structure.
	 */
	ppc = malloc(sizeof(struct ppc_data), M_DEVBUF, M_NOWAIT);
	if (!ppc) {
		printf("ppc: cannot malloc!\n");
		goto error;
	}
	bzero(ppc, sizeof(struct ppc_data));

	ppc->ppc_base = dvp->id_iobase;
	ppc->ppc_unit = dvp->id_unit;
	ppc->ppc_type = GENERIC;

	/* PPB_AUTODETECT is default to allow chipset detection even if
	 * mode is forced by dvp->id_flags (see later, ppc_detect() call) */
	ppc->ppc_mode = PPB_AUTODETECT;
	ppc->ppc_epp = (dvp->id_flags & 0x8) >> 3;

	/*
	 * XXX
	 * Try and detect if interrupts are working.
	 */
	if (!(dvp->id_flags & 0x10))
		ppc->ppc_irq = (dvp->id_irq);

	ppcdata[ppc->ppc_unit] = ppc;
	nppc ++;

	/*
	 * Try to detect the chipset and its mode.
	 */
	if (ppc_detect(ppc, dvp->id_flags & 0x7))
		goto error;

end_probe:

	return (1);

error:
	return (0);
}

static int
ppcattach(struct isa_device *isdp)
{
	struct ppc_data *ppc = ppcdata[isdp->id_unit];
	struct ppb_data *ppbus;

	/*
	 * Link the Parallel Port Chipset (adapter) to
	 * the future ppbus.
	 */
	ppc->ppc_link.adapter_unit = ppc->ppc_unit;
	ppc->ppc_link.adapter = &ppc_adapter;

	printf("ppc%d: %s chipset in %s mode%s\n", ppc->ppc_unit,
		ppc_types[ppc->ppc_type], ppc_modes[ppc->ppc_mode],
		(PPB_IS_EPP(ppc->ppc_mode)) ?
			ppc_epp_protocol[ppc->ppc_epp] : "");

	/*
	 * Prepare ppbus data area for upper level code.
	 */
	ppbus = ppb_alloc_bus();

	if (!ppbus)
		return (0);

	ppc->ppc_link.ppbus = ppbus;
	ppbus->ppb_link = &ppc->ppc_link;

	/*
	 * Probe the ppbus and attach devices found.
	 */
	ppb_attachdevs(ppbus);

	return (1);
}
#endif