/*
 * /sys/i386/isa/ppa3.c
 *
 *   FreeBSD driver for PPA3 adapter
 *   embedded in the IOMEGA ZIP 100 drive.
 *
 *   See README file.
 *
 *   Release: 0.10
 *   Release: 0.11
 *   -------------
 *	- *int32 becomes *int32_t. This may give some warnings during
 *	  compilation process.
 *
 *   Release: 0.20
 *   -------------
 *	- Interrupt emulation with timeout(). Some parallel
 *	  ports support interrupts, the ZIP drive doesn't.
 *	- This release is a port of the 0.26 Linux driver.
 *
 *   Release: 0.31
 *   -------------
 *	- ppa becomes a controller in the /sys/i386/conf/MACHINE file.
 *	- PS/2 byte mode via ECP supported.
 *	- EPP transfer mode supported but not detected.
 *	- Software interrupt disabled.
 *	- Transfer mode may be defined via the 'flags' field.
 *	- Note: this release is a port of the 0.26-athena Linux driver.
 *
 *   Release: 0.32
 *   -------------
 *	- bug fixed with SUCCESSFULLY_QUEUED return value.
 *
 *   Release: 0.40
 *   -------------
 *	- This release is a port of the 1.01-curtin Linux driver.
 *
 *   Release: 0.41
 *   -------------
 *	- HAVE_SMC patch removed.
 *	- Distributed with 'eppconfig' tool to configure epp_speed.
 *	- This release is a port of the 1.05-curtin Linux driver.
 *
 *   Release: 0.42-ALPHA
 *   -------------------
 *	- Better error report.
 *	- EPP+ECP generic detection.
 *	- Warning logs added.
 *	- splsoftclock() level for io transfers.
 *	- This release is a port of the 1.06-curtin Linux driver.
 *      - Bugs fixed in ppaio_outstr() and ppaio_instr()
 *
 *   Release: 0.50-BETA
 *   ------------------
 *	- Printer facilities added.
 *	- splbio() level for io transfers.
 *
 *   Release: 0.51-BETA
 *   ------------------
 *	- FreeBSD R2.2 port.
 *
 *   Release: 0.52-BETA
 *   ------------------
 *	- 'extern struct isa_driver lptdriver;'
 *	  added for 2.1.x compatibility.
 *
 *   Release: 0.53-BETA
 *   ------------------
 *	- PPA_21x_COMPAT defined.
 *	- (ppaio_wait() return value & 0xc0) compared to 0xc0.
 *	- PPA_SPINTMO increased up to 500000.
 *
 *   Release: 0.54-BETA
 *   ------------------
 *   	- Fixed for 2.1.x release.
 *
 *   Release: 0.60-BETA
 *   ------------------
 *	- PPA_INTRTMO and PPA_LPT removed.
 *	- PPA_BUFFER_SIZE set to 0x12000.
 *	- ppa_warning() added.
 *	- REQUEST_SENSE implemented.
 *
 *   Release: 0.61-BETA
 *   ------------------
 *	- PPA_SPINTMO increased up to 5000000.
 *
 *   -------------------------------------------------------------------
 *   THIS SOFTWARE IS DISTRIBUTED WITH NO WARRANTY.
 *   -------------------------------------------------------------------
 *
 *   This driver is a port of the Linux driver.
 *
 *   -------------------------------------------------------------------
 *   NOT SUPPORTED ANYMORE !!!
 *   -------------------------------------------------------------------
 */

/* --------------------------------------------------------------------
 * HERE ARE THINGS YOU MAY HAVE/WANT TO CHANGE
 */

/*
 * PPA_21x_COMPAT		defined if your system release is R2.1.x
 */

#ifndef PPA_ECHECKING
  #define PPA_ECHECKING	3	/* more error checking for EPP transfer mode,
				 * decreases transfer rate.
				 *	0 = no errors checking
				 *	1 = every 4 bytes
				 *	2 = every 2 bytes
				 *	3 = every byte	(DEFAULT and SAFE) */
#endif

#ifndef PPA_EPP_SPEED
  #define PPA_EPP_SPEED	4096	/* delay for EPP mode */
#endif

#define PPA_SPEED_HIGH	1	/* high speed delay for SPP modes */
#define PPA_SPEED_LOW	3	/* low speed delay for SPP modes */
#define PPA_SELTMO	5000	/* select timeout */
#define PPA_SPINTMO	5000000	/* wait status timeout */
#define PPA_MAXRETRY	0	/* internal retry count */

/* XXX
 * This is ALPHA/BETA code, warnings are mandatory.
 */
#ifndef PPA_WARNING
  #define PPA_WARNING		/* defined to get warnings about timeouts,
				 * except select timeouts */
#endif

/*
 * DO NOT MODIFY ANYTHING UNDER THIS LINE
 * --------------------------------------------------------------------
 */

#include <sys/types.h>

#ifdef KERNEL
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/proc.h>

#include <machine/stdarg.h>
#include <machine/clock.h>

#ifdef PPA_WARNING
#include <syslog.h>
#endif

#ifdef PPA_21x_COMPAT
#include <sys/devconf.h>
#endif

#include <i386/isa/isa_device.h>
#endif	/* KERNEL */
#include <scsi/scsi_all.h>
#include <scsi/scsi_disk.h>
#include <scsi/scsiconf.h>

#ifdef	KERNEL
#include <sys/kernel.h>
#endif /*KERNEL */

/*
 * If DEV_LKM is defined, NPPA must be > 0.
 */
#ifndef DEV_LKM
#include "ppa.h"
#endif

#if NPPA > 0
#define PPA_VERSION "0.62-BETA"

#define barrier() __asm__("": : :"memory")

#define PPA_INITIATOR	0x7

#define PPA_SECTOR_SIZE	512
#define PPA_BUFFER_SIZE	0x12000
#define STATUS_MASK	0xff

#define PPA_SPL() splbio()

#define PPA_ESELECT_TIMEOUT	1
#define PPA_ESPPCMD_TIMEOUT	2
#define PPA_EREPLY_TIMEOUT	3
#define PPA_ESTATUS_TIMEOUT	4
#define PPA_EDATA_OVERFLOW	5	

