/*
 * Copyright (C) 2006-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#ifdef STATE
struct {
	union {
		struct {
			unsigned int spp	:1;	/* interface is in spp mode */
			unsigned int nibble	:1;	/* also allows nibble mode */
			unsigned int tristate	:1;	/* now also tristate */
			unsigned int epp	:1;	/* epp */
			unsigned int useirq	:1;	/* irq */
			unsigned int reserved	:27;	/* reserved */
		} set;
		int all;
	} mode;				/* set mode of parport emulation */

	union {
		struct {
			unsigned int spp         :1;
			unsigned int nibble      :1;
			unsigned int tristate    :1;
			unsigned int epp         :1;
			unsigned int useirq	 :1;
			unsigned int reserved    :27;
		} set;
		int all;
	} modemask;			/* set which modes are allowed */

	/* Register Base+0 */
	union {
		unsigned char all;
	} data_reg;

	/* Register Base+1 */
	union {
		struct {
			/* lowest bit first*/
			unsigned int reserved_0	:1;
			unsigned int reserved_1	:1;
			unsigned int n_irq	:1;  /* used only on a few ports.
						Reset to 1 (no interrupt occured) on read then */
			unsigned int error	:1;
			unsigned int select_in	:1;
			unsigned int paper_out	:1;
			unsigned int ack	:1;
			unsigned int busy	:1;
			/* prefix n_ means the bit is active low */
		} spp_mode;
		struct {
			/* lowest bit first*/
			unsigned int epp_timeout:1;
			unsigned int reserved_1	:1;  /* used only on a few ports.
						Reset to 1 (no interrupt occured) on read then */
			unsigned int n_irq	:1;
			unsigned int u_def_2	:1;
			unsigned int xf_u_def_3	:1;  /* user defined flag 3, unless in 1284 negotiation mode */
			unsigned int u_def_1	:1;
			unsigned int intr	:1;
			unsigned int n_busy	:1;
			/* prefix n_ means the bit is active low */
		} epp_mode;
		unsigned char all;
	} status_reg;

	/* Register Base+2 */
	union {
		struct {
			/* lowest bit first */
			unsigned int strobe	:1;
			unsigned int auto_lf	:1;
			unsigned int init	:1;
			unsigned int slct_in	:1;
			unsigned int enable_irq	:1;
			unsigned int enable_bidi:1;
			unsigned int x_bit6	:1;
			unsigned int x_bit7	:1;
		} spp_mode;
		struct {
			/* lowest bit first */
			unsigned int write	:1;
			unsigned int data_strb	:1;
			unsigned int init	:1;
			unsigned int addr_strb	:1;
			unsigned int enable_irq	:1;
			unsigned int enable_bidi:1;
			unsigned int x_bit6	:1;
			unsigned int x_bit7	:1;
		} epp_mode;
		unsigned char all;
	} control_reg;

	/* EPP registers */
	unsigned char epp_data_reg;
	unsigned char epp_address_reg;
} NAME;
#endif /* STATE */

#ifdef BEHAVIOR

#include <assert.h>
#include <stdio.h>

static uint8_t
NAME_(inb)(struct cpssp *cpssp, uint16_t port)
{
	switch (port) {
	case 0: /* data register */
		return cpssp->NAME.data_reg.all;

	case 1: /* status register */
		if (cpssp->NAME.mode.set.useirq) {
			cpssp->NAME.status_reg.spp_mode.n_irq = 1;
		}
		return cpssp->NAME.status_reg.all;

	case 2: 
		return cpssp->NAME.control_reg.all;

	case 3: /* epp address register */
		return cpssp->NAME.epp_address_reg;

	case 4: /* epp data register */
		/* Implement TIMEOUT Message into protocol - FIXME Peter */
		
		/* pretend EPP hack */
		if (cpssp->NAME.mode.set.epp){
			cpssp->NAME.status_reg.epp_mode.epp_timeout = 1;
		}
		return cpssp->NAME.epp_data_reg;

	case 5 ... 7: /* additional epp register */
		/* Not supported (yet) */
		faum_log(FAUM_LOG_ERROR, "Parallel Port",
				SNAME,
				"Reading from unimplemented register %d.\n",
				port);
		return 0;

	default:
		assert(0);	/* Mustn't happen. */
	}
	return 0; /* Can't happen */
}

