/******************************************************************************
 * --- bt.c --- Copyright (C) 1994 by Thomas Weidenfeller
 *
 * File ID:
 *	$Id: bt.c,v 1.7 1994/10/26 18:23:46 root Exp $
 *
 * Description:
 *	Unofficial BiTronics(tm) (BI-directional cenTRONICS) Linux
 *	character device driver	for parallel printers with BiTronic
 *	interface like the HP4L.
 *		
 *	This is a free sample of no commercial value. If you plan
 *	to use it, see "Warranty" and "Copyright" below before usage!
 *
 *	If you don't use a BiTronics printer, this software is of no
 *	interest for you.
 *
 * Copyright:
 *	Copyright (C) 1994 by T. Weidenfeller. All rights reserved
 *	
 *	You are free to copy, modify and distribute this software as you
 *	see fit, and to use it for any purpose, provided this copyright
 *	notice and the warranty disclaimer are included without any
 *	modification in all copies and modifications. You have to mark all
 *	modifications clearly and you are also commited to give propper
 *	acknowledgment of the original source if you include this software
 *	or parts of it in another software.
 *	
 * Warranty:
 *	The BiTronics device driver and the accompaning material is a free
 *	sample of no commercial value. It comes without any warranty:
 *	
 *	THERE IS NO WARRANTY OF ANY KIND WITH REGARDS TO THIS MATERIAL,
 *	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *	MECHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The author
 *	shall not be liable for errors contained in the material or for
 *	incidental or consequental damage in connection with the
 *	furnishing, performance, or use of this material.
 *	
 * Trademark Credits (to the best of my knowlege):
 *	Centronics is a U.S. registered Trademark of Centronics
 *	Corporation. Microsoft is a registered trademark of Microsoft
 *	Corporation. PCL is a U.S registered trademark of
 *	Hewlett-Packard Company. Other brands or product names are
 *	trademarks or registered trademarks of their respective holders.
 *
 * Notes:
 *	- Only polling is currently supported
 *	- Only BiTronics nibble mode is currently supported (other
 *	  modes are in existance, but I couldn't find any documentation
 *	  about them).
 *	- Until now I was not able to figure out how BiTronics is
 *	  related to BOIS (IEEE P1284) (developed by Hewlett-Packard
 *	  and Microsoft Corporation). 
 *
 * Revisions:	$Log: bt.c,v $
 *	Revision 1.7  1994/10/26  18:23:46  root
 *	General harmonisation (prep 1. ALPHA release).
 *
 *	Revision 1.6  1994/10/23  20:18:27  root
 *	add call to status dump removed
 *
 *	Revision 1.5  1994/10/23  19:39:27  root
 *	General rearrangement (port macros moved to bt_io.h, trace facility moved to bt_trace, configuration data and defs. moved to bt_config.h).
 *	Name space cleanup.
 *	Pseudo code for select().
 *
 *	Revision 1.4  1994/10/16  16:30:36  root
 *	Error handling, read and write functions revised
 *
 *	Revision 1.3  1994/10/16  08:02:05  root
 *	Standard trace system for debugging added
 *
 *	Revision 1.2  1994/10/11  17:35:27  root
 *	First version that provides partly functional readback
 *
 *	Revision 1.1  1994/09/26  19:19:34  root
 *	Initial revision
 *****************************************************************************/

/*
.EOC
.P
*/
#include "bt_config.h"		/* ALLWAYS(!) first			*/
#include "bt_trace.h"		/* tracing (might generate code!)	*/
#include "bt_version.h"		/* tracing (might generate code!)	*/
#include "bt_io.h"		/* all this port io stuff		*/

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/malloc.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/segment.h>

#ifdef BT_COOPERATE_WITH_LP
	#include <linux/lp.h>
#endif

#include <linux/bt.h>

/******************************************************************************
 * driver internal definitions
 *****************************************************************************/

static char bt_RCS_ID[] = "@(#)$Id: bt.c,v 1.7 1994/10/26 18:23:46 root Exp $";
#define BT_USE(x)	((void)(x))	/* null macro to hide warning	*/


/*
.P
*/
/*******************************************************************************
 * Create the status lookup tables
 *******************************************************************************/

BT_PRN_STAT bt_prn_stat_norm[] = {
	BT_PRN_STE(READY),
	BT_PRN_STE(NO_PRN),
	BT_PRN_STE(PE),	  
	BT_PRN_STE(OFFLINE),
	BT_PRN_STE(UNSPEC),
	BT_PRN_STE(BUSY),
	{ 0, 0, NULL }
};