#define PPA_EPPCMD_TIMEOUT	6
#define PPA_EPPDATA_TIMEOUT	7
#define PPA_EPPSTATUS_ERROR	8

#define PPA_ENOPORT		9
#define PPA_EINITFAILED		10
#define PPA_EDATA_STATUS	11

#define PPA_EOTHER		12

#define PPA_OPENNINGS	1

#define PPA_MODE_MASK	0xff
#define PPA_EPP_MASK	0xffff00	/* 16 bits should be enough */
#define PPA_EPP_RSHIFT	8		/* according to PPA_MODE_MASK */

#define PPA_AUTODETECT	0x0	/* autodetect NIBBLE or PS/2 mode */
#define PPA_NIBBLE	0x1	/* standard 4 bit mode */
#define PPA_PS2		0x2	/* PS/2 byte mode */
#define PPA_EPP_8	0x3	/* EPP mode, 8 bit */
#define PPA_EPP_16	0x4	/* EPP mode, 16 bit */
#define PPA_EPP_32	0x5	/* EPP mode, 32 bit */
#define PPA_PS2_ECP	0x6	/* ECP in PS/2 mode */
#define PPA_UNKNOWN	0x7

#define SPP_DTR		0	/* SPP data register */
#define SPP_STR		1	/* SPP status register */
#define SPP_CTR		2	/* SPP control register */
#define EPP_DATA	4	/* EPP data register (8, 16 or 32 bit) */
#define ECP_FIFO	0x400	/* ECP fifo register */
#define ECP_ECR		0x402	/* ECP extended control register */

#define IN_EPP_MODE(ppa) ((ppa)->ppa_mode == PPA_EPP_8 || \
	(ppa)->ppa_mode == PPA_EPP_16 || (ppa)->ppa_mode == PPA_EPP_32)

#define CONNECT_EPP_MAYBE	1
#define CONNECT_NORMAL		0

struct ppa_sense {
	struct scsi_sense cmd;
	unsigned int stat;
	unsigned int count;
};

struct ppa_data {
	int ppa_base;
	unsigned short	ppa_unit;
#ifdef PPA_LPT
	struct isa_device * ppa_lptdev;
#endif
	struct ppa_sense ppa_sense;

	unsigned char ppa_buffer[PPA_BUFFER_SIZE];

	unsigned int	ppa_stat;
	unsigned int	ppa_count;
	unsigned char	ppa_mode;
	unsigned char	ppa_port_delay;
	struct scsi_link sc_link;

	int	ppa_epp_speed;
};

#define ppaio_outsb(ppa,port,addr,cnt) \
			outsb ((ppa)->ppa_base + port, addr, cnt)
#define ppaio_outsw(ppa,port,addr,cnt) \
			outsw ((ppa)->ppa_base + port, addr, cnt)
#define ppaio_outsl(ppa,port,addr,cnt)  \
			outsl ((ppa)->ppa_base + port, addr, cnt)

#define ppaio_insb(ppa,port,addr,cnt) \
			insb ((ppa)->ppa_base + port, addr, cnt)
#define ppaio_insw(ppa,port,addr,cnt) \
			insw ((ppa)->ppa_base + port, addr, cnt)
#define ppaio_insl(ppa,port,addr,cnt)  \
			insl ((ppa)->ppa_base + port, addr, cnt)

#define r_dtr(ppa) ((char) inb((ppa)->ppa_base + SPP_DTR))
#define r_str(ppa) ((char) inb((ppa)->ppa_base + SPP_STR))
#define r_ctr(ppa) ((char) inb((ppa)->ppa_base + SPP_CTR))
#define r_epp(ppa) ((char) inb((ppa)->ppa_base + EPP_DATA))
#define r_fifo(ppa) ((char) inb((ppa)->ppa_base + ECP_FIFO))
#define r_ecr(ppa) ((char) inb((ppa)->ppa_base + ECP_ECR))

#define w_dtr(ppa,byte) { \
	outb((ppa)->ppa_base + SPP_DTR, byte); \
}
#define w_str(ppa,byte) { \
	outb((ppa)->ppa_base + SPP_STR, byte); \
}
#define w_ctr(ppa,byte) { \
	outb((ppa)->ppa_base + SPP_CTR, byte); \
	DELAY ((ppa)->ppa_port_delay); \
}
#define w_epp(ppa,byte) outb((ppa)->ppa_base + EPP_DATA, byte)
#define w_fifo(ppa,byte) outb((ppa)->ppa_base + ECP_FIFO, byte)
#define w_ecr(ppa,byte) outb((ppa)->ppa_base + ECP_ECR, byte)

static inline int ppaio_do_scsi	__P((struct ppa_data *, int, int, char *, int,
				 char *, int, int *, int *));

static struct ppa_data * ppadata[NPPA];

static int	ppaprobe __P((struct isa_device *));
static int	ppaattach __P((struct isa_device *));
static int32_t	ppa_scsi_cmd __P((struct scsi_xfer *));
static void	ppaminphys __P((struct buf *));
static u_int32_t ppa_adapter_info __P((int));

static int	ppaio_detect __P((struct ppa_data *));

#ifdef KERNEL
static struct scsi_adapter ppa_switch =
{
	ppa_scsi_cmd,
	ppaminphys,
	0,
	0,
	ppa_adapter_info,
	"ppa",
	{ 0, 0 }
};

/* 
 * The below structure is so we have a default dev struct
 * for out link struct.
 */
static struct scsi_device ppa_dev =
{
	NULL,	/* Use default error handler */
	NULL,	/* have a queue, served by this */
	NULL,	/* have no async handler */
	NULL,	/* Use default 'done' routine */
	"ppa",
	0,
	{ 0, 0 }
};

struct isa_driver ppadriver =
{
	ppaprobe,
	ppaattach,
	"ppa"
};

#ifdef PPA_21x_COMPAT
static struct kern_devconf kdc_ppa[NPPA] = { {
	0, 0, 0,		/* filled in by dev_attach */
	"ppa", 0, { MDDT_ISA, 0, "bio" },
	isa_generic_externalize, 0, 0, ISA_EXTERNALLEN,
	&kdc_isa0,		/* parent */
	0,			/* parentdata */
	DC_UNCONFIGURED,	/* always start out here */
	"Iomega PPA3 Parallel Port SCSI host adapter",
	DC_CLS_MISC
}};
#endif

