#ifdef CONFIG_XENO_DRIVERS_ANALOGY_8255
#ifdef CONFIG_XENO_DRIVERS_ANALOGY
/**
 * @file
 * Analogy driver for standard parallel port
 * @note Copyright (C) 1998,2001 David A. Schleef <ds@schleef.org>
 *
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Xenomai; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* 
   A cheap and easy way to get a few more digital I/O lines.  Steal
   additional parallel ports from old computers or your neighbors'
   computers.

   Attach options list:
   0: I/O port base for the parallel port.
   1: IRQ

   Parallel Port Lines:

   pin     subdev  chan    aka
   ---     ------  ----    ---
   1       2       0       strobe
   2       0       0       data 0
   3       0       1       data 1
   4       0       2       data 2
   5       0       3       data 3
   6       0       4       data 4
   7       0       5       data 5
   8       0       6       data 6
   9       0       7       data 7
   10      1       3       acknowledge
   11      1       4       busy
   12      1       2       output
   13      1       1       printer selected
   14      2       1       auto LF
   15      1       0       error
   16      2       2       init
   17      2       3       select printer
   18-25   ground

   Notes:

   Subdevices 0 is digital I/O, subdevice 1 is digital input, and
   subdevice 2 is digital output.  Unlike other Analogy devices,
   subdevice 0 defaults to output.

   Pins 13 and 14 are inverted once by Analogy and once by the
   hardware, thus cancelling the effect.

   Pin 1 is a strobe, thus acts like one.  There's no way in software
   to change this, at least on a standard parallel port.

   Subdevice 3 pretends to be a digital input subdevice, but it always
   returns 0 when read.  However, if you run a command with
   scan_begin_src=TRIG_EXT, it uses pin 10 as a external triggering
   pin, which can be used to wake up tasks.

   see http://www.beyondlogic.org/ for information.
   or http://www.linux-magazin.de/ausgabe/1999/10/IO/io.html
*/

#include <linux/module.h>
#include <linux/ioport.h>
#include <analogy/analogy_driver.h>

#define PARPORT_SIZE 3

#define PARPORT_A 0
#define PARPORT_B 1
#define PARPORT_C 2

#define DEFAULT_ADDRESS 0x378
#define DEFAULT_IRQ 7

typedef struct parport_subd_priv {
	unsigned long io_bits;
} parport_spriv_t;

typedef struct parport_priv {
	unsigned long io_base;
	unsigned int a_data;
	unsigned int c_data;
	int enable_irq;
} parport_priv_t;

#define devpriv ((parport_priv_t *)(dev->priv))

static int parport_insn_a(a4l_subd_t *subd, a4l_kinsn_t *insn)
{
	a4l_dev_t *dev = subd->dev;
	uint8_t *data = (uint8_t *)insn->data;

	if (data[0]) {
		devpriv->a_data &= ~data[0];
		devpriv->a_data |= (data[0] & data[1]);

		outb(devpriv->a_data, devpriv->io_base + PARPORT_A);
	}

	data[1] = inb(devpriv->io_base + PARPORT_A);

	return 0;
}

static int parport_insn_config_a(a4l_subd_t *subd, a4l_kinsn_t *insn)
{
	a4l_dev_t *dev = subd->dev;
	parport_spriv_t *spriv = (parport_spriv_t *)subd->priv;
	unsigned int *data = (unsigned int *)insn->data;

	/* No need to check the channel descriptor; the input / output
	   setting is global for all channels */

	switch (data[0]) {

	case A4L_INSN_CONFIG_DIO_OUTPUT:
		spriv->io_bits = 0xff;
		devpriv->c_data &= ~(1 << 5);		
		break;

	case A4L_INSN_CONFIG_DIO_INPUT:
		spriv->io_bits = 0;
		devpriv->c_data |= (1 << 5);
		break;

	case A4L_INSN_CONFIG_DIO_QUERY:
		data[1] = (spriv->io_bits == 0xff) ? 
			A4L_OUTPUT: A4L_INPUT;
		break;

	default:
		return -EINVAL;
	}

	outb(devpriv->c_data, devpriv->io_base + PARPORT_C);

	return 0;
}

static int parport_insn_b(a4l_subd_t *subd, a4l_kinsn_t *insn)
{
	a4l_dev_t *dev = subd->dev;
	uint8_t *data = (uint8_t *)insn->data;

	if (data[0]) {
		/* should writes be ignored? */
	}

	data[1] = (inb(devpriv->io_base + PARPORT_B) >> 3);

	return 0;
}