BT_PRN_STAT bt_prn_stat_bt[] = {
	BT_PRN_STE(ACK),  
	BT_PRN_STE(ADR),    
	BT_PRN_STE(CLK_H),
	BT_PRN_STE(CLK_L),
	BT_PRN_STE(TERM),
	{ 0, 0, NULL }
};


/*******************************************************************************
 * Lookup table and related constants to map status line bits to values 
 * NOTE: This is an interlaced table: Line 1 & 3 are for the lower nibble,
 *       line 2 & 4 for for the upper nibble.
 *******************************************************************************/

/* Status bits which form the data when a nibble is read         */
#define BT_DATA_BITS (BT_STAT_nERROR | BT_STAT_SLCT | BT_STAT_PE | BT_STAT_nBUSY)

/* Offset between lower and upper nibble values in lookup table */
#define BT_UPPER_NIBBLE (8)

/* Nbr. of unused low zero bits in nibble data                  */
#define BT_DATA_SHIFT   (3)

static char bt_val[] = {
	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
	0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0,
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70
};


/*
.P
*/
/******************************************************************************
 * BiTronics mode, status and error declarations
 ******************************************************************************/

#define	BT_NIBBLE_MODE	(0x00)	/* we don't support anything else...	*/
#define BT_DEVICE_ID	(0x04)	/* fetch device id			*/

/*
 * BiTronics data flow direction and states
 */
typedef enum {
	BT_NO_PORT,	/* nothing there which could have a dir.*/
	BT_HOST2PRN,	/* computer -> printer  (normal mode)	*/
	BT_PRN2HOST,	/* printer  -> computer (readback)	*/
	BT_ID2HOST	/* printer  -> computer (fetch ID)	*/
} BT_PORT_DIR;


/*
 * BiTronics error types
 * NOTE: We use a preprocessor trick to get the values and messages in...
 */
#define BTERROR(v, m)	v
typedef enum {
#	include "bt_error.h"
} BT_ERROR;
#define BTERROR(v, m)	m
static char *bt_err_msgs[] = {
#	include "bt_error.h"
};


/*
.P
*/
/******************************************************************************
 * Per minor device number data
 *****************************************************************************/

typedef struct {
	unsigned	base_addr;	/* base address of port controller */
	BT_PORT_DIR	port_dir;	/* data flow direction		*/
	int		busy;		/* tells if port already in use	*/
	int		data_avail;	/* identifies if read dta avail	*/
	BT_ERROR	last_error;	/* last error which was flagged	*/
	/* XXX ad mutex for multithreaded OS */
} BT_DATA;


static BT_DATA bt_data[] = {
	{ 0x3bc, BT_NO_PORT, 0, 0, BT_NO_ERROR },		/* lpt0	*/
	{ 0x378, BT_NO_PORT, 0, 0, BT_NO_ERROR },		/* lpt1	*/
	{ 0x278, BT_NO_PORT, 0, 0, BT_NO_ERROR },		/* lpt2	*/
};

#define BT_NBR	(sizeof(bt_data) / sizeof(BT_DATA))


/******************************************************************************
 * Table of supported device driver features
 ******************************************************************************/
static void bt_release(struct inode *inode, struct file *file);
static int  bt_open(   struct inode *inode, struct file *file);
static int  bt_write(  struct inode* inode, struct file *file, char *buf, int count);
static int  bt_read(   struct inode* inode, struct file *file, char *buf, int count);

/*
 * the table of possible driver operations
 */
static struct file_operations bt_fops = {
	NULL,		/* seek		*/
	bt_read,	/* read		*/
	bt_write,	/* write	*/
	NULL,		/* readdir	*/
	NULL,		/* select	*/
	NULL,		/* ioctl	*/
	NULL,		/* mmap		*/
	bt_open,	/* open		*/
	bt_release	/* [Strange, does Linus not like traditional close()?]*/
};


/*
.P
*/
/******************************************************************************
 * Common support routines
 ******************************************************************************/

#ifdef BT_STAT_DUMP		/* very usefull during debugging	*/
static void bt_status_dump(int minor)
/******************************************************************************
 * Dumps the status bits out in a readable form
 * IN:	minor		Minor device number 
 ******************************************************************************/
{
	static char *smsg[] = {
		"nBUSY",  "nACK",   "PE",     "SLCT",
		"nERROR", "nIRQST", "RSRVD2", "RSRVD1",
	};

	int status = BT_STAT_IN(minor);
	int i;

	printk(BT_DRIVER_NAME "%d: status bits: ", minor);
	for(i = 0; i < 8; i++) {
		if(status & 0x80) {
			printk("%d: %s  ", 8 - i, smsg[i]);
		}
		status = status << 1;
	}
	printk("\n");
}
#else
#	define bt_status_dump(m)
#endif /* BT_STAT_DUMP	*/