#ifdef PPA_21x_COMPAT
#ifndef DEV_LKM
static inline void
ppa_registerdev (struct isa_device * id)
{
	if (id->id_unit)
		kdc_ppa[id->id_unit] = kdc_ppa[0];
	kdc_ppa[id->id_unit].kdc_unit = id->id_unit;
	kdc_ppa[id->id_unit].kdc_parentdata = id;
	dev_attach(&kdc_ppa[id->id_unit]);
}
#endif
#endif

#endif /* KERNEL */

static int ppaunit = 0;

static u_int32_t
ppa_adapter_info (int unit) {

	return 1;
}

static int
ppa_init (int unit) {

	struct ppa_data * ppa = ppadata[unit];
	int rs;
	
	if ((rs = ppaio_detect (ppa))) {
		switch (rs) {
		case PPA_ENOPORT:
			printf ("ppa%d: parallel port not found at 0x%x.\n",
				unit, ppa->ppa_base);
			break;
		case PPA_EINITFAILED:
			printf ("ppa%d: controller initialisation failed.\n",
				unit);
			break;
		default:
			printf ("ppa%d: ppa_init() failed, error %d.\n",
				unit, rs);
		}
		return 1;
	}

	return 0;
}

#if defined (PPA_21x_COMPAT) && defined(PPA_LPT)
extern struct isa_driver lptdriver;
#endif

#ifdef PPA_LPT
static int
ppa_lptprobe (struct ppa_data * ppa)
{
	struct isa_device * dvp;

	printf ("ppa%d: searching disabled polling lpt driver... ",
		ppa->ppa_unit);
	for (dvp = isa_devtab_tty; dvp->id_driver; dvp++) {
		if (dvp->id_enabled)
			continue;
		if (dvp->id_driver == &lptdriver && dvp->id_irq == 0) {
			printf ("found.\n");
			goto probe;	
		}
	}
	printf ("not found.\n");

	return 1;

probe:
	dvp->id_iobase = ppa->ppa_base;
	ppa->ppa_lptdev = dvp;
	(*ppa->ppa_lptdev->id_driver->probe) (ppa->ppa_lptdev);
	return 0;
}
#endif

static int
ppaprobe (struct isa_device * dev) {

	int unit = ppaunit;
	struct ppa_data * ppa;
	int rv = -1;

	if (unit >= NPPA) {
		printf("ppa%d: unit number too high\n", unit);
		return 0;
	}
	dev->id_unit = unit;

	if (ppadata[unit]) {
		printf("ppa%d: memory already allocated\n", unit);
		return 0;
	}
	ppa = malloc (sizeof(struct ppa_data), M_TEMP, M_NOWAIT);
	if (!ppa) {
		printf("ppa%d: cannot malloc!\n", unit);
		return 0;
	}
	bzero(ppa, sizeof(struct ppa_data));
	ppadata[unit] = ppa;
	ppa->ppa_base = dev->id_iobase;
	ppa->ppa_port_delay = PPA_SPEED_LOW;
	ppa->ppa_unit = unit;

#define GET_PPA_MODE(dev) \
	(dev->id_flags & PPA_MODE_MASK) 

	ppa->ppa_mode = GET_PPA_MODE (dev);

#define GET_EPP_SPEED(dev) \
	((dev->id_flags & PPA_EPP_MASK) ? \
		(dev->id_flags & PPA_EPP_MASK) >> PPA_EPP_RSHIFT : \
		PPA_EPP_SPEED)

	ppa->ppa_epp_speed = GET_EPP_SPEED (dev);

	if (ppa->ppa_mode >= PPA_UNKNOWN) {
		printf ("ppa%d: invalid mode (0x%x), " \
			"autodetect mode selected.\n",
			ppa->ppa_unit, ppa->ppa_mode);
		ppa->ppa_mode = PPA_AUTODETECT;
	}

	if (ppa->ppa_mode)
		switch (ppa->ppa_mode) {
		case PPA_NIBBLE:
			printf ("ppa%d: SPP (4 bit) mode forced.\n",
				ppa->ppa_unit);
			break;
		case PPA_PS2:
			printf ("ppa%d: PS/2 (8 bit) mode forced.\n",
				ppa->ppa_unit);
			break;
		case PPA_EPP_8:
			printf ("ppa%d: EPP 8 bit mode forced, " \
				"ppa_epp_speed = %d at 0x%x\n",
				ppa->ppa_unit, ppa->ppa_epp_speed,
				&ppa->ppa_epp_speed);
			break;
		case PPA_EPP_16:
			printf ("ppa%d: EPP 16 bit mode forced, " \
				"ppa_epp_speed = %d at 0x%x\n",
				ppa->ppa_unit, ppa->ppa_epp_speed,
				&ppa->ppa_epp_speed);
			break;
		case PPA_EPP_32:
			printf ("ppa%d: EPP 32 bit mode forced, " \
				"ppa_epp_speed = %d at 0x%x\n",
				ppa->ppa_unit, ppa->ppa_epp_speed,
				&ppa->ppa_epp_speed);
			break;
		case PPA_PS2_ECP:
			printf ("ppa%d: PS/2 (8 bit) mode forced "\
				"with ECP.\n", ppa->ppa_unit);
			break;
		default:
			panic ("ppa_probe(): invalid mode (0x%x)!",
				ppa->ppa_mode);
		}

#ifdef PPA_21x_COMPAT
#ifndef DEV_LKM
	ppa_registerdev(dev);
#endif
#endif

	if ((rv = ppa_init(unit)) != 0) {
#ifdef DEBUG_PPA3
	log (LOG_DEBUG, "ppaprobe(): ppa_init() error (%d)\n", rv);
#endif

		ppadata[unit] = NULL;
		free(ppa, M_TEMP);
		return 0;
	}

#ifdef PPA_LPT
	ppa->ppa_lptdev = 0;
	if (IN_EPP_MODE (ppa))
		printf ("ppa%d: lpt unavailable in EPP mode.\n",
			ppa->ppa_unit);
	else
		ppa_lptprobe (ppa);
#endif

	ppaunit ++;
	return 4;
}

