/* * Analogy driver for standard parallel port * Copyright (C) 1998,2001 David A. Schleef * * 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 #include #include /* For inb/outb */ #include #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(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *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(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *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(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *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(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *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(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { uint8_t *data = (uint8_t *)insn->data; if (insn->data_size < sizeof(uint8_t)) return -EINVAL; data[1] = 0; return 0; } static struct a4l_cmd_desc 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(struct a4l_subdevice *subd, struct a4l_cmd_desc * 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(struct a4l_subdevice *subd, struct a4l_cmd_desc *cmd) { struct a4l_device *dev = subd->dev; devpriv->c_data |= 0x10; outb(devpriv->c_data, devpriv->io_base + PARPORT_C); devpriv->enable_irq = 1; return 0; } static void parport_intr_cancel(struct a4l_subdevice *subd) { struct a4l_device *dev = subd->dev; a4l_info(dev, "cancel in progress\n"); devpriv->c_data &= ~0x10; outb(devpriv->c_data, devpriv->io_base + PARPORT_C); devpriv->enable_irq = 0; } static int parport_interrupt(unsigned int irq, void *d) { struct a4l_device *dev = d; struct a4l_subdevice *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 struct a4l_channels_desc parport_chan_desc_a = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = 8, .chans = { {A4L_CHAN_AREF_GROUND, 1}, }, }; static struct a4l_channels_desc parport_chan_desc_b = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = 5, .chans = { {A4L_CHAN_AREF_GROUND, 1}, }, }; static struct a4l_channels_desc parport_chan_desc_c = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = 4, .chans = { {A4L_CHAN_AREF_GROUND, 1}, }, }; static struct a4l_channels_desc 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(struct a4l_subdevice *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(struct a4l_subdevice *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(struct a4l_subdevice *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(struct a4l_subdevice *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])(struct a4l_subdevice *) = { setup_subd_a, setup_subd_b, setup_subd_c }; static int dev_parport_attach(struct a4l_device *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, "address = 0x%lx\n", io_base); for (i = 0; i < 3; i++) { struct a4l_subdevice *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) { struct a4l_subdevice *subd; a4l_info(dev, "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(struct a4l_device *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 struct a4l_driver drv_parport = { .owner = THIS_MODULE, .board_name = "analogy_parport", .driver_name = "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);