static void bt_sleep(int ticks)
/******************************************************************************
 * Pseudo sleep() function: Sets up everything for getting re-enabled and
 * returns control to kernel
 * IN:	ticks		Nbr. of "jiffies" to sleep
 ******************************************************************************/
{
	BTMARK(BTTF_TIME, "ticks = %d\n", ticks);
	current->state   = TASK_INTERRUPTIBLE;
	current->timeout = jiffies + ticks;
	schedule();
}


static void bt_status_analyze(int minor)
/******************************************************************************
 * Prints the status of the BiTronics printer
 * IN:	minor		Minor device number 
 ******************************************************************************/
{
	int status = BT_STAT_IN(minor);
	BT_PRN_STAT	*current;

	BTMARK(BTTF_LOWF,"minor = %d, status bits: 0x%x\n", minor, status);

	bt_status_dump(minor);

	current = bt_prn_stat_bt;
	while(current->msg) {
		if(BT_STAT_TST(status, current->and, current->xor)) {
			printk(BT_DRIVER_NAME "%d: BT port status: %s\n",
				 minor, current->msg);
			break;
		}
		current++;
	}

	current = bt_prn_stat_norm;
	while(current->msg) {
		if(BT_STAT_TST(status, current->and, current->xor)) {
			printk(BT_DRIVER_NAME "%d: Printer status: %s\n",
				 minor, current->msg);
			break;
		}
		current++;
	}
}


static void bt_reset(int minor)
/******************************************************************************
 * Forces port into normal mode
 * IN:	minor		Minor device number to reset
 ******************************************************************************/
{
	BTMARK(BTTF_LOWF,"\n");

	BT_CTRL_OUT(minor, BT_HOST_NORMAL);	/* set port to normal op */
	udelay(BT_STD_DELAY);
	BT_CTRL_OUT(minor, BT_HOST_NORMAL);	/* required by BiTronics */
	bt_data[minor].port_dir   = BT_HOST2PRN;
	bt_data[minor].data_avail = 0;
}


static int bt_status_wait(int minor, int and, int xor, unsigned long ticks)
/******************************************************************************
 * Waits until a specific port status is reached (specific status bits have to
 * be low).
 * NOTE: This function abuses the systems jiffies counter a little bit
 *       (but only a little bit).
 * IN:	minor		Minor device number to wait for
 *	and		Status bits to take into account
 *	xor		Status bits which have to be low
 *	ticks		Maximum time, in quantities of 10ms (jiffies),
 *			to wait for the required status
 * OUT:	bt_status_wait	0: Timeout; 1: OK
 ******************************************************************************/
{
	unsigned long start = jiffies;

	BTENTER(BTTF_LOWF, "mask = 0x%x, bits = 0x%x\n", and, xor);

	/*
         * NOTE: The while() has to look as it is to ensure jiffie overflow
         *       is handled correctly (will happen after more than a year...)
	 */
	while((jiffies - start) < ticks) {
		if(BT_STAT_TST(BT_STAT_IN(minor), and, xor)) {
			BTEXIT(BTTF_LOWF, "OK, reached\n");
			return 1;
		}
		if(need_resched) {
			BTPRINT(BTTF_TIME, "rescheduling!\n");
			schedule();
		}
	}
	BTEXIT(BTTF_LOWF, "timed out!\n");
	return 0;	/* timeout occured	*/
}


static void bt_err_clear(int minor, BT_ERROR error)
/******************************************************************************
 * Prints msg and clears the minor device after a status error has ocured
 * IN:	minor		Minor device to clear
 *	error		Error which has ocured
 ******************************************************************************/
{
	BTMARK(BTTF_ALL,"minor = %d, error # = %d\n", minor, (int)error);

	if(error != bt_data[minor].last_error) { /* flag msg once only */
		printk(BT_DRIVER_NAME "%d: Error: %s\n", minor, bt_err_msgs[error]);
		bt_status_analyze(minor);
		bt_data[minor].last_error = error;
	}
	bt_reset(minor);			/* set port to normal op */
			/* XXX better bt_terminate()??? */
}


/*
.P
*/

/******************************************************************************
 * BiTronics support routines
 ******************************************************************************/