/*
 * Attach all the sub-devices we can find.
 */
static int
ppaattach (struct isa_device * dev) {

	int unit = dev->id_unit;
	struct ppa_data * ppa = ppadata[unit];
	struct scsibus_data * scbus;

	ppa->sc_link.adapter_unit = unit;
	ppa->sc_link.adapter_targ = PPA_INITIATOR;
	ppa->sc_link.adapter = &ppa_switch;
	ppa->sc_link.device = &ppa_dev;
	ppa->sc_link.opennings = PPA_OPENNINGS;

	/*
	 * Prepare the scsibus_data area for the upperlevel
	 * scsi code.
	 */
	scbus = scsi_alloc_bus();
	if(!scbus)
		return 0;
	scbus->adapter_link = &ppa->sc_link;

#ifdef PPA_21x_COMPAT
	/*
	 * Ask the adapter what subunits are present.
	 */
	kdc_ppa[unit].kdc_state = DC_BUSY; /* host adapters are always busy */
#endif

	scsi_attachdevs(scbus);

#ifdef PPA_LPT
	if (ppa->ppa_lptdev)
		(*ppa->ppa_lptdev->id_driver->attach) (ppa->ppa_lptdev);
#endif

	return 1;
}

static void
ppaminphys(struct buf * bp) {

	if (bp->b_bcount > PPA_BUFFER_SIZE)
		bp->b_bcount = PPA_BUFFER_SIZE;

	return;
}

static inline void
ppa_warning (struct ppa_data * ppa, struct scsi_xfer * xs, int timeout) {

	switch (timeout) {
	case 0:
	case PPA_ESELECT_TIMEOUT:
		break;
	case PPA_EPPCMD_TIMEOUT:
		log (LOG_WARNING,
			"ppa%d: EPP command timeout\n", ppa->ppa_unit);
		break;
	case PPA_EPPDATA_TIMEOUT:
		log (LOG_WARNING,
			"ppa%d: EPP data timeout\n", ppa->ppa_unit);
		break;
	case PPA_EPPSTATUS_ERROR:
		log (LOG_WARNING,
			"ppa%d: EPP status error\n", ppa->ppa_unit);
		break;
	case PPA_ESTATUS_TIMEOUT:
		log (LOG_WARNING,
			"ppa%d: SPP status timeout\n", ppa->ppa_unit);
		break;
	case PPA_EDATA_OVERFLOW:
		log (LOG_WARNING,
			"ppa%d: data overflow\n", ppa->ppa_unit);
		break;
	case PPA_EDATA_STATUS:
		log (LOG_WARNING,
			"ppa%d: unknown transfer status\n", ppa->ppa_unit);
		break;
	case PPA_ESPPCMD_TIMEOUT:
		log (LOG_WARNING,
			"ppa%d: SPP command timeout\n", ppa->ppa_unit);
		break;
	case PPA_EREPLY_TIMEOUT:
		log (LOG_WARNING,
			"ppa%d: command reply timeout\n", ppa->ppa_unit);
		break;
	default:
		log (LOG_WARNING,
			"ppa%d: timeout = %d\n", ppa->ppa_unit, timeout);
		break;
	}
}

static inline void
ppaintr (struct ppa_data * ppa, struct scsi_xfer * xs) {

	register int timeout;

	if (xs->datalen && !(xs->flags & SCSI_DATA_IN))
		bcopy (xs->data, ppa->ppa_buffer, xs->datalen);

	timeout = ppaio_do_scsi (ppa, PPA_INITIATOR,
		xs->sc_link->target,
		(char *) xs->cmd, xs->cmdlen,
		ppa->ppa_buffer, xs->datalen,
		&ppa->ppa_stat, &ppa->ppa_count);

#ifdef PPA_WARNING
	ppa_warning (ppa, xs, timeout);
#endif

#ifdef DEBUG_PPA3
	log (LOG_DEBUG, "ppa_do_scsi = %d, status = 0x%x, count = %d\n", 
	 timeout, ppa->ppa_stat, ppa->ppa_count);
#endif

#define RESERVED_BITS_MASK 0x3e		/* 00111110b */
#define NO_SENSE	0x0
#define CHECK_CONDITION	0x02

	switch (ppa->ppa_stat & RESERVED_BITS_MASK) {
	case NO_SENSE:
		break;

	case CHECK_CONDITION:
	default:
		ppa->ppa_sense.cmd.op_code = REQUEST_SENSE;
		ppa->ppa_sense.cmd.length = sizeof (xs->sense);
		ppa->ppa_sense.cmd.control = 0;

		timeout = ppaio_do_scsi (ppa, PPA_INITIATOR,
			xs->sc_link->target,
			(char *) &ppa->ppa_sense.cmd,
			sizeof (ppa->ppa_sense.cmd),
			(char *) &xs->sense, sizeof (xs->sense),
			&ppa->ppa_sense.stat, &ppa->ppa_sense.count);

		xs->error = XS_SENSE;
		goto error;
	}

	switch (timeout) {
		case 0:
			break;

		case PPA_EPPCMD_TIMEOUT:
		default:
			xs->error = XS_TIMEOUT;
			goto error;
	}

	if (xs->datalen && (xs->flags & SCSI_DATA_IN))
		bcopy (ppa->ppa_buffer, xs->data, xs->datalen);

done:
	xs->resid = 0;
	xs->error = XS_NOERROR;

error:
	xs->flags |= ITSDONE;
	scsi_done (xs);

	return;
}