static void
NAME_(outb)(struct cpssp *cpssp, uint8_t value, unsigned int port)
{
	struct sig_parallel_msg sc;

	switch (port) {
	case 0: /* data register */
		cpssp->NAME.data_reg.all = value;
		break;

	case 1: /* status register */
		if (cpssp->NAME.mode.set.epp){
			if (value & 0x01) {
				cpssp->NAME.status_reg.epp_mode.epp_timeout = 0;	
			}
		}
		return;

	case 2: { /* control register */ 
		unsigned char swap;

		if (value & 0xC0) {
			fprintf(stderr, "WARNING: par: %s.\n",
					"Accessing reserved bits");
		} 
		if (cpssp->NAME.modemask.set.tristate) {
			if ((value >> 5) & 1) {
				cpssp->NAME.mode.set.tristate = 1;
				cpssp->NAME.control_reg.spp_mode.enable_bidi = 1;
			} else {
				cpssp->NAME.mode.set.tristate = 0;
				cpssp->NAME.control_reg.spp_mode.enable_bidi = 0;
			}
		}
		if (cpssp->NAME.modemask.set.useirq) {
			if ((value >> 4) & 1) {
				cpssp->NAME.mode.set.useirq = 1;
			} else {
				cpssp->NAME.mode.set.useirq = 0;
			}
		}

		swap = cpssp->NAME.control_reg.all;
		cpssp->NAME.control_reg.all = value;

		if ((swap & 0x0f) == (value & 0x0f)) {
			/*
			 * Avoid sending a packet if the changes are not visible
			 * for the peripheral.
			 */
			return;
		}
		break;
	    }
	case 3: /* epp address register */
		cpssp->NAME.epp_address_reg = value;

		/* FIXME Peter - not yet implemented */
		
		return;

	case 4: /* epp data register */
		cpssp->NAME.epp_data_reg = value;

		/* FIXME Peter - not yet implemented */
		
		return;

	case 5 ... 7: /* additional registers */
		/* Not supported by this controller */
		faum_log(FAUM_LOG_ERROR,
				"Parallel Port",
				SNAME,
				"Writing to unimplemented register %d.\n",
				port);
		return;

	default:
		assert(0);	/* Mustn't happen. */
	}

	sc.data = cpssp->NAME.data_reg.all;
	sc.control = cpssp->NAME.control_reg.all & 0x0f;	/* Only send bits 0-3 */
	sc.status = cpssp->NAME.status_reg.all & 0xf8;	/* Only send bits 3-7 */
	
	NAME_(send)(cpssp, &sc);
}

static void
NAME_(recv)(struct cpssp *cpssp, struct sig_parallel_msg *sc)
{
	unsigned char swap;

	/* Save previous status */
	swap = cpssp->NAME.status_reg.all;

	/* Compile new status */
	cpssp->NAME.status_reg.all
		= (sc->status & 0xf8)
		| (cpssp->NAME.status_reg.all & 0x07);

	if (cpssp->NAME.modemask.set.useirq == 1
	 && cpssp->NAME.mode.set.useirq == 1
	 && ((sc->status >> 6) & 1) != ((swap >> 6) & 1)) {
		/* ACK status changed. */
		int ack;

		ack = (sc->status >> 6) & 1;
		if (ack) {
			cpssp->NAME.status_reg.spp_mode.n_irq = 0;
		}
		NAME_(irq_set)(cpssp, ack);
	}
}

static void
NAME_(reset)(struct cpssp *cpssp)
{
	cpssp->NAME.modemask.all = 0x01;
	cpssp->NAME.mode.all = 0x01; /* only SPP activated */
#if 0
	cpssp->NAME.mode.set.epp = 1;
#endif
	cpssp->NAME.epp_data_reg = 0x37;

	/* Setup registers */
	cpssp->NAME.data_reg.all = 0;
	cpssp->NAME.status_reg.all = 0xD8;
	cpssp->NAME.control_reg.all = 0x4;

	/* enable irqs by setting both values to 1 */
	/* This actually seems not to make sense in
	 * conjunction with the lp driver of 2.2.x,
	 * but maybe with other peripherals...  */

	cpssp->NAME.modemask.set.useirq = 0;
	cpssp->NAME.status_reg.spp_mode.n_irq = 0;
}

static void
NAME_(init)(struct cpssp *cpssp)
{
	/* Nothing to do... */
}

#endif /* BEHAVIOR */