static int bt_terminate(int minor)
/******************************************************************************
 * Terminates BiTronics operation at the specific port
 * IN:	minor		Minor device for which BiTronics should be terminated
 * OUT:	bt_terminate	0: Termination failed!, 1: Terminated
 ******************************************************************************/
{
	int	low_bits;	/* bits which have to go low as anser to req. */

	BTENTER(BTTF_ALL,"\n");

	low_bits = BT_PRN_TERM |
		((BT_STAT_IN(minor) & BT_PRN_TERM_BITS) ^ BT_PRN_TERM_BITS);

	BT_CTRL_OUT(minor, BT_HOST_TERM_REQ);	/* host requests termination  */
	if(!bt_status_wait(minor, BT_PRN_TERM_MASK, low_bits, BT_STD_TIMEOUT)) {
		/*
		 * Printer did not responde to termination request, game over
		 */
		bt_err_clear(minor, BT_START_TERMINATE);
		BTEXIT(BTTF_ALL,"no resp. to term. req!\n");
		return 0;
	}

	BT_CTRL_OUT(minor, BT_HOST_TERM_DO);	/* execute the termination */
	if(!bt_status_wait(minor, BT_PRN_CLK_H_MASK, BT_PRN_CLK_H, BT_STD_TIMEOUT)) {
		/*
		 * Printer did not responde to termination execution, game over
		 */
		bt_err_clear(minor, BT_END_TERMINATE);
		BTEXIT(BTTF_ALL,"no resp. to term. exe!\n");
		return 0;
	}
	
	BT_CTRL_OUT(minor, BT_HOST_NORMAL);	/* reset to normal mode	*/
	bt_data[minor].port_dir = BT_HOST2PRN;	/* normal direction	*/
	BTPRINT(BTTF_DIR,"port_dir = %d\n", bt_data[minor].port_dir);
	
	BTEXIT(BTTF_ALL,"OK, termination done\n");
	return 1;	/* Terminated	*/
}


static int bt_data_avail(int minor)
/******************************************************************************
 * Tests if data is available at the specified minor device
 * IN:	minor	Minor device to use
 * OUT:	bt_data_avail	-1: error; 0: no data; 1: data
 ******************************************************************************/
{
	int status = BT_STAT_IN(minor);

	BTENTER(BTTF_ALL,"current prt status: 0x%x\n", status);

	if(BT_CTRL_IN(minor) & BT_HOST_IDLE_BIT) {
		/*
		 * Host is NOT busy: PE must be high!
		 */
		if(! (status & BT_STAT_PE)) {
			bt_err_clear(minor, BT_PERROR_SYNC);
			BTEXIT(BTTF_ALL,"err: PE not hight!\n");
			return -1;
		}
	} else {
		/*
		 * Host is busy, PE and FAULT should be in sync
		 */
		if((!(status & BT_STAT_PE)) != (!(status & BT_STAT_nERROR))) {
			bt_err_clear(minor, BT_PERROR_SYNC);
			BTEXIT(BTTF_ALL,"err: nFAULT and PE not in sync!\n");
			return -1;
		}
	}

	BTEXIT(BTTF_ALL,"OK, retval = %d\n", !(status & BT_PRN_DATA_MASK));
	return !(status & BT_PRN_DATA_MASK);
}


static int bt_negotiate(int minor)
/******************************************************************************
 * Negotiates BiTronics mode with printer (we only support nibble mode)
 * IN:	minor		Minor device to use
 * OUT:	bt_negotiate	1: OK, 0: not ok (BiTronics error msg displayed)
 ******************************************************************************/
{
	/*
	 * indicate requested receive mode
	 * NOTE: We do NOT toggle strobe with printer select, so this will not
	 *	 be interpreted as printing data! (one of the many BiTronics
	 *	 tricks...)
	 */
	BTENTER(BTTF_ALL,"minor = %d\n", minor);

	BT_DATA_OUT(minor, BT_NIBBLE_MODE);
	BT_CTRL_OUT(minor, BT_HOST_REQ);
	if(!bt_status_wait(minor, BT_PRN_ACK_MASK, BT_PRN_ACK, BT_STD_TIMEOUT)) {
		bt_err_clear(minor, BT_NOT_BT_DEVICE);
		BTEXIT(BTTF_ALL,"wait for PRN_ACK timed out (no BT device?)\n");
		return 0;
	}
	BT_CTRL_OUT(minor, BT_HOST_BT_STROBE);
	BT_CTRL_OUT(minor, BT_HOST_BUSY);
	if(!bt_status_wait(minor, BT_PRN_CLK_H_MASK, BT_PRN_CLK_H, BT_STD_TIMEOUT)) {
		bt_err_clear(minor, BT_END_NEGOTIATE);
		BTEXIT(BTTF_ALL,"wait for PRN_CLK_H timed out (negotiation end missing)\n");
		return 0;
	}
	if(BT_STAT_IN(minor) & BT_PRN_XFLAG_MASK) {
		bt_err_clear(minor, BT_UNSUPPORTED_MODE);
		bt_terminate(minor);	/* XXX realy required??? */
		BTEXIT(BTTF_ALL,"BiTronics mode not supported!\n");
		return 0;
	}

	bt_data[minor].port_dir = BT_PRN2HOST;
	BTPRINT(BTTF_DIR,"port_dir = %d\n", bt_data[minor].port_dir);
	bt_data[minor].data_avail = bt_data_avail(minor);
	if(bt_data[minor].data_avail < 0) {
		BTEXIT(BTTF_ALL,"err in bt_data_avail()\n");
		return 0;
	} else if (!bt_data[minor].data_avail) {
		BT_CTRL_OUT(minor, BT_HOST_IDLE); /* no data -> no host busy */
	}
	BTEXIT(BTTF_ALL,"OK\n");
	return 1;
}