static int32_t
ppa_scsi_cmd (struct scsi_xfer * xs) {

	int s;

	if (xs->sc_link->lun > 0) {
		xs->error = XS_DRIVER_STUFFUP;
		return TRY_AGAIN_LATER;
	}

	if (xs->flags & SCSI_DATA_UIO) {
		printf ("UIO not supported by ppa_driver !\n");
		xs->error = XS_DRIVER_STUFFUP;
		return TRY_AGAIN_LATER;
	}

#ifdef DEBUG_PPA3
	{ int i;
	log (LOG_DEBUG, "ppa_scsi_cmd(): xs->flags = 0x%x, "\
		 "xs->data = 0x%x, xs->datalen = %d\n",
		 xs->flags, xs->data, xs->datalen);

	for (i=0; i<xs->cmdlen; i++)
		log (LOG_DEBUG, "%2x", ((u_char *) xs->cmd)[i]);
		log (LOG_DEBUG, "\n");
	}
#endif

	if (xs->flags & SCSI_NOMASK) {
		ppaintr (ppadata[xs->sc_link->adapter_unit], xs);
		return COMPLETE;
	}

	s = PPA_SPL();

	ppaintr (ppadata[xs->sc_link->adapter_unit], xs);

	splx (s);
	return SUCCESSFULLY_QUEUED;
}

#define ppaio_d_pulse(ppa,b) { \
	w_dtr(ppa, b); \
	w_ctr(ppa, 0xc); \
	w_ctr(ppa, 0xe); \
	w_ctr(ppa, 0xc); \
	w_ctr(ppa, 0x4); \
	w_ctr(ppa, 0xc); \
}

#define ppaio_disconnect(ppa) { \
	ppaio_d_pulse(ppa, 0); \
	ppaio_d_pulse(ppa, 0x3c); \
	ppaio_d_pulse(ppa, 0x20); \
	ppaio_d_pulse(ppa, 0xf); \
}

#define ppaio_c_pulse(ppa,b) { \
	w_dtr(ppa, b); \
	w_ctr(ppa, 0x4); \
	w_ctr(ppa, 0x6); \
	w_ctr(ppa, 0x4); \
	w_ctr(ppa, 0xc); \
}

#define ppaio_connect(ppa,how) { \
	ppaio_c_pulse (ppa, 0); \
	ppaio_c_pulse (ppa, 0x3c); \
	ppaio_c_pulse (ppa, 0x20); \
	if ((how == CONNECT_EPP_MAYBE) && IN_EPP_MODE(ppa)) { \
		ppaio_c_pulse (ppa, 0xcf); \
	} else { \
		ppaio_c_pulse (ppa, 0x8f); \
	} \
}


static void ecp_sync (struct ppa_data * ppa) {

	int i, r;

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

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

	printf ("ppa%d: ECP sync failed as data still " \
		"present in FIFO.\n", ppa->ppa_unit);

	return;
}

/*
 * Start of Chipset kludges
 */

#ifdef PPA_PC87332
static inline int pc87332_port(struct ppa_data * ppa)
{
	/* A routine to detect and kludge pc87332 chipsets into the
	 * "optimum" mode for parallel port data transfer.
	 * This assumes EPP is better than ECP...
	 * (Which it is for disk drives but not printers and scanners)
	 */

	/* This is where an pc87332 can hide */
	unsigned short index_addr[4] =
	{
		0x0398, 0x026e, 0x015c, 0x002e
	};

	/* Bits 0&1 of FAR (Function Address Register) which specify where
	 * the LPT port will show up at.
	 */
	unsigned short port_ref[4] =
	{
		0x378, 0x3bc, 0x278, 0xffff
	};

	unsigned char a;
	int loop;

	for (loop = 0; loop < 4; loop++) {
		/* Clear the "wax" out of the pc87332, only needed after hard
		 * reset.
		 */
		inb(index_addr[loop]);
		inb(index_addr[loop]);
		inb(index_addr[loop]);
		inb(index_addr[loop]);

		/* Anyone home ?? */
		outb(index_addr[loop], 0xff);
		a = inb(index_addr[loop]);
		switch (a) {
		case (0x0f):	/* PC87732 */
			printf("ppa%d: NatSemi PC8733x ", ppa->ppa_unit);
			break;
		case (0x1f):	/* PC87306 */
			printf("ppa%d: NatSemi PC8730x ", ppa->ppa_unit);
			break;
		default:
			continue;
		}

		/* Is this pc87332 on the desired port */
		outb(index_addr[loop], 0x01);
		a = inb(index_addr[loop] + 1);
		if (port_ref[a & 0x03] != ppa->ppa_base) {
			printf ("on port 0x%x, ignored.\n",
				port_ref[a & 0x03]);
			continue;
		}

		/* Found a pc87332 */

		/* Try to enable EPP modes
		 * with hardware data direction
		 */
		if (ppa->ppa_base != 0x3bc) {
			/* EPP 1.9 */
			outb(index_addr[loop], 0x04);
			a = inb(index_addr[loop] + 1);
			a = (a & 0xfb) | 0x03;
			outb(index_addr[loop] + 1, a);
			outb(index_addr[loop] + 1, a);

			/* Hardware data direction selection */
			outb(index_addr[loop], 0x02);
			a = inb(index_addr[loop] + 1);
			a = (a & 0x7f);
			outb(index_addr[loop] + 1, a);
			outb(index_addr[loop] + 1, a);
			ppa->ppa_mode = PPA_EPP_32;
			printf(" in EPP mode, ppa_epp_speed = %d at 0x%x\n",
				ppa->ppa_epp_speed, &ppa->ppa_epp_speed);
		} else {
			/* There is not enough address space for the 0x3bc port
			 * to have EPP registers so we will kludge it into an
			 * ECP
			 * port to allow bi-directional byte mode...
			 */
			/* ECP */
			outb(index_addr[loop], 0x4);
			a = inb(index_addr[loop] + 1);
			a = (a & 0xfb) | 0x06;
			outb(index_addr[loop] + 1, a);
			outb(index_addr[loop] + 1, a);
			ppa->ppa_mode = PPA_PS2;
			printf(" in PS2 mode\n");
		}

		outb(index_addr[loop], 0x4);
		a = inb(index_addr[loop] + 1);
		return ppa->ppa_mode;
	}
	return 0;
}
#endif