static int parport_insn_c(a4l_subd_t *subd, a4l_kinsn_t *insn)
{
	a4l_dev_t *dev = subd->dev;
	uint8_t *data = (uint8_t *)insn->data;

	data[0] &= 0x0f;
	if (data[0]) {
		devpriv->c_data &= ~data[0];
		devpriv->c_data |= (data[0] & data[1]);

		outb(devpriv->c_data, devpriv->io_base + PARPORT_C);
	}

	data[1] = devpriv->c_data & 0xf;

	return 2;
}

static int parport_intr_insn(a4l_subd_t *subd, a4l_kinsn_t *insn)
{
	uint8_t *data = (uint8_t *)insn->data;

	if (insn->data_size < sizeof(uint8_t))
		return -EINVAL;

	data[1] = 0;
	return 0;
}

static a4l_cmd_t parport_intr_cmd_mask = {
	.idx_subd = 0,
	.start_src = TRIG_NOW,
	.scan_begin_src = TRIG_EXT,
	.convert_src = TRIG_FOLLOW,
	.scan_end_src = TRIG_COUNT,
	.stop_src = TRIG_NONE,
};

static int parport_intr_cmdtest(a4l_subd_t *subd, a4l_cmd_t * cmd)
{

	if (cmd->start_arg != 0) {
		return -EINVAL;
	}
	if (cmd->scan_begin_arg != 0) {
		return -EINVAL;
	}
	if (cmd->convert_arg != 0) {
		return -EINVAL;
	}
	if (cmd->scan_end_arg != 1) {
		return -EINVAL;
	}
	if (cmd->stop_arg != 0) {
		return -EINVAL;
	}

	return 0;
}

static int parport_intr_cmd(a4l_subd_t *subd, a4l_cmd_t *cmd)
{
	a4l_dev_t *dev = subd->dev;

	devpriv->c_data |= 0x10;
	outb(devpriv->c_data, devpriv->io_base + PARPORT_C);

	devpriv->enable_irq = 1;

	return 0;
}

static int parport_intr_cancel(a4l_subd_t *subd)
{
	a4l_dev_t *dev = subd->dev;

	a4l_info(dev, "parport_intr_cancel: cancel in progress\n");

	devpriv->c_data &= ~0x10;
	outb(devpriv->c_data, devpriv->io_base + PARPORT_C);

	devpriv->enable_irq = 0;

	return 0;
}

static int parport_interrupt(unsigned int irq, void *d)
{
	a4l_dev_t *dev = d;
	a4l_subd_t *subd = a4l_get_subd(dev, 3);

	if (!devpriv->enable_irq) {
		a4l_err(dev, "parport_interrupt: bogus irq, ignored\n");
		return IRQ_NONE;
	}

	a4l_buf_put(subd, 0, sizeof(unsigned int));
	a4l_buf_evt(subd, 0);
	
	return 0;
}


/* --- Channels descriptor --- */

static a4l_chdesc_t parport_chan_desc_a = {
	.mode = A4L_CHAN_GLOBAL_CHANDESC,
	.length = 8,
	.chans = { 
		{A4L_CHAN_AREF_GROUND, 1},
	},
};

static a4l_chdesc_t parport_chan_desc_b = {
	.mode = A4L_CHAN_GLOBAL_CHANDESC,
	.length = 5,
	.chans = { 
		{A4L_CHAN_AREF_GROUND, 1},
	},
};

static a4l_chdesc_t parport_chan_desc_c = {
	.mode = A4L_CHAN_GLOBAL_CHANDESC,
	.length = 4,
	.chans = { 
		{A4L_CHAN_AREF_GROUND, 1},
	},
};

static a4l_chdesc_t parport_chan_desc_intr = {
	.mode = A4L_CHAN_GLOBAL_CHANDESC,
	.length = 1,
	.chans = { 
		{A4L_CHAN_AREF_GROUND, 1},
	},
};

/* --- Subdevice initialization functions --- */

static void setup_subd_a(a4l_subd_t *subd)
{
	subd->flags = A4L_SUBD_DIO;
	subd->chan_desc = &parport_chan_desc_a;
	subd->rng_desc = &range_digital;
	subd->insn_bits = parport_insn_a;
	subd->insn_config = parport_insn_config_a;
}