/*
.P
*/
/******************************************************************************
 * WRITE operation
 ******************************************************************************/

static int bt_write_char(int minor, char c)
/******************************************************************************
 * Writes out one char to the specified printer. Assumes port is already
 * in BT_HOST2PRN mode and BiTronics is off!
 * IN:	minor		Minor device to write to
 *	c		Character to write
 * OUT:	bt_write_char	-1: BiTronics ERROR
 *			0:  Printer busy
 *			1:  OK
 ******************************************************************************/
{
	BTENTER(BTTF_MEDF,  "char = %d\n", c);

	if (!bt_status_wait(minor, BT_PRN_READY_MASK, BT_PRN_READY, BT_STD_TIMEOUT))
	{
		if(BT_STAT_TST(BT_STAT_IN(minor), BT_PRN_ONLY_BUSY_MASK,
		   BT_PRN_ONLY_BUSY))
		{
			BTEXIT(BTTF_MEDF, "prt busy!\n");
			return 0;
		}
		bt_err_clear(minor, BT_WRITE_READY);
		BTEXIT(BTTF_MEDF, "prt status err!\n");
		return -1;
	}
	
	BT_DATA_OUT(minor, c);			/* place char in latch reg.*/

#ifdef BT_STROBE_DELAY
	/* normaly undefined because HPs with BiTronics don't need it */
	udelay(BT_STROBE_DELAY);
#endif

	BT_CTRL_OUT(minor, BT_HOST_STROBE);	/* host signals strobe	*/

#ifdef BT_STROBE_DELAY
	/* normaly undefined because HPs with BiTronics don't need it */
	udelay(BT_STROBE_DELAY);
#endif

	BT_CTRL_OUT(minor, BT_HOST_NORMAL);	/* back to normal	*/

	BTEXIT(BTTF_MEDF, "char written\n");
	return 1;				/* we did it		*/
}



static int bt_write(struct inode* inode, struct file *file, char *buf,
	int count)
/******************************************************************************
 * Performes the writing of data to the printer
 * IN:	inode		Minor devices inode entry
 *	file		Minor devices file structure (not used)
 *	buf		Buffer with the data
 *	count		Nbr of bytes to write
 * OUT:	bt_write	Nbr of written bytes or system error code.
 ******************************************************************************/
{
	int	minor = MINOR(inode->i_rdev);
	int	bufpos = 0;

	BTENTER(BTTF_ALL,"minor = %d, %d chars to write\n", minor, count);

	/*
	 * If port mode does not indicate right direction, we try to terminate
	 * BiTronics operation (which may lead to strange results for bt_read(),
	 * but we are more interested in correctly printed data, than in the
	 * printer or job status...
	 * If BiTronics termination failed, we return an error.
	 */
	if((bt_data[minor].port_dir != BT_HOST2PRN) && !bt_terminate(minor)) {
		BTEXIT(BTTF_ALL,"bt_write(): termination failed!\n");
		return -EBUSY; /* XXX should be -EIO? */
	}

	/*
	 * write out data char by char, check for timeout and interrupt
	 */
	while(bufpos < count) {
		switch(bt_write_char(minor, get_fs_byte(buf + bufpos))) {
		case 1:		/* OK	*/
			bufpos++;
			break;

		case 0:		/* printer busy, sleep	*/
			if(current->signal & ~current->blocked)
			{
				BTEXIT(BTTF_ALL, "Interrupted; %d chars written\n", bufpos);
				return bufpos ? bufpos : -EINTR;
			}
			bt_sleep(BT_TO_WRBUSY);
			break;

		case -1:	/* some error occured	*/
			/* write(2) system call interrupted?  */
			if(current->signal & ~current->blocked)
			{
				BTEXIT(BTTF_ALL, "Interrupted; %d chars written\n", bufpos);
				return bufpos ? bufpos : -EINTR;
			}
			bt_sleep(BT_TO_WRERR);
			break;
		
		default:
			bt_err_clear(minor, BT_UNKNOWN_ERR);
			return -EFAULT;	/* XXX other errcode? */
		} 
	}
	BTEXIT(BTTF_ALL, "OK, %d chars written\n", bufpos);
	return bufpos;
}