static inline int generic_port(struct ppa_data * ppa)
{
	/* Generic parallel port detection
	 * This will try to discover if the port is
	 * EPP, ECP, PS/2 or NIBBLE (In that order, approx....)
	 */
	char save_control, r;

	/* First reset the EPP timeout bit */
	r = r_str(ppa);
	w_str(ppa, r);
	w_str(ppa, r & 0xfe);

	r = r_str(ppa);
	if (!(r & 0x01)) {
		/* If that EPP timeout bit is not reset, DON'T use EPP */
		ppa->ppa_mode = PPA_EPP_32;
		printf("ppa%d: Generic Chipset in EPP mode, " \
			"ppa_epp_speed = %d at 0x%x\n",
			ppa->ppa_unit, ppa->ppa_epp_speed,
			&ppa->ppa_epp_speed);
		return ppa->ppa_mode;
	}
	/* Now check for ECP */
	w_ecr(ppa, 0x20);
	r = r_ecr(ppa);
	if ((r & 0xe0) == 0x20) {
		/* Search for SMC style EPP+ECP mode */
		w_ecr(ppa, 0x80);

		/* First reset the EPP timeout bit */
		r = r_str(ppa);
		w_str(ppa, r);
		w_str(ppa, r & 0xfe);

		r = r_str(ppa);
		if (!(r & 0x01)) {
			/* If EPP timeout bit is not reset, DON'T use EPP */
			ppa->ppa_mode = PPA_EPP_32;
			printf("ppa%d: Generic Chipset in EPP+ECP mode, " \
				"ppa_epp_speed = %d at 0x%x\n",
				ppa->ppa_unit, ppa->ppa_epp_speed,
				&ppa->ppa_epp_speed);
			return ppa->ppa_mode;
		}
		w_ecr(ppa, 0x20);
		ppa->ppa_mode = PPA_PS2;
		printf("ppa%d: Generic Chipset in PS2 mode\n",
			ppa->ppa_unit);
		return ppa->ppa_mode;
	}
	save_control = r_ctr(ppa);
	ppa->ppa_mode = PPA_PS2;

	w_ctr(ppa, 0xec);
	w_dtr(ppa, 0x55);
	r = r_dtr(ppa);
	if (r != (char) 0xff) {
		ppa->ppa_mode = PPA_NIBBLE;
		if (r != (char) 0x55)
			return 0;
		w_dtr(ppa, 0xaa);
		if (r_dtr(ppa) != (char) 0xaa)
			return 0;
		printf("ppa%d: Generic Chipset in NIBBLE mode\n",
			ppa->ppa_unit);
	} else
		printf("ppa%d: Generic Chipset in PS2 mode\n",
			ppa->ppa_unit);
	w_ctr(ppa, save_control);
	return ppa->ppa_mode;
}

static int
ppaio_init (struct ppa_data * ppa) {

	char retval;

	ppaio_disconnect (ppa);
	ppaio_connect (ppa, CONNECT_NORMAL);

	w_ctr (ppa, 0x6);
	if ((retval = (r_str (ppa) & 0xf0)) != (char) 0xf0) {
		printf ("ppa%d: retval is 0x%x, should be 0xf0\n",
			ppa->ppa_unit, retval);
		return PPA_EINITFAILED;
	}

	w_ctr (ppa, 0x4);
	if ((retval = (r_str (ppa) & 0xf0)) != (char) 0x80) {
		printf ("ppa%d: retval is 0x%x, should be 0x80\n",
			ppa->ppa_unit, retval);
		return PPA_EINITFAILED;
	}

	/*
	 * This is SCSI reset signal.
	 */
	w_dtr (ppa, 0x40);
	w_ctr (ppa, 0x8);
	DELAY (50);
	w_ctr (ppa, 0xc);

	ppaio_disconnect(ppa);

	return 0;
}

static int
ppaio_detect (struct ppa_data * ppa) {

	int rs, retv;

	w_ctr(ppa, 0x0c);	/* To avoid missing PS2 ports */
	w_dtr(ppa, 0xaa);
	if (r_dtr(ppa) != (char) 0xaa)
		return PPA_ENOPORT;

	/*
	 * Attempt to initialise the controller.
	 */
	retv = ppa->ppa_mode;

#ifdef PPA_PC87332
	if (!retv)
		retv = pc87332_port(ppa);
#endif

	if (!retv)
		generic_port(ppa);

	/*
	 * PS/2 mode forced with ECP.
	 */
	if (ppa->ppa_mode == PPA_PS2_ECP) {
		w_ecr(ppa, 0x20);
		ppa->ppa_mode = PPA_PS2;
	}

	if ((rs = ppaio_init(ppa)))
		return rs;

	return 0;
}

static inline char
ppaio_select (struct ppa_data * ppa, int initiator, int target) {

	register char	r;
	register int	k;

	r = r_str(ppa);	/* TODO */

	w_dtr(ppa, (1 << target));
	w_ctr(ppa, 0xe);
	w_ctr(ppa, 0xc);
	w_dtr(ppa, (1 << initiator));
	w_ctr(ppa, 0x8);

	k = 0;
	while (!(r = (r_str(ppa) & 0xf0)) && (k++ < PPA_SELTMO))
		barrier();

	if (k >= PPA_SELTMO)
		return 0;

	return r;
}

static inline char
ppaio_wait (struct ppa_data * ppa) {

	register int	k;
	register char	r;

	k = 0;
	while (!((r = r_str(ppa)) & 0x80) && (k++ < PPA_SPINTMO))
		barrier ();

	/*
	 * Return some status information.
	 * Semantics :	0xc0 = ZIP wants more data
	 *		0xd0 = ZIP wants to send more data
	 *		0xe0 = ???
	 *		0xf0 = end of transfer, ZIP is sending status
	 */
	if (k < PPA_SPINTMO)
	  return (r & 0xf0);

	return 0;			   /* command timed out */	
}

static inline int
ppa_check_epp_status (struct ppa_data * ppa) {

	register char r;

	r = r_str (ppa);

	if (r & 1) {
		/* 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
		 * Others	- (???) write 0 to EPP timeout bit
		 */
		w_str (ppa, r);
		w_str (ppa, r & 0xfe);

		return PPA_EPPDATA_TIMEOUT;
	}

	if (!(r & 0x80))
		return PPA_EPPSTATUS_ERROR;

	return 0;
}

