/*
 * lpportio.h
 * 
 * Parallel port (lp port) lowest level driver for various 
 * boards connected to the parallel port. 
 * Requires "bidirectional port", _no_ ECP or EPP. 
 * 
 * Copyright (c) 2003 by Wolfgang Wieser (wwieser@gmx.de) 
 * 
 * This file may be distributed and/or modified under the terms of the 
 * GNU General Public License version 2 as published by the Free Software 
 * Foundation. 
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 */

#ifndef _LPPORTIO_H_
#define _LPPORTIO_H_

#include <linux/lp.h>
#include <sys/io.h>
#include <sys/types.h>
#include <stdio.h>


#define CONTROL_OUTPORT_DELAY  for(int i=0; i<35; i++); /* WAS: 35 */
#define DATA_OUTPORT_DELAY   /*for(int i=0; i<350; i++);*/  /* WAS: 0 */

#include <unistd.h>
#include <assert.h>   // debugging only

typedef unsigned char uchar;


// This class is the basic line printer (i.e. "parallel") 
// port IO access class. You can share it among several 
// high-level classes (using one and the same instance!). 
// The setbit/clearbit routies are provided to allow different 
// classes to use the LPPortIO simultaniously without interfering 
// each other. (Well, at least it allows you to program in that 
// way; the class will not enforce it.) 
class LPPortIO
{
	public:
		// Control port bits: 
		enum
		{
			C_STROBE=0x01,    /* INVERTED */
			C_AUTOLF=0x02,    /* INVERTED */
			C_INIT=  0x04,    /* ok       */
			C_SELECT=0x08,    /* INVERTED */
			C_INTREN=0x10,    /* ?? ok; interrupt enable = SET */
			C_INPEN= 0x20,    /* ok; input enable = SET */
			C_INVMASK=0x0b    // XOR inversion mask
		};
		
		// Statuc port bits: 
		enum
		{
			S_ERROR= 0x08,    /* ok       */
			S_SELECT=0x10,    /* ok       */
			S_PEND=  0x20,    /* ok       */
			S_ACK=   0x40,    /* ok       */
			S_BUSY=  0x80,    /* INVERTED */
			S_INVMASK=0x80    // XOR inversion mask
		};
		
		enum
		{
			ODATA_STACK_SIZE=16
		};
		
	private:
		// Port addresses: 
		size_t port_outdata;
		size_t port_status;
		size_t port_control;
		size_t port_indata;
		
		uchar control_bits;
		uchar odata_bits;
		
		int odata_stack_top;
		uchar odata_stack[ODATA_STACK_SIZE];
		
		// Internally used raw port IO. 
		// NOTE: When using EPP and reading from base+3, then 
		// the inb will trigger a SELECT (pin 17) LOW spike if 
		// SELECT is high (i.e. 0x08 in control register not set). 
		inline void do_ctl_outb(uchar val,size_t adr) const
			{  outb(val,adr);  CONTROL_OUTPORT_DELAY  }
			/*{  outb(val,adr); CONTROL_OUTPORT_DELAY 
			assert(inb(adr)==control_bits^C_INVMASK);
			fprintf(stderr,"[%c%02x]",control_bits & C_INPEN ? 'i' : 'o',control_bits^C_INVMASK); usleep(300000); }*/
		inline void do_data_outb(uchar val,size_t adr) const
			{  outb(val,adr);  DATA_OUTPORT_DELAY  }
		inline uchar do_inb(size_t adr) const
			 {  return(inb(adr));  }
			//{  fprintf(stderr,"###\a"); usleep(300000);  uchar x=inb(adr); fprintf(stderr,"###\a");  usleep(300000); return(x); }
		
		// Internally used for stack data overflow. 
		void _stack_error();
		
		// Do not use these: 
		LPPortIO(const LPPortIO &) {}
		void operator=(const LPPortIO &) {}
	public:
		// Check failflag for failure or pass NULL 
		// (will exit on failure). 
		LPPortIO(size_t parport_baseaddr,int *failflag);
		~LPPortIO();
		
		/**** lowest level port access functions: ****/
		// *** DO NOT USE THESE *OUTPUT* FUNCTIONS IF YOU ARE 
		// *** USING THE BIT MODIFYING FUNCTIONS BELOW. 
		
		// Output x on the control port: 
		// Data x is OR'ed together from 
		//    C_STROBE, C_AUTOLF, C_INIT, C_SELECT; 
		// x is XOR'ed with C_INVMASK for positive logic. 
		inline void control_outb(register uchar x)
			{  control_bits=x;  do_ctl_outb(x ^ C_INVMASK,port_control);  }
		
		// Input from status port: 
 		// Result is OR'ed together from 
		//    S_ERROR, S_SELECT, S_PE, S_ACK, S_BUSY; 
		// S_INVMASK is applied to ensure positive logic. 
		inline uchar status_inb(void) const
			{  return((do_inb(port_status)) ^ S_INVMASK);  }
		
		// Data input from lp port: 
		// Result is data, non-inverted, of course. 
		inline uchar data_inb() const
			{  return(do_inb(port_indata));  }
		
		// Data output to lp port: 
		// Write data to port (non-inverted, of course) 
		inline void data_outb(register uchar x)
			{  odata_bits=x;  do_data_outb(x,port_outdata);  }
		
		/**** Bit modifying access functions: ****/
		
		// These functions set/clear the passed bitmask 
		// and call the appropriate port IO function. 
		// Initially, control is queried via inb() and 
		// data is assumed to be 0. 
		
		// These functions use the "lowest level port 
		// access functions" from above, so it's all 
		// positive logic. 
		
		inline void control_setbit(register uchar mask)
			{  control_outb(control_bits|mask);  }
		inline void control_clrbit(register uchar mask)
			{  control_outb(control_bits&~mask);  }
		inline void control_chgbit(register uchar set,register uchar clear)
			{  control_outb((control_bits|set)&~clear);  }
		
		inline void data_setbit(register uchar mask)
			{  data_outb(odata_bits|mask);  }
		inline void data_clrbit(register uchar mask)
			{  data_outb(odata_bits&~mask);  }
		inline void data_chgbit(register uchar set,register uchar clear)
			{  data_outb((odata_bits|set)&~clear);  }
		
		// This is the data stack. 
		// You can push the data on top of the data stack 
		// using push_data(), then modify the port data, 
		// then put the original data back using pop_data(). 
		// Stack size is 16 and should be more than enough. 
		inline void push_data()
		{
			if(odata_stack_top>=ODATA_STACK_SIZE)  _stack_error();
			odata_stack[odata_stack_top++]=odata_bits;
		}
		inline void pop_data()
		{
			if(odata_stack_top<=0)  _stack_error();
			odata_bits=odata_stack[--odata_stack_top];
			data_outb(odata_bits);
		}
};

#endif  /* _LPPORTIO_H_ */