/******************************************************************************
 * READ operation
 ******************************************************************************/

int bt_read_char(int minor, char *c)
/******************************************************************************
 * Reads one byte (two nibbles) from the printer
 * IN:  minor           Minor device to use
 * OUT: c               Character read if bt_read_char() == 1
 *      bt_read_char()  -1: BiTronics error, 0: OK, but no data avail, 
 *                       1:  One byte read
 *****************************************************************************/
{
	BTMARK(BTTF_MEDF,"\n");

	/*
	 * data_avail is used for some strange optimization:
	 * a) to reduce the number of data available requests, and
	 * b) to reduce host busy synchronisation
	 */

	if(bt_data[minor].data_avail < 1 &&
           (bt_data[minor].data_avail = bt_data_avail(minor)) < 1)
	{
		return bt_data[minor].data_avail;
	}

	/*
	 * If BiTronics interface is in idle mode, we raise host busy and
	 * wait for ADR ack from printer
	 */
	if(BT_CTRL_IN(minor) & BT_HOST_IDLE_BIT) {
		BT_CTRL_OUT(minor, BT_HOST_BUSY);
		if(!bt_status_wait(minor, BT_PRN_ADR_MASK, BT_PRN_ADR, BT_STD_TIMEOUT)) {
			bt_err_clear(minor, BT_FROM_IDLE);
			return -1;
		}
	}

	BT_CTRL_OUT(minor, BT_HOST_IDLE);        /* dta negociation done */

	/*
	 * read first nibble
	 * XXX perhaps a common function?
	 */
	if(!bt_status_wait(minor, BT_PRN_CLK_L_MASK, BT_PRN_CLK_L, BT_STD_TIMEOUT)) {
		bt_err_clear(minor, BT_START_NIBBLE_1);
		return -1;
	}
	*c = bt_val[(BT_STAT_IN(minor) & BT_DATA_BITS) >> BT_DATA_SHIFT];
	BT_CTRL_OUT(minor, BT_HOST_BUSY);
	if(!bt_status_wait(minor, BT_PRN_CLK_H_MASK, BT_PRN_CLK_H, BT_STD_TIMEOUT)) {
		bt_err_clear(minor, BT_END_NIBBLE_1);
		return -1;
	}
	BT_CTRL_OUT(minor, BT_HOST_IDLE);    
	/*
	 * read second nibble
	 */
	if(!bt_status_wait(minor, BT_PRN_CLK_L_MASK, BT_PRN_CLK_L, BT_STD_TIMEOUT)) {
		bt_err_clear(minor, BT_START_NIBBLE_2);
		return -1;
	}
	*c |= bt_val[((BT_STAT_IN(minor) & BT_DATA_BITS) >> BT_DATA_SHIFT) | BT_UPPER_NIBBLE];
	BT_CTRL_OUT(minor, BT_HOST_BUSY);
	if(!bt_status_wait(minor, BT_PRN_CLK_H_MASK, BT_PRN_CLK_H, BT_STD_TIMEOUT)) {
		bt_err_clear(minor, BT_END_NIBBLE_2);
		return -1;
	}
	/*
	 * strange optimisation: we only drive host to idle if no
	 * add. data avail, AND we store this info!!!
	 */
	if(!(bt_data[minor].data_avail = bt_data_avail(minor)))
		BT_CTRL_OUT(minor, BT_HOST_IDLE);        
	else if (bt_data[minor].data_avail < 0) {
		return -1;
	}
	return 1;		/* OK, we got the data	*/
}

static int bt_read(struct inode* inode, struct file *file, char *buf,
	int count)