static inline int
ppa_force_epp_byte (struct ppa_data * ppa, char byte) {

	char r;

	w_epp (ppa, byte);

	r = r_str (ppa);
	if (!(r & 1)) 
		return 0;

	if (ppa->ppa_epp_speed) {
		/* 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
		 * Others	- (???) write 0 to EPP timeout bit
		 */
		w_str (ppa, r);
		w_str (ppa, r & 0xfe);
		DELAY (ppa->ppa_epp_speed);

		w_epp (ppa, byte);
		r = r_str (ppa);
	}	
	if (r & 1) {
		w_str (ppa, r);
		w_str (ppa, r & 0xfe);
		return 1;
	}

	return 0;
}

static inline int
ppaio_outstr (struct ppa_data * ppa, char * buffer) {

	register int k;
#if PPA_ECHECKING > 0
	int r;
#endif

	switch (ppa->ppa_mode) {
		case PPA_NIBBLE:
		case PPA_PS2:
			for (k=0;k<PPA_SECTOR_SIZE;k++) {
				w_dtr(ppa, *buffer++);
				w_ctr(ppa, 0xe);
				w_ctr(ppa, 0xc);
			}
			return 0;

#if PPA_ECHECKING > 0
		case PPA_EPP_32:
#if PPA_ECHECKING < 2

			w_ctr (ppa,0x4);
			for (k=0; k<PPA_SECTOR_SIZE; k += 4) {
				w_epp (ppa, *buffer++);
				w_epp (ppa, *buffer++);
				w_epp (ppa, *buffer++);
				w_epp (ppa, *buffer++);
				r = ppa_check_epp_status (ppa);
				if (r)
					return r;
			}
			w_ctr (ppa, 0xc);
			ecp_sync (ppa);
			return 0;
#endif

		case PPA_EPP_16:
#if PPA_ECHECKING < 3
			w_ctr (ppa, 0x4);
			for (k=0; k<PPA_SECTOR_SIZE; k += 2) {
				w_epp (ppa, *buffer++);
				w_epp (ppa, *buffer++);
				r = ppa_check_epp_status (ppa);
				if (r)
					return r;
			}
			w_ctr (ppa, 0xc);
			ecp_sync (ppa);
			return 0;
#endif
		case PPA_EPP_8:
			w_ctr (ppa, 0x4);
			for (k=0; k<PPA_SECTOR_SIZE; k++) {
				w_epp (ppa, *buffer++);
				r = ppa_check_epp_status (ppa);
				if (r)
					return r;
			}
			w_ctr (ppa, 0xc);
			ecp_sync (ppa);
			return 0;
#else
		case PPA_EPP_8:
			w_ctr(ppa,0x4);
			ppaio_outsb(ppa,EPP_DATA,buffer,PPA_SECTOR_SIZE);

			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0xc);
			ecp_sync (ppa);
			return k;

		case PPA_EPP_16:
			w_ctr(ppa,0x4);
			ppaio_outsw(ppa,EPP_DATA,buffer,PPA_SECTOR_SIZE / 2);

			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0xc);
			ecp_sync (ppa);
			return k;

		case PPA_EPP_32:
			w_ctr(ppa,0x4);
			ppaio_outsl(ppa,EPP_DATA,buffer,PPA_SECTOR_SIZE / 4);

			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0xc);
			ecp_sync (ppa);
			return k;
#endif

		default:
			printf ("ppaio_outstr(): unknown transfer mode (%d)!\n",
				ppa->ppa_mode);
			return 1;
	}

	panic ("ppaio_outstr(): code never executed !");
}

static inline int
ppaio_instr (struct ppa_data * ppa, char * buffer) {

	register int k;
	register char h, l;

#if PPA_ECHECKING > 0
	int r;
#endif
	
	switch (ppa->ppa_mode) {
		case PPA_NIBBLE:
			for (k=0;k<PPA_SECTOR_SIZE;k++) {
				w_ctr(ppa,0x4);
				h = r_str(ppa);
				w_ctr(ppa,0x6);
				l = r_str(ppa);
				*buffer++ = ((l >> 4) & 0x0f) + (h & 0xf0);
			}

			w_ctr(ppa,0xc);
			return 0;

		case PPA_PS2:
			for (k=0;k<PPA_SECTOR_SIZE;k++) {
				w_ctr(ppa,0x25);
				*buffer++ = r_dtr(ppa);
				w_ctr(ppa,0x27);
			}

			w_ctr(ppa,0x5);
			w_ctr(ppa,0x4);
			w_ctr(ppa,0xc);
			return 0;

#if PPA_ECHECKING > 0
		case PPA_EPP_32:
#if PPA_ECHECKING < 2
			w_ctr (ppa, 0x24);
			for (k=0; k<PPA_SECTOR_SIZE; k += 4) {
				*buffer++ = r_epp (ppa);
				*buffer++ = r_epp (ppa);
				*buffer++ = r_epp (ppa);
				*buffer++ = r_epp (ppa);
				r = ppa_check_epp_status (ppa);
				if (r)
					return r;
			}
			w_ctr (ppa, 0x2c);
			ecp_sync (ppa);
			return 0;
#endif
		case PPA_EPP_16:
#if PPA_ECHECKING < 3
			w_ctr (ppa, 0x24);
			for (k=0; k<PPA_SECTOR_SIZE; k += 2) {
				*buffer++ = r_epp (ppa);
				*buffer++ = r_epp (ppa);
				r = ppa_check_epp_status (ppa);
				if (r)
					return r;
			}
			w_ctr (ppa, 0x2c);
			ecp_sync (ppa);
			return 0;
#endif
		case PPA_EPP_8:
			w_ctr(ppa, 0x24);
			for (k=0; k<PPA_SECTOR_SIZE; k++) {
				*buffer++ = r_epp(ppa);
				r = ppa_check_epp_status(ppa);
				if (r)
					return r;
			}
			w_ctr(ppa, 0x2c);
			ecp_sync(ppa);
			return 0;
#else

		case PPA_EPP_8:
			w_ctr(ppa,0x24);
			ppaio_insb(ppa,EPP_DATA,buffer,PPA_SECTOR_SIZE);
			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0x2c);
			ecp_sync (ppa);
			return k;

		case PPA_EPP_16:
			w_ctr(ppa,0x24);
			ppaio_insw(ppa,EPP_DATA,buffer,PPA_SECTOR_SIZE / 2);
			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0x2c);
			ecp_sync (ppa);
			return k;

		case PPA_EPP_32:
			w_ctr(ppa,0x24);
			ppaio_insl(ppa,EPP_DATA,buffer,PPA_SECTOR_SIZE / 4);
			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0x2c);
			ecp_sync (ppa);
			return k;