static void setup_subd_b(a4l_subd_t *subd)
{
	subd->flags = A4L_SUBD_DI;
	subd->chan_desc = &parport_chan_desc_b;
	subd->rng_desc = &range_digital;
	subd->insn_bits = parport_insn_b;
}

static void setup_subd_c(a4l_subd_t *subd)
{
	subd->flags = A4L_SUBD_DO;
	subd->chan_desc = &parport_chan_desc_c;
	subd->rng_desc = &range_digital;
	subd->insn_bits = parport_insn_c;
}

static void setup_subd_intr(a4l_subd_t *subd)
{
	subd->flags = A4L_SUBD_DI;
	subd->chan_desc = &parport_chan_desc_intr;
	subd->rng_desc = &range_digital;
	subd->insn_bits = parport_intr_insn;
	subd->cmd_mask = &parport_intr_cmd_mask;
	subd->do_cmdtest = parport_intr_cmdtest;
	subd->do_cmd = parport_intr_cmd;
	subd->cancel = parport_intr_cancel;
}

static void (*setup_subds[3])(a4l_subd_t *) = {
	setup_subd_a,
	setup_subd_b,
	setup_subd_c
};

static int dev_parport_attach(a4l_dev_t *dev, a4l_lnkdesc_t *arg)
{
	int i, err = 0, irq = A4L_IRQ_UNUSED;
	unsigned long io_base;

	if(arg->opts == NULL || arg->opts_size < sizeof(unsigned long)) {

		a4l_warn(dev, 
			 "dev_parport_attach: no attach options specified, "
			 "taking default options (addr=0x%x, irq=%d)\n",
			 DEFAULT_ADDRESS, DEFAULT_IRQ);

		io_base = DEFAULT_ADDRESS;
		irq = DEFAULT_IRQ;
	} else {

		io_base = ((unsigned long *)arg->opts)[0];

		if (arg->opts_size >= 2 * sizeof(unsigned long))
			irq = (int) ((unsigned long *)arg->opts)[1];
	}
	
	if (!request_region(io_base, PARPORT_SIZE, "analogy_parport")) {
		a4l_err(dev, "dev_parport_attach: I/O port conflict");
		return -EIO;
	}

	a4l_info(dev, "dev_parport_attach: address = 0x%lx\n", io_base);

	for (i = 0; i < 3; i++) {
		
		a4l_subd_t *subd = a4l_alloc_subd(sizeof(parport_spriv_t), 
						  setup_subds[i]);
		if (subd == NULL)
			return -ENOMEM;
		
		err = a4l_add_subd(dev, subd);
		if (err != i)
			return err;		
	}

	if (irq != A4L_IRQ_UNUSED) {

		a4l_subd_t *subd;

		a4l_info(dev, "dev_parport_attach: irq = %d\n", irq);

		err = a4l_request_irq(dev, irq, parport_interrupt, 0, dev);
		if (err < 0) {
			a4l_err(dev, "dev_parport_attach: irq not available\n");
			return err;
		}

		subd = a4l_alloc_subd(0, setup_subd_intr);
		if (subd == NULL)
			return -ENOMEM;
		
		err = a4l_add_subd(dev, subd);
		if (err < 0)
			return err;
	}

	devpriv->io_base = io_base;

	devpriv->a_data = 0;
	outb(devpriv->a_data, devpriv->io_base + PARPORT_A);

	devpriv->c_data = 0;
	outb(devpriv->c_data, devpriv->io_base + PARPORT_C);

	return 0;
}

static int dev_parport_detach(a4l_dev_t *dev)
{
	int err = 0;

	if (devpriv->io_base != 0)
		release_region(devpriv->io_base, PARPORT_SIZE);
	
	if (a4l_get_irq(dev) != A4L_IRQ_UNUSED) {
		a4l_free_irq(dev, a4l_get_irq(dev));
	}

	
	return err;
}

static a4l_drv_t drv_parport = {
	.owner = THIS_MODULE,
	.board_name = "analogy_parport",
	.attach = dev_parport_attach,
	.detach = dev_parport_detach,	
	.privdata_size = sizeof(parport_priv_t),
};

static int __init drv_parport_init(void)
{
	return a4l_register_drv(&drv_parport);
}

static void __exit drv_parport_cleanup(void)
{
	a4l_unregister_drv(&drv_parport);
}

MODULE_DESCRIPTION("Analogy driver for standard parallel port");
MODULE_LICENSE("GPL");

module_init(drv_parport_init);
module_exit(drv_parport_cleanup);
#endif
#endif