/******************************************************************************
 * Performes the readback from the printer
 * IN:	inode		Minor devices inode entry
 *	file		Minor devices file structure (not used)
 *	buf		Buffer with the data
 *	count		Nbr of bytes to write
 ******************************************************************************/
{
	int	minor  = MINOR(inode->i_rdev);
	int	bufpos = 0;
	int	retry  = 0;
	char	c;


	BTENTER(BTTF_ALL,"minor = %d, %d chars to read\n", minor, count);

	/*
	 * fource port into required mode (this has got no impact on
	 * bt_write(), because bt_write() is an atomic operation)
	 */
	if(bt_data[minor].port_dir != BT_PRN2HOST &&
	   bt_data[minor].port_dir != BT_HOST2PRN)
		bt_terminate(minor);		/* terminate non data BiTronics mode */
	if(bt_data[minor].port_dir == BT_HOST2PRN && !bt_negotiate(minor)) {
		BTEXIT(BTTF_ALL,"negotiation failed!\n");
		return -EIO;	/* negotiation failed, XXX other errno?	*/
	}

	while(bufpos < count) {
		switch(bt_read_char(minor, &c)) {
		case  1:	/* ok, we got a byte	*/
			put_fs_byte(c, buf + bufpos);
			bufpos++;
			retry = 0;	/* reset cntr	*/
			break;

		case  0:
			/*
			 * No (more) data, but also no error
			 *
			 * Because BT is a little bit slow, we start
			 * a retry operation instead of directly returning
			 * Uhhh, this is uggly but usefull...
			 */
			if(++retry > BT_READ_RETRYS) {
				BTEXIT(BTTF_ALL,"%d chars of %d read:\n", bufpos, count);
				return bufpos;
			}
			if(current->signal & ~current->blocked) {
				/* read(2) system call interrupted	*/
				BTEXIT(BTTF_ALL, "read(2) interrupted\n");
				return bufpos ? bufpos : -EINTR;
			}
			bt_sleep(BT_TO_RDOK);
			break;

		case -1:
			/*
			 * Error during read
			 *
			 * This is a difficult situation, because there
			 * are a lot of reasons why the read might have
			 * failed, most of them fatal...
			 * At least the situation is stable, because
			 * bt_err_clear() already has invoked bt_reset()
			 * and we are back in HOST2PRN mode. We drive 
			 * a recovery operation even for most of the
			 * fatal errors).
			 */
			BTPRINT(BTTF_ALL,"error from bt_read_char()!\n");

			if(current->signal & ~current->blocked) {
				/* read(2) system call interrupted	*/
				BTEXIT(BTTF_ALL, "read(2) interrupted\n");
				return bufpos ? bufpos : -EINTR;
			}

			if(++retry > BT_READ_RETRYS) {
				/* retry cntr zero: game over	*/
				BTEXIT(BTTF_ALL, "fatal error (retries failed)\n");
				return bufpos ? bufpos : -EIO;
			}

			/*
			 * OK, we wait a little bit and then we
			 * try a re-negotiation ...
			 */
			bt_sleep(BT_TO_RDERR);
			if(!bt_negotiate(minor)) {
				BTEXIT(BTTF_ALL,"retry negotiation failed!\n");
				return bufpos ? bufpos : -EIO; /* XXX other errno?	*/
			}
			break;

		default:
			bt_err_clear(minor, BT_UNKNOWN_ERR);
			return -EFAULT;	/* XXX other errcode? */
		}
	}	

	BTEXIT(BTTF_ALL,"%d chars read\n", bufpos);
	return bufpos;
}


/******************************************************************************
 * Select (currently I would not count on this...)
 ******************************************************************************/
#if 0
static int bt_select(struct inode *inode, struct file *file,
		int sel_type, select_table * wait)
/******************************************************************************
 * Tests read/write conditions of the specific port
 * (IRQs might be nice...)
 *
 * OUT:	bt_select	1: condition met, 0 not met
 ******************************************************************************/
{
	unsigned int minor = MINOR(inode->i_rdev);

	switch (sel_type) {
	case SEL_IN:	/* something to read?	*/
		/*
		if not in prn -> host mode
			negotiate  -- will this work with all the rescheduling???
				   -- will it not disturbe any dta to write???
		if data available  -- reuse function 
			return 1
		-- can't wait for an event because not IRQ driven
		-- what else to do???
		return 0 
		 
		*/
		return 0;

	case SEL_EX:	/* exception condition?	*/
		/*
		-- what exception conditions to wait for?
		-- paper out? perror? data avail?
		-- which direction to use?
		*/
		return 0;

	case SEL_OUT:	/* something to write?	*/
		/*
		if not in host->prn mode
			terminate	-- see remarks for SEL_IN
		check status port	-- which status bits to check???
		*/
		return 0;

	default:
		return 0;
	} /* switch() */
}
#endif


/******************************************************************************
 * Open/Release & Initialisation/Deinitialisation
 ******************************************************************************/