#endif

		default:
			printf ("ppaio_instr(): unknown transfer mode (%d)!\n",
				ppa->ppa_mode);
			return 1;
	}

	panic ("ppaio_instr(): code never executed !");
}

static inline int
ppaio_outbyte (struct ppa_data * ppa, char byte) {

	register int k;

	switch (ppa->ppa_mode) {
		case PPA_NIBBLE:
		case PPA_PS2:
			w_dtr(ppa,byte);
			w_ctr(ppa,0xe);
			w_ctr(ppa,0xc);
			return 0;

		case PPA_EPP_8:
		case PPA_EPP_16:
		case PPA_EPP_32:
			w_ctr(ppa,0x4);
			w_epp(ppa,byte);
			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0xc);
			ecp_sync (ppa);
			return k;

		default:
			printf ("ppaio_outbyte(): " \
				"unknown transfer mode (%d)!\n",
				ppa->ppa_mode);
			return 1;
	}

	panic ("ppaio_outbyte(): code never executed !");
}

static inline int
ppaio_inbyte (struct ppa_data * ppa, char * buffer) {

	register int k;
	register char h, l;
	
	switch (ppa->ppa_mode) {
		case PPA_NIBBLE:
			w_ctr(ppa,0x4);
			h = r_str(ppa);
			w_ctr(ppa,0x6);
			l = r_str(ppa);
			*buffer = ((l >> 4) & 0x0f) + (h & 0xf0);
			w_ctr(ppa,0xc);
			return 0;

		case PPA_PS2:
			w_ctr(ppa,0x25);
			*buffer = r_dtr(ppa);
			w_ctr(ppa,0x27);
			w_ctr(ppa,0x5);
			w_ctr(ppa,0x4);
			w_ctr(ppa,0xc);
			return 0;

		case PPA_EPP_8:
		case PPA_EPP_16:
		case PPA_EPP_32:
			w_ctr(ppa,0x4);
			*buffer = r_epp(ppa);
			k = ppa_check_epp_status (ppa);
			w_ctr(ppa,0xc);
			ecp_sync (ppa);
			return k;

		default:
			printf ("ppaio_inbyte(): " \
				"unknown transfer mode (%d)!\n",
				ppa->ppa_mode);
			return 1;
	}

	panic ("ppaio_inbyte(): code never executed !");
}

static inline int 
ppaio_do_scsi (struct ppa_data * ppa, int host, int target,
		   char * command, int clen,
		   char * buffer, int blen,
		   int * result, int * count) {

	register char r;
	char l, h;
	int dir, bulk, fast;
	int error = 0;
	register int k, cnt;

	ppaio_connect(ppa, CONNECT_EPP_MAYBE);

	r = ppaio_select(ppa,host,target);
	if (r == 0)  {
		error = PPA_ESELECT_TIMEOUT;
		goto error;
	}

	/*
	 * Send the command ...
	 */
	if (IN_EPP_MODE (ppa)) {
		w_ctr(ppa,0x4);
		for (k=0;k<clen;k++)
			if (ppa_force_epp_byte (ppa, command[k])) {
				error = PPA_EPPCMD_TIMEOUT;
				goto error;
			}

		w_ctr(ppa,0xc);
		ecp_sync (ppa);

	} else {
		w_ctr(ppa,0xc);
		for (k=0;k<clen;k++) {
			if (!ppaio_wait(ppa)) {
				error = PPA_ESPPCMD_TIMEOUT;
				goto error;
			}
			w_dtr(ppa,command[k]);
			w_ctr(ppa,0xe);
			w_ctr(ppa,0xc);
		}
	}

	/* 
	 * Completion ... 
	 */
	bulk = ((command[0] == READ_COMMAND) || (command[0] == READ_BIG) ||
		(command[0] == WRITE_COMMAND) || (command[0] == WRITE_BIG));
	cnt = 0;  dir = 0;

	if (!(r = ppaio_wait(ppa))) {
		error = PPA_EREPLY_TIMEOUT;
		goto error;
	}
	dir = (r == (char) 0xc0);

	if (!IN_EPP_MODE (ppa))
		ppa->ppa_port_delay = PPA_SPEED_HIGH;

	while (r != (char) 0xf0) {
		if ((r & 0xc0) != 0xc0) {
			error = PPA_EDATA_STATUS;
			goto error;
		}
		if (cnt >= blen) {
			error = PPA_EDATA_OVERFLOW;
			goto error;
		}
		fast = (bulk && ((blen - cnt) >= PPA_SECTOR_SIZE) &&
			(((int) (buffer + cnt)) & 0x3) == 0);

		if (fast) {
			if (dir)
				error = ppaio_outstr (ppa, &buffer[cnt]);
			else
				error = ppaio_instr (ppa, &buffer[cnt]);
			cnt += PPA_SECTOR_SIZE;
		} else {
			if (dir)
				error = ppaio_outbyte (ppa, buffer[cnt]);
			else
				error = ppaio_inbyte (ppa, &buffer[cnt]);
			cnt ++;
		}

		if (error)
			goto error;

		if (!(r = ppaio_wait(ppa))) {
			error = PPA_ESTATUS_TIMEOUT;
			goto error;
		}
	}

	ppa->ppa_port_delay = PPA_SPEED_LOW;
	*count = cnt;

	if (ppaio_inbyte (ppa, &l)) {
		error = PPA_EOTHER; goto error;
	}

	if (ppaio_wait (ppa) == 0) {
		error = PPA_EOTHER+1; goto error;
	}

	if (ppaio_inbyte (ppa, &h)) {
		error = PPA_EOTHER+2; goto error;
	}

	ppaio_disconnect(ppa);

	*result = ((int) h << 8) | ((int) l & STATUS_MASK);

	return 0;

error:
	ppa->ppa_port_delay = PPA_SPEED_LOW;
	ppaio_disconnect (ppa);
	return error;
}

#endif