static int bt_open(struct inode *inode, struct file *file)
/******************************************************************************
 * Probes if a minor device exists and - if yes - brings it into service
 * IN:	inode		Inode of /dev entry
 *	file		Not used
 * OUT: bt_open		System error code or 0 if OK.
 ******************************************************************************/
{
	unsigned int minor = MINOR(inode->i_rdev);

	BTENTER(BTTF_ALL,"minor = %d\n", minor);
	
	if((minor >= BT_NBR) || (bt_data[minor].port_dir == BT_NO_PORT)) {
		BTEXIT(BTTF_ALL,"no such port!\n");
		return -ENODEV;	/* don't have such a port	*/
	}

	BTPRINT(BTTF_DIR,"port_dir = %d\n", bt_data[minor].port_dir);
	if(bt_data[minor].busy) {
		BTEXIT(BTTF_ALL, "port already in use!\n");
		return -EBUSY;	/* port is already in use	*/
	}

#ifdef BT_COOPERATE_WITH_LP
	/*
	 * This code considered harmfull!!!
	 *
	 * a) It accesses data which is outside of the scope of the own driver.
         *    This will definitely leed to sporadic crashes in a multithreaded
	 *    environment.
	 *
	 * b) Any change in the corresponding part of the lp code will break
	 *    this code.
	 */
	if(LP_F(minor) & LP_BUSY) {	/* lp already uses the port	   */
		BTEXIT(BTTF_ALL, "lp driver already uses the port!\n");
		return -EBUSY;
	}

	LP_F(minor) |= LP_BUSY;	/* tell lp driver that device is now busy */
#endif

	bt_data[minor].busy = 1;
	bt_data[minor].last_error = BT_NO_ERROR;

	BTEXIT(BTTF_ALL,"OK\n");
	return 0;
}


static void bt_release(struct inode *inode, struct file *file)
/******************************************************************************
 * Release the driver
 * IN:	inode		Inode of /dev entry
 *	file		Not used
 *****************************************************************************/
{
	unsigned int minor = MINOR(inode->i_rdev);

	BTMARK(BTTF_ALL,"minor = %d\n", minor);

#ifdef BT_COOPERATE_WITH_LP
	/*
	 * This code considered harmfull!!!
	 *
	 * a) It accesses data which is outside of the scope of the own driver.
         *    This will definitely leed to sporadic crashes in a multithreaded
	 *    environment.
	 *
	 * b) Any change in the corresponding part of the lp code will break
	 *    this code.
	 */
	LP_F(minor) &= ~LP_BUSY;
#endif
	bt_data[minor].busy = 0;
}


static int  bt_probe_port(int minor)
/******************************************************************************
 * Probe the port corresponding to the given minor device and - if port
 * exists - mark it as usable (change port mode from BT_NO_PORT to BT_HOST2PRN). 
 * IN:	minor		Minor number
 * OUT:	bt_probe_port	0: Port does not exist; 1: Port exists
 *****************************************************************************/
{
	BTMARK(BTTF_LOWF,"port # %d\n", minor);

	BT_DATA_OUT(minor, 0);	/* write zero in controllers latch register */
	udelay(BT_STD_DELAY);	/* don't overrun the hardware		*/
	if(!BT_DATA_IN(minor)) { /* read the value back	*/
		/*
		 * Ok, there is a port
		 */
		printk(BT_DRIVER_NAME ": parallel port at 0x%X is minor device # %d (bt%d)\n",
			bt_data[minor].base_addr, minor, minor);
	 	bt_reset(minor);	/* reset the port to normal op	*/
	 	return 1;
	} else {
	 	bt_data[minor].port_dir = BT_NO_PORT;
	 	return 0;
	}
}


long bt_init(long kmem_start)
/******************************************************************************
 * Initialize the driver (really strange that this is called by mem.c, but
 * if Linus thinks the world has to be like this, we'll agree...)
 * IN:	kmem_start	First kernel mem addr. which could be used
 * OUT:	bt_init		New first unused kernel mem addr.
 *****************************************************************************/
{
	int	minor;
	int	n_existing = 0;	/* nbr of existing ports	*/

	BT_USE(bt_RCS_ID);	/* get rid of "defined but not used" warning */

	/*
	 * use the chance to make ourselfe known
	 */
	if (register_chrdev(BT_MAJOR, BT_DRIVER_NAME, &bt_fops)) {
		printk(BT_DRIVER_NAME ": unable to get major # %d for driver\ndriver not usable!\n", BT_MAJOR);
		return kmem_start;
			/* XXX perhaps a loop to get first unused major # ???*/
			/* XXX perhaps auto creation of /dev/bt? entries???  */
	}


	printk(BT_DRIVER_NAME ": Experimental BiTronics device driver v%s at major # %d\n", bt_version, BT_MAJOR);

	for(minor = 0; minor < BT_NBR; minor++) {
		/*
		 * iterate over all possible ports and note succesfull attempts
		 */
		n_existing += bt_probe_port(minor);
	}
	if(!n_existing)
		printk(BT_DRIVER_NAME ": no printer ports found!\n");

	return kmem_start;
}
