diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-04-29 21:13:14 +0000 |
commit | 19c9bba94152148523ba0f7ef7cffe3d45656b11 (patch) | |
tree | 40b1cb534496a7f1ca0f5c314a523c69f1fee464 /drivers/pnp | |
parent | 7206675c40394c78a90e74812bbdbf8cf3cca1be (diff) |
Import of Linux/MIPS 2.1.36
Diffstat (limited to 'drivers/pnp')
-rw-r--r-- | drivers/pnp/BUGS-parport | 13 | ||||
-rw-r--r-- | drivers/pnp/Config.in | 16 | ||||
-rw-r--r-- | drivers/pnp/Makefile | 51 | ||||
-rw-r--r-- | drivers/pnp/TODO-parport | 21 | ||||
-rw-r--r-- | drivers/pnp/parport_init.c | 831 | ||||
-rw-r--r-- | drivers/pnp/parport_ll_io.h | 21 | ||||
-rw-r--r-- | drivers/pnp/parport_probe.c | 266 | ||||
-rw-r--r-- | drivers/pnp/parport_procfs.c | 340 | ||||
-rw-r--r-- | drivers/pnp/parport_share.c | 469 |
9 files changed, 2028 insertions, 0 deletions
diff --git a/drivers/pnp/BUGS-parport b/drivers/pnp/BUGS-parport new file mode 100644 index 000000000..6b8420a2c --- /dev/null +++ b/drivers/pnp/BUGS-parport @@ -0,0 +1,13 @@ +Currently known (or at least suspected) bugs in parport: + +o /proc/parport is untested under 2.0.XX + +o SCSI aborts for PPA under 2.0.29 [reported by jmr]. Under investigation. + +o make config (etc) allow you to select CONFIG_PNP_PARPORT=m, CONFIG_PPA=y - + the resulting kernel won't link. + +o IEEE1284 code does not do the terminating handshake after transfers, which + seems to upset some devices. + +o lp doesn't allow you to read status while printing is in progress. diff --git a/drivers/pnp/Config.in b/drivers/pnp/Config.in new file mode 100644 index 000000000..44bb52d68 --- /dev/null +++ b/drivers/pnp/Config.in @@ -0,0 +1,16 @@ +# +# Plug and Play configuration +# + +mainmenu_option next_comment +comment 'Plug and Play support' + +bool 'Plug and Play support' CONFIG_PNP + +if [ "$CONFIG_PNP" = "y" ]; then + if [ "$CONFIG_PNP_PARPORT" != "n" ]; then + bool ' Auto-probe for parallel devices' CONFIG_PNP_PARPORT_AUTOPROBE + fi +fi + +endmenu diff --git a/drivers/pnp/Makefile b/drivers/pnp/Makefile new file mode 100644 index 000000000..6153b8da6 --- /dev/null +++ b/drivers/pnp/Makefile @@ -0,0 +1,51 @@ +# +# Makefile for the kernel Plug-and-Play device drivers. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now inherited from the +# parent makes.. +# +# Note 3! Plug and Play is the Borg. We have assimilated some other +# drivers in the `char', `net' and `scsi' directories, but left them +# there to allay suspicion. + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +L_TARGET := pnp.a +MX_OBJS := +LX_OBJS := +MI_OBJS := +MIX_OBJS := + +ifeq ($(CONFIG_PNP_PARPORT),y) + L_OBJS += parport_share.o + ifeq ($(CONFIG_PROC_FS),y) + L_OBJS += parport_procfs.o + endif + ifeq ($(CONFIG_PNP_PARPORT_AUTOPROBE),y) + L_OBJS += parport_probe.o + endif + LX_OBJS += parport_init.o +else + ifeq ($(CONFIG_PNP_PARPORT),m) + MI_OBJS += parport_share.o + ifneq ($(CONFIG_PROC_FS),n) + MI_OBJS += parport_procfs.o + endif + ifeq ($(CONFIG_PNP_PARPORT_AUTOPROBE),y) + MI_OBJS += parport_probe.o + endif + MIX_OBJS += parport_init.o + M_OBJS += parport.o + endif +endif + +include $(TOPDIR)/Rules.make + +parport.o: $(MI_OBJS) $(MIX_OBJS) + $(LD) $(LD_RFLAG) -r -o $@ $(MI_OBJS) $(MIX_OBJS) diff --git a/drivers/pnp/TODO-parport b/drivers/pnp/TODO-parport new file mode 100644 index 000000000..eea6a01fd --- /dev/null +++ b/drivers/pnp/TODO-parport @@ -0,0 +1,21 @@ +Things to be done. + +0. Fix the bugs (see BUGS-parport). + +1. Proper documentation. + +2. Overhaul lp.c: + + a) It's a mess, and there is a lot of code duplication. + + b) ECP support would be nice. This can only work if both the port and + the printer support it. + + c) Errors could do with being handled better. There's no point logging a + message every 10 seconds when the printer is out of paper. + + d) Handle status readback automatically. IEEE1284 printers can post status + bits when they have something to say. We should read out and deal + with (maybe just log) whatever the printer wants to tell the world. + +3. Assimilate more drivers. diff --git a/drivers/pnp/parport_init.c b/drivers/pnp/parport_init.c new file mode 100644 index 000000000..57574b6a5 --- /dev/null +++ b/drivers/pnp/parport_init.c @@ -0,0 +1,831 @@ +/* $Id: parport_init.c,v 1.3.2.4 1997/04/16 21:20:44 phil Exp $ + * Parallel-port initialisation code. + * + * Authors: David Campbell <campbell@tirian.che.curtin.edu.au> + * Tim Waugh <tmw20@cam.ac.uk> + * Jose Renau <renau@acm.org> + * + * based on work by Grant Guenther <grant@torque.net> + * and Philip Blundell <Philip.Blundell@pobox.com> + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/tasks.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/parport.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/malloc.h> + +#include "parport_ll_io.h" + +static int io[PARPORT_MAX] = { 0, }; +static int irq[PARPORT_MAX] = { PARPORT_IRQ_NONE, }; +static int dma[PARPORT_MAX] = { PARPORT_DMA_NONE, }; + +/****************************************************** + * DMA detection section: + */ + +/* + * Prepare DMA channels from 0-8 to transmit towards buffer + */ +static int parport_prepare_dma(char *buff, int size) +{ + int tmp = 0; + int i,retv; + + for (i = 0; i < 8; i++) { + retv = request_dma(i, "probe"); + if (retv) + continue; + tmp |= 1 << i; + + cli(); + disable_dma(i); + clear_dma_ff(i); + set_dma_addr(i, virt_to_bus(buff)); + set_dma_count(i, size); + set_dma_mode(i, DMA_MODE_READ); + sti(); + } + + return tmp; +} + +/* + * Activate all DMA channels passed in dma + */ +static int parport_enable_dma(int dma) +{ + int i; + + for (i = 0; i < 8; i++) + if (dma & (1 << i)) { + cli(); + enable_dma(i); + sti(); + } + + return dma; +} + +static int parport_detect_dma_transfer(int dma,int size,int *resid) +{ + int i,n,retv; + int count=0; + + retv = -1; + for (i = 0; i < 8; i++) + if (dma & (1 << i)) { + disable_dma(i); + clear_dma_ff(i); + n = get_dma_residue(i); + if (n != size) { + *resid = n; + retv = i; + if (count > 0) { + retv = -1; /* Multiple DMA's */ + printk(KERN_ERR "parport: multiple DMA detected. Huh?\n"); + } + count++; + } + free_dma(i); + } + + return retv; +} + +/* Only if supports ECP mode */ +static int programmable_dma_support(struct parport *pb) +{ + int dma; + + w_ecr(pb,0xE0); /* Configuration MODE */ + + dma = r_cnfgB(pb) & 0x07; + + w_ecr(pb,pb->ecr); + + if( dma == 0 || dma == 4 ) /* Jumper selection */ + return -1; + else + return dma; +} + +/* Only called if port supports ECP mode. + * + * The only restriction on DMA channels is that it has to be + * between 0 to 7 (inclusive). Used only in an ECP mode, DMAs are + * considered a shared resource and hence they should be registered + * when needed and then immediately unregistered. + * + * DMA autoprobes for ECP mode are known not to work for some + * main board BIOS configs. I had to remove everything from the + * port, set the mode to SPP, reboot to DOS, set the mode to ECP, + * and reboot again, then I got IRQ probes and DMA probes to work. + * [Is the BIOS doing a device detection?] + * + * A value of -1 is allowed indicating no DMA support. + * + * if( 0 < DMA < 4 ) + * 1Byte DMA transfer + * else // 4 < DMA < 8 + * 2Byte DMA transfer + * + */ +static int parport_dma_probe(struct parport *pb) +{ + int dma,retv; + int dsr,dsr_read; + char *buff; + + retv = programmable_dma_support(pb); + if (retv != -1) + return retv; + + if (!(buff = kmalloc(2048, GFP_KERNEL | GFP_DMA))) { + printk(KERN_ERR "parport: memory squeeze\n"); + return -1; + } + + dsr = r_ctr(pb); + dsr_read = (dsr & ~(0x20)) | 0x04; /* Direction == read */ + + w_ecr(pb, 0xc0); /* ECP MODE */ + w_ctr(pb, dsr_read ); + dma=parport_prepare_dma(buff,1000); + w_ecr(pb, 0xd8); /* ECP FIFO + enable DMA */ + parport_enable_dma(dma); + udelay(500); /* Give some for DMA tranfer */ + retv = parport_detect_dma_transfer(dma,1000,&pb->speed); + pb->speed = pb->speed * 2000; /* 500uSec * 2000 = 1sec */ + + /* + * National Semiconductors only supports DMA tranfers + * in ECP MODE + */ + if (retv == -1) { + w_ecr(pb, 0x60); /* ECP MODE */ + w_ctr(pb, dsr_read ); + dma=parport_prepare_dma(buff,1000); + w_ecr(pb, 0x68); /* ECP FIFO + enable DMA */ + parport_enable_dma(dma); + udelay(500); /* Give some for DMA tranfer */ + retv = parport_detect_dma_transfer(dma,1000,&pb->speed); + pb->speed = pb->speed * 2000; /* 500uSec * 2000 = 1sec */ + } + + kfree(buff); + + w_ctr(pb, pb->ctr); + w_ecr(pb, pb->ecr); + + return retv; +} +/****************************************************** + * MODE detection section: + */ + +/* + * Clear TIMEOUT BIT in EPP MODE + */ +static int epp_clear_timeout(struct parport *pb) +{ + int r; + + if (!(r_str(pb) & 0x01)) + return 1; + + /* To clear timeout some chips require double read */ + r_str(pb); + r = r_str(pb); + w_str(pb, r | 0x01); /* Some reset by writing 1 */ + w_str(pb, r & 0xfe); /* Others by writing 0 */ + r = r_str(pb); + + return !(r & 0x01); +} + + +/* + * Checks for port existence, all ports support SPP MODE + */ +static int parport_SPP_supported(struct parport *pb) +{ + /* Do a simple read-write test to make sure the port exists. */ + + w_dtr(pb, 0xaa); + if (r_dtr(pb) != 0xaa) return 0; + + w_dtr(pb, 0x55); + if (r_dtr(pb) != 0x55) return 0; + + return PARPORT_MODE_SPP; +} + +/* Check for ECP + * + * Old style XT ports alias io ports every 0x400, hence accessing ECR + * on these cards actually accesses the CTR. + * + * Modern cards don't do this but reading from ECR will return 0xff + * regardless of what is written here if the card does NOT support + * ECP. + * + * We will write 0x2c to ECR and 0xcc to CTR since both of these + * values are "safe" on the CTR since bits 6-7 of CTR are unused. + */ +static int parport_ECR_present(struct parport *pb) +{ + int r; + + r= r_ctr(pb); + if ((r_ecr(pb) & 0x03) == (r & 0x03)) { + w_ctr(pb, r ^ 0x03 ); /* Toggle bits 0-1 */ + + r= r_ctr(pb); + if ((r_ecr(pb) & 0x03) == (r & 0x03)) + return 0; /* Sure that no ECR register exists */ + } + + if ((r_ecr(pb) & 0x03 ) != 0x01) + return 0; + + w_ecr(pb,0x34); + if (r_ecr(pb) != 0x35) + return 0; + + w_ecr(pb,pb->ecr); + w_ctr(pb,pb->ctr); + + return PARPORT_MODE_ECR; +} + +static int parport_ECP_supported(struct parport *pb) +{ + int i; + + /* If there is no ECR, we have no hope of supporting ECP. */ + if (!(pb->modes & PARPORT_MODE_ECR)) + return 0; + + /* + * Using LGS chipset it uses ECR register, but + * it doesn't support ECP or FIFO MODE + */ + + w_ecr(pb, 0xc0); /* TEST FIFO */ + for (i=0; i < 1024 && (r_ecr(pb) & 0x01); i++) + w_fifo(pb, 0xaa); + + w_ecr(pb, pb->ecr); + + if (i == 1024) + return 0; + + return PARPORT_MODE_ECP; +} + +/* EPP mode detection + * Theory: + * Bit 0 of STR is the EPP timeout bit, this bit is 0 + * when EPP is possible and is set high when an EPP timeout + * occurs (EPP uses the HALT line to stop the CPU while it does + * the byte transfer, an EPP timeout occurs if the attached + * device fails to respond after 10 micro seconds). + * + * This bit is cleared by either reading it (National Semi) + * or writing a 1 to the bit (SMC, UMC, WinBond), others ??? + * This bit is always high in non EPP modes. + */ +static int parport_EPP_supported(struct parport *pb) +{ + /* If EPP timeout bit clear then EPP available */ + if (!epp_clear_timeout(pb)) + return 0; /* No way to clear timeout */ + + w_ctr(pb, r_ctr(pb) | 0x20); + w_ctr(pb, r_ctr(pb) | 0x10); + epp_clear_timeout(pb); + + r_epp(pb); + udelay(30); /* Wait for possible EPP timeout */ + + if (r_str(pb) & 0x01) { + epp_clear_timeout(pb); + return PARPORT_MODE_EPP; + } + + return 0; +} + +static int parport_ECPEPP_supported(struct parport *pb) +{ + int mode; + + if (!(pb->modes & PARPORT_MODE_ECR)) + return 0; + + /* Search for SMC style EPP+ECP mode */ + w_ecr(pb, 0x80); + + mode = parport_EPP_supported(pb); + + w_ecr(pb, pb->ecr); + + if (mode) + return PARPORT_MODE_ECPEPP; + + return 0; +} + +/* Detect PS/2 support. + * + * Bit 5 (0x20) sets the PS/2 data direction; setting this high + * allows us to read data from the data lines. In theory we would get back + * 0xff but any peripheral attached to the port may drag some or all of the + * lines down to zero. So if we get back anything that isn't the contents + * of the data register we deem PS/2 support to be present. + * + * Some SPP ports have "half PS/2" ability - you can't turn off the line + * drivers, but an external peripheral with sufficiently beefy drivers of + * its own can overpower them and assert its own levels onto the bus, from + * where they can then be read back as normal. Ports with this property + * and the right type of device attached are likely to fail the SPP test, + * (as they will appear to have stuck bits) and so the fact that they might + * be misdetected here is rather academic. + */ + +static int parport_PS2_supported(struct parport *pb) +{ + int ok = 0; + + epp_clear_timeout(pb); + + w_ctr(pb, pb->ctr | 0x20); /* try to tri-state the buffer */ + + w_dtr(pb, 0x55); + if (r_dtr(pb) != 0x55) ok++; + + w_dtr(pb, 0xaa); + if (r_dtr(pb) != 0xaa) ok++; + + w_ctr(pb, pb->ctr); /* cancel input mode */ + + return ok?PARPORT_MODE_PS2:0; +} + +static int parport_ECPPS2_supported(struct parport *pb) +{ + int mode; + + if (!(pb->modes & PARPORT_MODE_ECR)) + return 0; + + w_ecr(pb, 0x20); + + mode = parport_PS2_supported(pb); + + w_ecr(pb,pb->ecr); + + if (mode) + return PARPORT_MODE_ECPPS2; + + return 0; +} + +/****************************************************** + * IRQ detection section: + * + * This code is for detecting ECP interrupts (due to problems with the + * monolithic interrupt probing routines). + * + * In short this is a voting system where the interrupt with the most + * "votes" is the elected interrupt (it SHOULD work...) + * + * This is horribly x86-specific at the moment. I'm not convinced it + * belongs at all. + */ + +static int intr_vote[16]; + +static void parport_vote_intr_func(int irq, void *dev_id, struct pt_regs *regs) +{ + intr_vote[irq]++; + return; +} + +static long open_intr_election(void) +{ + long tmp = 0; + int i; + + /* We ignore the timer - irq 0 */ + for (i = 1; i < 16; i++) { + intr_vote[i] = 0; + if (request_irq(i, parport_vote_intr_func, + SA_INTERRUPT, "probe", intr_vote) == 0) + tmp |= 1 << i; + } + return tmp; +} + +static int close_intr_election(long tmp) +{ + int irq = PARPORT_IRQ_NONE; + int i; + + /* We ignore the timer - irq 0 */ + for (i = 1; i < 16; i++) + if (tmp & (1 << i)) { + if (intr_vote[i]) { + if (irq != PARPORT_IRQ_NONE) + /* More than one interrupt */ + return PARPORT_IRQ_NONE; + irq = i; + } + free_irq(i, intr_vote); + } + return irq; +} + +/* Only if supports ECP mode */ +static int programmable_irq_support(struct parport *pb) +{ + int irq; + + w_ecr(pb,0xE0); /* Configuration MODE */ + + irq = (r_cnfgB(pb) >> 3) & 0x07; + + switch(irq){ + case 2: + irq = 9; + break; + case 7: + irq = 5; + break; + case 0: + irq = -1; + break; + default: + irq += 7; + } + + w_ecr(pb,pb->ecr); + + return irq; +} + +static int irq_probe_ECP(struct parport *pb) +{ + int irqs,i; + + probe_irq_off(probe_irq_on()); /* Clear any interrupts */ + irqs = open_intr_election(); + + w_ecr(pb, 0x00); /* Reset FIFO */ + w_ctr(pb, pb->ctr ); /* Force direction = 0 */ + w_ecr(pb, 0xd0); /* TEST FIFO + nErrIntrEn */ + + /* If Full FIFO sure that WriteIntrThresold is generated */ + for( i=0 ; i < 1024 && !(r_ecr(pb) & 0x02) ; i++ ){ + w_fifo(pb, 0xaa); + } + + pb->irq = close_intr_election(irqs); + + w_ecr(pb, pb->ecr); + + return pb->irq; +} + +/* + * This detection seems that only works in National Semiconductors + * This doesn't work in SMC, LGS, and Winbond + */ +static int irq_probe_EPP(struct parport *pb) +{ + int irqs; + +#ifndef ADVANCED_DETECT + return -1; +#endif + + probe_irq_off(probe_irq_on()); /* Clear any interrupts */ + irqs = open_intr_election(); + + if( pb->modes & PARPORT_MODE_ECR ) + w_ecr(pb, r_ecr(pb) | 0x10 ); + + epp_clear_timeout(pb); + w_ctr(pb, r_ctr(pb) | 0x20); + w_ctr(pb, r_ctr(pb) | 0x10); + epp_clear_timeout(pb); + + /* Device isn't expecting an EPP read + * and generates an IRQ. + */ + r_epp(pb); + udelay(20); + + pb->irq = close_intr_election(irqs); + + w_ctr(pb,pb->ctr); + + return pb->irq; +} + +static int irq_probe_SPP(struct parport *pb) +{ + int irqs; + +#ifndef ADVANCED_DETECT + return -1; +#endif + + probe_irq_off(probe_irq_on()); /* Clear any interrupts */ + irqs = probe_irq_on(); + + if( pb->modes & PARPORT_MODE_ECR ) + w_ecr(pb, 0x10 ); + + w_dtr(pb,0x00); + w_ctr(pb,0x00); + w_ctr(pb,0x0c); + udelay(5); + w_ctr(pb,0x0d); + udelay(5); + w_ctr(pb,0x0c); + udelay(25); + w_ctr(pb,0x08); + udelay(25); + w_ctr(pb,0x0c); + udelay(50); + + pb->irq = probe_irq_off(irqs); + if (pb->irq <= 0) + pb->irq = PARPORT_IRQ_NONE; /* No interrupt detected */ + + w_ctr(pb,pb->ctr); + + return pb->irq; +} + +/* We will attempt to share interrupt requests since other devices + * such as sound cards and network cards seem to like using the + * printer IRQs. + * + * When ECP is available we can autoprobe for IRQs. + * NOTE: If we can autoprobe it, we can register the IRQ. + */ +static int parport_irq_probe(struct parport *pb) +{ + if (pb->modes & PARPORT_MODE_ECR) + pb->irq = programmable_irq_support(pb); + + if (pb->modes & PARPORT_MODE_ECP) + pb->irq = irq_probe_ECP(pb); + + if (pb->irq == PARPORT_IRQ_NONE && (pb->modes & PARPORT_MODE_ECPEPP)) { + w_ecr(pb,0x80); + pb->irq = irq_probe_EPP(pb); + w_ecr(pb,pb->ecr); + } + + epp_clear_timeout(pb); + + if (pb->irq == PARPORT_IRQ_NONE && (pb->modes & PARPORT_MODE_EPP)) + pb->irq = irq_probe_EPP(pb); + + epp_clear_timeout(pb); + + if (pb->irq == PARPORT_IRQ_NONE) + pb->irq = irq_probe_SPP(pb); + + return pb->irq; +} + + +int initialize_parport(struct parport *pb, unsigned long base, int irq, int dma, int count) +{ + /* Check some parameters */ + if (dma < -2) { + printk(KERN_ERR "parport: Invalid DMA[%d] at base 0x%lx\n",dma,base); + return 0; + } + + if (irq < -2) { + printk(KERN_ERR "parport: Invalid IRQ[%d] at base 0x%lx\n",irq,base); + return 0; + } + + /* Init our structure */ + memset(pb, 0, sizeof(struct parport)); + pb->base = base; + pb->irq = irq; + pb->dma = dma; + pb->modes = 0; + pb->next = NULL; + pb->devices = pb->cad = pb->lurker = NULL; + pb->flags = 0; + + /* Before we start, set the control registers to something sensible. */ + pb->ecr = 0xc; + pb->ctr = 0xc; + + pb->name = kmalloc(15, GFP_KERNEL); + if (!pb->name) { + printk(KERN_ERR "parport: memory squeeze\n"); + return 0; + } + sprintf(pb->name, "parport%d", count); + + if (!parport_SPP_supported(pb)) { + epp_clear_timeout(pb); + if (!parport_SPP_supported(pb)) { + kfree(pb->name); + return 0; + } + } + + pb->modes |= PARPORT_MODE_SPP; /* All ports support SPP mode. */ + pb->modes |= parport_PS2_supported(pb); + + if (pb->base != 0x3bc) { + pb->modes |= parport_ECR_present(pb); + pb->modes |= parport_ECP_supported(pb); + pb->modes |= parport_ECPPS2_supported(pb); + pb->modes |= parport_EPP_supported(pb); + pb->modes |= parport_ECPEPP_supported(pb); + } + + /* Now register regions */ + if ((pb->modes & (PARPORT_MODE_EPP | PARPORT_MODE_ECPEPP)) && + (check_region(pb->base, 8))) { + printk(KERN_INFO "%s: EPP disabled due to port conflict at %x.\n", pb->name, pb->base + 3); + pb->modes &= ~(PARPORT_MODE_EPP | PARPORT_MODE_ECPEPP); + } + pb->size = (pb->modes & (PARPORT_MODE_EPP | PARPORT_MODE_ECPEPP)) ? 8 : 3; + + request_region(pb->base, pb->size, pb->name); + if (pb->modes & PARPORT_MODE_ECR) + request_region(pb->base+0x400, 3, pb->name); + + /* DMA check */ + if (pb->modes & PARPORT_MODE_ECP) { + if (pb->dma == PARPORT_DMA_NONE) + pb->dma = parport_dma_probe(pb); + else if (pb->dma == -2) + pb->dma = PARPORT_DMA_NONE; + } + + /* IRQ check */ + if (pb->irq == PARPORT_IRQ_NONE) + pb->irq = parport_irq_probe(pb); + else if (pb->irq == -2) + pb->irq = PARPORT_IRQ_NONE; + + return 1; +} + +#ifndef MODULE +static int parport_setup_ptr = 0; + +void parport_setup(char *str, int *ints) +{ + if (ints[0] == 0 || ints[1] == 0) { + /* Disable parport if "parport=" or "parport=0" in cmdline */ + io[0] = PARPORT_DISABLE; + return; + } + if (parport_setup_ptr < PARPORT_MAX) { + io[parport_setup_ptr] = ints[1]; + if (ints[0]>1) { + irq[parport_setup_ptr] = ints[2]; + if (ints[0]>2) dma[parport_setup_ptr] = ints[3]; + } + parport_setup_ptr++; + } else { + printk(KERN_ERR "parport=0x%x", ints[1]); + if (ints[0]>1) { + printk(",%d", ints[2]); + if (ints[0]>2) printk(",%d", ints[3]); + } + printk(" ignored, too many ports.\n"); + } +} +#endif + +#ifdef CONFIG_PNP_PARPORT_AUTOPROBE +extern void parport_probe_one(struct parport *port); +#endif + +#ifdef MODULE +MODULE_PARM(io, "1-" __MODULE_STRING(PARPORT_MAX) "i"); +MODULE_PARM(irq, "1-" __MODULE_STRING(PARPORT_MAX) "i"); +MODULE_PARM(dma, "1-" __MODULE_STRING(PARPORT_MAX) "i"); + +int init_module(void) +#else +int pnp_parport_init(void) +#endif /* MODULE */ +{ + struct parport *pb; + + printk(KERN_INFO "Parallel port sharing: %s\n", + "$Revision: 1.3.2.4 $"); + + if (io[0] == PARPORT_DISABLE) return 1; + +#ifdef CONFIG_PROC_FS + parport_proc_init(); +#endif + + /* Run probes to ensure parport does exist */ +#define PORT(a,b,c) \ + if ((pb = parport_register_port((a), (b), (c)))) \ + parport_destroy(pb); + + + if (io[0]) { + /* If the user specified any ports, use them */ + int i; + for (i = 0; io[i] && i < PARPORT_MAX; i++) { + PORT(io[i], irq[i], dma[i]); + } + } else { + /* Go for the standard ports. */ + PORT(0x378, PARPORT_IRQ_NONE, PARPORT_DMA_NONE); + PORT(0x278, PARPORT_IRQ_NONE, PARPORT_DMA_NONE); + PORT(0x3bc, PARPORT_IRQ_NONE, PARPORT_DMA_NONE); +#undef PORT + } + +#if defined(CONFIG_PNP_PARPORT_AUTOPROBE) || defined(CONFIG_PROC_FS) + for (pb = parport_enumerate(); pb; pb = pb->next) { +#ifdef CONFIG_PNP_PARPORT_AUTOPROBE + parport_probe_one(pb); +#endif +#ifdef CONFIG_PROC_FS + parport_proc_register(pb); +#endif + } +#endif + + return 0; +} + +#ifdef MODULE +void cleanup_module(void) +{ + struct parport *port, *next; + + for (port = parport_enumerate(); port; port = next) { + next = port->next; + parport_destroy(port); + parport_proc_unregister(port); + kfree(port->name); + kfree(port); + } + + parport_proc_cleanup(); +} +#endif + +/* Exported symbols for modules. */ + +EXPORT_SYMBOL(parport_claim); +EXPORT_SYMBOL(parport_release); +EXPORT_SYMBOL(parport_register_port); +EXPORT_SYMBOL(parport_destroy); +EXPORT_SYMBOL(parport_register_device); +EXPORT_SYMBOL(parport_unregister_device); +EXPORT_SYMBOL(parport_enumerate); +EXPORT_SYMBOL(parport_ieee1284_nibble_mode_ok); + +#ifdef CONFIG_PNP_PARPORT_AUTOPROBE +EXPORT_SYMBOL(parport_probe); +EXPORT_SYMBOL(parport_probe_one); +#endif + +void inc_parport_count(void) +{ +#ifdef MODULE + MOD_INC_USE_COUNT; +#endif +} + +void dec_parport_count(void) +{ +#ifdef MODULE + MOD_DEC_USE_COUNT; +#endif +} diff --git a/drivers/pnp/parport_ll_io.h b/drivers/pnp/parport_ll_io.h new file mode 100644 index 000000000..c2592aff6 --- /dev/null +++ b/drivers/pnp/parport_ll_io.h @@ -0,0 +1,21 @@ +/* $Id: parport_ll_io.h,v 1.1.2.1 1997/03/26 13:01:09 phil Exp $ + * David Campbell's "favourite IO routines" for parallel ports + */ + +#define r_dtr(x) inb((x)->base) +#define r_str(x) inb((x)->base+1) +#define r_ctr(x) inb((x)->base+2) +#define r_epp(x) inb((x)->base+4) +#define r_fifo(x) inb((x)->base+0x400) +#define r_ecr(x) inb((x)->base+0x402) +#define r_cnfgA(x) inb((x)->base+0x400) +#define r_cnfgB(x) inb((x)->base+0x401) + +#define w_dtr(x,y) outb((y), (x)->base) +#define w_str(x,y) outb((y), (x)->base+1) +#define w_ctr(x,y) outb((y), (x)->base+2) +#define w_epp(x,y) outb((y), (x)->base+4) +#define w_fifo(x,y) outb((y), (x)->base+0x400) +#define w_ecr(x,y) outb((y), (x)->base+0x402) +#define w_cnfgA(x,y) outb((y), (x)->base+0x400) +#define w_cnfgB(x,y) outb((y), (x)->base+0x401) diff --git a/drivers/pnp/parport_probe.c b/drivers/pnp/parport_probe.c new file mode 100644 index 000000000..b40164496 --- /dev/null +++ b/drivers/pnp/parport_probe.c @@ -0,0 +1,266 @@ +/* $Id: parport_probe.c,v 1.1.2.9 1997/03/29 21:08:16 phil Exp $ + * Parallel port device probing code + * + * Authors: Carsten Gross, carsten@sol.wohnheim.uni-ulm.de + * Philip Blundell <Philip.Blundell@pobox.com> + */ + +#include <linux/tasks.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/parport.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/ctype.h> + +#include <linux/lp.h> + +#include <asm/uaccess.h> +#undef DEBUG_PROBE + +static inline int read_nibble(struct parport *port) +{ + unsigned char i; + i = parport_r_status(port)>>3; + i&=~8; + if ( ( i & 0x10) == 0) i|=8; + return(i & 0x0f); +} + +static void read_terminate(struct parport *port) { + parport_w_ctrl(port, (parport_r_ctrl(port) & ~2) | 8); + /* SelectIN high, AutoFeed low */ + if (parport_wait_peripheral(port, 0x80, 0)) + /* timeout, SelectIN high, Autofeed low */ + return; + parport_w_ctrl(port, parport_r_ctrl(port) | 2); + /* AutoFeed high */ + parport_wait_peripheral(port, 0x80, 0x80); + /* no timeout possible, Autofeed low, SelectIN high */ + parport_w_ctrl(port, (parport_r_ctrl(port) & ~2) | 8); + return; +} + +static long read_polled(struct parport *port, char *buf, + unsigned long length) +{ + int i; + char *temp=buf; + int count = 0; + unsigned char z=0; + unsigned char Byte=0; + + for (i=0; ; i++) { + parport_w_ctrl(port, parport_r_ctrl(port) | 2); /* AutoFeed high */ + if (parport_wait_peripheral(port, 0x40, 0)) { + printk("%s: read1 timeout.\n", port->name); + parport_w_ctrl(port, parport_r_ctrl(port) & ~2); + break; + } + z = read_nibble(port); + parport_w_ctrl(port, parport_r_ctrl(port) & ~2); /* AutoFeed low */ + if (parport_wait_peripheral(port, 0x40, 0x40)) { + printk("%s: read2 timeout.\n", port->name); + break; + } + if (( i & 1) != 0) { + Byte= (Byte | z<<4); + if (temp) + *(temp++) = Byte; + if (count++ == length) + temp = NULL; + /* Does the error line indicate end of data? */ + if ((parport_r_status(port) & LP_PERRORP) == LP_PERRORP) + break; + } else Byte=z; + } + read_terminate(port); + return count; +} + +static struct wait_queue *wait_q = NULL; + +static int wakeup(void *ref) +{ + struct ppd **dev = (struct ppd **)ref; + + if (!wait_q || parport_claim(*dev)) + return 1; + + wake_up(&wait_q); + return 0; +} + +int parport_probe(struct parport *port, char *buffer, int len) +{ + struct ppd *dev = parport_register_device(port, "IEEE 1284 probe", + NULL, wakeup, NULL, + PARPORT_DEV_TRAN, &dev); + + int result = 0; + + if (!dev) { + printk("%s: unable to register for probe.\n", port->name); + return -EINVAL; + } + + if (parport_claim(dev)) { + sleep_on(&wait_q); + wait_q = NULL; + } + + switch (parport_ieee1284_nibble_mode_ok(port, 4)) { + case 1: + current->state=TASK_INTERRUPTIBLE; + current->timeout=jiffies+1; + schedule(); /* HACK: wait 10ms because printer seems to + * ack wrong */ + result = read_polled(port, buffer, len); + break; + case 0: + result = -EIO; + break; + } + + parport_release(dev); + parport_unregister_device(dev); + + return result; +} + +static struct { + char *token; + char *descr; +} classes[] = { + { "", "Legacy device" }, + { "PRINTER", "Printer" }, + { "MODEM", "Modem" }, + { "NET", "Network device" }, + { "HDC", "Hard disk" }, + { "PCMCIA", "PCMCIA" }, + { "MEDIA", "Multimedia device" }, + { "FDC", "Floppy disk" }, + { "PORTS", "Ports" }, + { "SCANNER", "Scanner" }, + { "DIGICAM", "Digital camera" }, + { "", "Unknown device" }, + { "", "Unspecified" }, + { NULL, NULL } +}; + +static char *strdup(char *str) +{ + int n = strlen(str)+1; + char *s = kmalloc(n, GFP_KERNEL); + if (!s) return NULL; + return strcpy(s, str); +} + +static void parse_data(struct parport *port, char *str) +{ + char *txt = kmalloc(strlen(str)+1, GFP_KERNEL); + char *p = txt, *q; + int guessed_class = PARPORT_CLASS_UNSPEC; + + if (!txt) { + printk("%s probe: memory squeeze\n", port->name); + return; + } + strcpy(txt, str); + while (p) { + char *sep; + q = strchr(p, ';'); + if (q) *q = 0; + sep = strchr(p, ':'); + if (sep) { + char *u = p; + *(sep++) = 0; + while (*u) { + *u = toupper(*u); + u++; + } + if (!strcmp(p, "MFG") || !strcmp(p, "MANUFACTURER")) { + port->probe_info.mfr = strdup(sep); + } else if (!strcmp(p, "MDL") || !strcmp(p, "MODEL")) { + port->probe_info.model = strdup(sep); + } else if (!strcmp(p, "CLS") || !strcmp(p, "CLASS")) { + int i; + for (u = sep; *u; u++) + *u = toupper(*u); + for (i = 0; classes[i].token; i++) { + if (!strcmp(classes[i].token, sep)) { + port->probe_info.class = i; + goto rock_on; + } + } + printk(KERN_WARNING "%s probe: warning, class '%s' not understood.\n", port->name, sep); + port->probe_info.class = PARPORT_CLASS_OTHER; + } else if (!strcmp(p, "CMD") || !strcmp(p, "COMMAND SET")) { + /* if it speaks printer language, it's + probably a printer */ + if (strstr(sep, "PJL") || strstr(sep, "PCL")) + guessed_class = PARPORT_CLASS_PRINTER; + } else if (!strcmp(p, "DES") || !strcmp(p, "DESCRIPTION")) { + port->probe_info.description = strdup(sep); + } + } + rock_on: + if (q) p = q+1; else p=NULL; + } + + /* If the device didn't tell us its class, maybe we have managed to + guess one from the things it did say. */ + if (port->probe_info.class == PARPORT_CLASS_UNSPEC) + port->probe_info.class = guessed_class; + + kfree(txt); +} + +static void pretty_print(struct parport *port) +{ + printk(KERN_INFO "%s: %s", port->name, classes[port->probe_info.class].descr); + if (port->probe_info.class) { + printk(", %s (%s)", port->probe_info.model, port->probe_info.mfr); + } + printk("\n"); +} + +void parport_probe_one(struct parport *port) +{ + char *buffer = kmalloc(2048, GFP_KERNEL); + int r; + + port->probe_info.model = "Unknown device"; + port->probe_info.mfr = "Unknown vendor"; + port->probe_info.description = NULL; + port->probe_info.class = PARPORT_CLASS_UNSPEC; + + if (!buffer) { + printk(KERN_ERR "%s probe: Memory squeeze.\n", port->name); + return; + } + + r = parport_probe(port, buffer, 2047); + + if (r < 0) { + printk(KERN_INFO "%s: no IEEE-1284 device present.\n", + port->name); + port->probe_info.class = PARPORT_CLASS_LEGACY; + } else if (r == 0) { + printk(KERN_INFO "%s: no ID data returned by device.\n", + port->name); + } else { + buffer[r] = 0; +#ifdef DEBUG_PROBE + printk("%s id: %s\n", port->name, buffer+2); +#endif + parse_data(port, buffer+2); + pretty_print(port); + } + kfree(buffer); +} diff --git a/drivers/pnp/parport_procfs.c b/drivers/pnp/parport_procfs.c new file mode 100644 index 000000000..896abbdd4 --- /dev/null +++ b/drivers/pnp/parport_procfs.c @@ -0,0 +1,340 @@ +/* $Id: parport_procfs.c,v 1.3.2.6 1997/04/16 21:30:38 phil Exp $ + * Parallel port /proc interface code. + * + * Authors: David Campbell <campbell@tirian.che.curtin.edu.au> + * Tim Waugh <tmw20@cam.ac.uk> + * + * based on work by Grant Guenther <grant@torque.net> + * and Philip Blundell <Philip.Blundell@pobox.com> + */ + +#include <linux/tasks.h> +#include <asm/ptrace.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/malloc.h> + +#include <linux/proc_fs.h> + +#include <linux/parport.h> +#include "parport_ll_io.h" + +#undef PARPORT_INCLUDE_BENCH + +struct proc_dir_entry *base=NULL; + +void parport_null_intr_func(int irq, void *dev_id, struct pt_regs *regs); + +static int irq_write_proc(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + int newirq; + struct parport *pp = (struct parport *)data; + + if (count > 4 ) /* more than 4 digits for a irq 0x?? 0?? ?? */ + return(-EOVERFLOW); + + if (buffer[0] < 32 || !strncmp(buffer, "none", 4)) { + newirq = PARPORT_IRQ_NONE; + } else { + if (buffer[0] == '0') { + if( buffer[1] == 'x' ) + newirq = simple_strtoul(&buffer[2],0,16); + else + newirq = simple_strtoul(&buffer[1],0,8); + } else { + newirq = simple_strtoul(buffer,0,10); + } + } + + if (pp->irq != PARPORT_IRQ_NONE && !(pp->flags & PARPORT_FLAG_COMA)) + free_irq(pp->irq, pp); + + pp->irq = newirq; + + if (pp->irq != PARPORT_IRQ_NONE && !(pp->flags & PARPORT_FLAG_COMA)) { + struct ppd *pd = pp->cad; + + if (pd == NULL) { + pd = pp->devices; + if (pd != NULL) + request_irq(pp->irq, pd->irq_func ? + pd->irq_func : + parport_null_intr_func, + SA_INTERRUPT, pd->name, pd->port); + } else { + request_irq(pp->irq, pd->irq_func ? pd->irq_func : + parport_null_intr_func, + SA_INTERRUPT, pp->name, pd->port); + } + } + + return count; +} + +static int irq_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct parport *pp = (struct parport *)data; + int len; + + if (pp->irq == PARPORT_IRQ_NONE) + len = sprintf(page, "none\n"); + else + len = sprintf(page, "%d\n", pp->irq); + + *start = 0; + *eof = 1; + return len; +} + +static int devices_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct parport *pp = (struct parport *)data; + struct ppd *pd1; + int len=0; + + for (pd1 = pp->devices; pd1 ; pd1 = pd1->next) { + if (pd1 == pp->cad) + len += sprintf(page+len, "+"); + else + len += sprintf(page+len, " "); + + len += sprintf(page+len, "%s",pd1->name); + + if (pd1 == pp->lurker) + len += sprintf(page+len, " LURK"); + + len += sprintf(page+len,"\n"); + } + + *start = 0; + *eof = 1; + return len; +} + +static int hardware_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct parport *pp = (struct parport *)data; + int len=0; + + len += sprintf(page+len, "base:\t0x%x\n",pp->base); + if (pp->irq == PARPORT_IRQ_NONE) + len += sprintf(page+len, "irq:\tnone\n"); + else + len += sprintf(page+len, "irq:\t%d\n",pp->irq); + len += sprintf(page+len, "dma:\t%d\n",pp->dma); + + len += sprintf(page+len, "modes:\t"); + { +#define printmode(x) {if(pp->modes&PARPORT_MODE_##x){len+=sprintf(page+len,"%s%s",f?",":"",#x);f++;}} + int f = 0; + printmode(SPP); + printmode(PS2); + printmode(EPP); + printmode(ECP); + printmode(ECPEPP); + printmode(ECPPS2); +#undef printmode + } + len += sprintf(page+len, "\n"); + + len += sprintf(page+len, "mode:\t"); + if (pp->modes & PARPORT_MODE_ECR) { + switch (r_ecr(pp) >> 5) { + case 0: + len += sprintf(page+len, "SPP"); + if( pp->modes & PARPORT_MODE_PS2 ) + len += sprintf(page+len, ",PS2"); + if( pp->modes & PARPORT_MODE_EPP ) + len += sprintf(page+len, ",EPP"); + break; + case 1: + len += sprintf(page+len, "ECPPS2"); + break; + case 2: + len += sprintf(page+len, "DATAFIFO"); + break; + case 3: + len += sprintf(page+len, "ECP"); + break; + case 4: + len += sprintf(page+len, "ECPEPP"); + break; + case 5: + len += sprintf(page+len, "Reserved?"); + break; + case 6: + len += sprintf(page+len, "TEST"); + break; + case 7: + len += sprintf(page+len, "Configuration"); + break; + } + } else { + len += sprintf(page+len, "SPP"); + if (pp->modes & PARPORT_MODE_PS2) + len += sprintf(page+len, ",PS2"); + if (pp->modes & PARPORT_MODE_EPP) + len += sprintf(page+len, ",EPP"); + } + len += sprintf(page+len, "\n"); + +#if 0 + /* Now no detection, please fix with an external function */ + len += sprintf(page+len, "chipset:\tunknown\n"); +#endif +#ifdef PARPORT_INCLUDE_BENCHMARK + if (pp->speed) + len += sprintf(page+len, "bench:\t%d Bytes/s\n",pp->speed); + else + len += sprintf(page+len, "bench:\tunknown\n"); +#endif + + *start = 0; + *eof = 1; + return len; +} + +static struct proc_dir_entry *new_proc_entry(const char *name, mode_t mode, + struct proc_dir_entry *parent, + unsigned short ino) +{ + struct proc_dir_entry *ent; + + ent = kmalloc(sizeof(struct proc_dir_entry), GFP_KERNEL); + if (!ent) + return NULL; + memset(ent, 0, sizeof(struct proc_dir_entry)); + + if (mode == S_IFDIR) + mode |= S_IRUGO | S_IXUGO; + else if (mode == 0) + mode = S_IFREG | S_IRUGO; + + + ent->low_ino = ino; + ent->name = name; + ent->namelen = strlen(name); + ent->mode = mode; + if (S_ISDIR(mode)) + ent->nlink = 2; + else + ent->nlink = 1; + + proc_register(parent, ent); + + return ent; +} + + +int parport_proc_init() +{ + base = new_proc_entry("parport", S_IFDIR, &proc_root,PROC_PARPORT); + + if (base) + return 1; + else { + printk(KERN_ERR "parport: Error creating proc entry /proc/parport\n"); + return 0; + } +} + +int parport_proc_cleanup() +{ + if (base) + proc_unregister(&proc_root,base->low_ino); + + base = NULL; + + return 0; +} + +int parport_proc_register(struct parport *pp) +{ + struct proc_dir_entry *ent; + static int conta=0; + char *name; + + memset(&pp->pdir,0,sizeof(struct parport_dir)); + + if (!base) { + printk(KERN_ERR "parport: Error entry /proc/parport, not generated?\n"); + return 1; + } + + name = pp->pdir.name; + sprintf(name,"%d",conta++); + + ent = new_proc_entry(name, S_IFDIR, base,0); + if (!ent) { + printk(KERN_ERR "parport: Error registering proc_entry /proc/%s\n",name); + return 1; + } + pp->pdir.entry = ent; + + ent = new_proc_entry("irq", S_IFREG | S_IRUGO | S_IWUSR, pp->pdir.entry,0); + if (!ent) { + printk(KERN_ERR "parport: Error registering proc_entry /proc/%s/irq\n",name); + return 1; + } + ent->read_proc = irq_read_proc; + ent->write_proc= irq_write_proc; + ent->data = pp; + pp->pdir.irq = ent; + + ent = new_proc_entry("devices", 0, pp->pdir.entry,0); + if (!ent) { + printk(KERN_ERR "parport: Error registering proc_entry /proc/%s/devices\n",name); + return 1; + } + ent->read_proc = devices_read_proc; + ent->data = pp; + pp->pdir.devices = ent; + + ent = new_proc_entry("hardware", 0, pp->pdir.entry,0); + if (!ent) { + printk(KERN_ERR "parport: Error registering proc_entry /proc/%s/hardware\n",name); + return 1; + } + ent->read_proc = hardware_read_proc; + ent->data = pp; + pp->pdir.hardware = ent; + return 0; +} + +int parport_proc_unregister(struct parport *pp) +{ + if (pp->pdir.entry) { + if (pp->pdir.irq) { + proc_unregister(pp->pdir.entry, pp->pdir.irq->low_ino); + kfree(pp->pdir.irq); + } + + if (pp->pdir.devices) { + proc_unregister(pp->pdir.entry, + pp->pdir.devices->low_ino); + kfree(pp->pdir.devices); + } + + if (pp->pdir.hardware) { + proc_unregister(pp->pdir.entry, + pp->pdir.hardware->low_ino); + kfree(pp->pdir.hardware); + } + + proc_unregister(base, pp->pdir.entry->low_ino); + kfree(pp->pdir.entry); + } + + return 0; +} diff --git a/drivers/pnp/parport_share.c b/drivers/pnp/parport_share.c new file mode 100644 index 000000000..fc19fffd4 --- /dev/null +++ b/drivers/pnp/parport_share.c @@ -0,0 +1,469 @@ +/* $Id: parport_share.c,v 1.3.2.5 1997/04/16 21:20:44 phil Exp $ + * Parallel-port resource manager code. + * + * Authors: David Campbell <campbell@tirian.che.curtin.edu.au> + * Tim Waugh <tmw20@cam.ac.uk> + * Jose Renau <renau@acm.org> + * + * based on work by Grant Guenther <grant@torque.net> + * and Philip Blundell <Philip.Blundell@pobox.com> + */ + +#include <linux/tasks.h> +#include <asm/io.h> +#include <asm/dma.h> + +#include <linux/parport.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/malloc.h> + +#undef PARPORT_PARANOID + +#include "parport_ll_io.h" + +static struct parport *portlist = NULL, *portlist_tail = NULL; +static int portcount = 0; + +/* from parport_init.c */ +extern int initialize_parport(struct parport *, unsigned long base, + int irq, int dma, int count); + +/* Return a list of all the ports we know about. */ +struct parport *parport_enumerate(void) +{ + return portlist; +} + +void parport_null_intr_func(int irq, void *dev_id, struct pt_regs *regs) +{ + /* NULL function - Does nothing */ + return; +} + +struct parport *parport_register_port(unsigned long base, int irq, int dma) +{ + struct parport new, *tmp; + + /* Check for a previously registered port. + * NOTE: we will ignore irq and dma if we find a previously + * registered device. + */ + for (tmp = portlist; tmp; tmp = tmp->next) { + if (tmp->base == base) + return tmp; + } + + /* Has someone grabbed the address yet? */ + if (check_region(base, 3)) + return NULL; + + if (!initialize_parport(&new,base,irq,dma,portcount)) + return NULL; + + if (new.dma >= 0) { + if (request_dma(new.dma, new.name)) { + printk(KERN_INFO "%s: unable to claim DMA %d\n", + new.name, new.dma); + release_region(new.base, new.size); + if( new.modes & PARPORT_MODE_ECR ) + release_region(new.base+0x400, 3); + kfree(new.name); + return NULL; + } + } + + tmp = kmalloc(sizeof(struct parport), GFP_KERNEL); + if (!tmp) { + printk(KERN_WARNING "parport: memory squeeze\n"); + release_region(new.base, new.size); + if( new.modes & PARPORT_MODE_ECR ) + release_region(new.base+0x400, 3); + kfree(new.name); + return NULL; + } + memcpy(tmp, &new, sizeof(struct parport)); + + if (new.irq != PARPORT_IRQ_NONE) { + if (request_irq(new.irq, parport_null_intr_func, + SA_INTERRUPT, new.name, tmp) != 0) { + printk(KERN_INFO "%s: unable to claim IRQ %d\n", + new.name, new.irq); + kfree(tmp); + release_region(new.base, new.size); + if( new.modes & PARPORT_MODE_ECR ) + release_region(new.base+0x400, 3); + kfree(new.name); + return NULL; + } + } + + /* Here we chain the entry to our list. */ + if (portlist_tail) + portlist_tail->next = tmp; + portlist_tail = tmp; + if (!portlist) + portlist = tmp; + + printk(KERN_INFO "%s at 0x%x", tmp->name, tmp->base); + if (tmp->irq >= 0) + printk(", irq %d", tmp->irq); + if (tmp->dma >= 0) + printk(", dma %d", tmp->dma); + printk(" ["); + { + /* Ugh! */ +#define printmode(x) {if(tmp->modes&PARPORT_MODE_##x){printk("%s%s",f?",":"",#x);f++;}} + int f = 0; + printmode(SPP); + printmode(PS2); + printmode(EPP); + printmode(ECP); + printmode(ECPEPP); + printmode(ECPPS2); +#undef printmode + } + printk("]\n"); + portcount++; + + /* Restore device back to default conditions */ + if (tmp->modes & PARPORT_MODE_ECR) + w_ecr(tmp, tmp->ecr); + w_ctr(tmp, tmp->ctr); + + tmp->probe_info.class = PARPORT_CLASS_LEGACY; /* assume the worst */ + return tmp; +} + +void parport_destroy(struct parport *port) +{ + /* Dangerous to try destroying a port if its friends are nearby. */ + if (port->devices) { + printk("%s: attempt to release active port\n", port->name); + return; /* Devices still present */ + } + + /* No point in further destroying a port that already lies in ruins. */ + if (port->flags & PARPORT_FLAG_COMA) + return; + + /* Now clean out the port entry */ + if (port->irq >= 0) + free_irq(port->irq, port); + if (port->dma >= 0) + free_dma(port->dma); + release_region(port->base, port->size); + if( port->modes & PARPORT_MODE_ECR ) + release_region(port->base+0x400, 3); + port->flags |= PARPORT_FLAG_COMA; +} + +struct ppd *parport_register_device(struct parport *port, const char *name, + callback_func pf, callback_func kf, + irq_handler_func irq_func, int flags, + void *handle) +{ + struct ppd *tmp; + + /* We only allow one lurker device (eg PLIP) */ + if (flags & PARPORT_DEV_LURK) { + if (port->lurker) { + printk(KERN_INFO "%s: refused to register second lurker (%s)\n", + port->name, name); + return NULL; + } + if (!pf || !kf) { + printk(KERN_INFO "%s: refused to register lurking device (%s) without callbacks\n" + ,port->name, name); + return NULL; + } + } + + /* We may need to claw back the port hardware. */ + if (port->flags & PARPORT_FLAG_COMA) { + if (check_region(port->base, 3)) { + return NULL; + } + request_region(port->base, port->size, port->name); + if( port->modes & PARPORT_MODE_ECR ) + request_region(port->base+0x400, 3,port->name); + + if (port->dma >= 0) { + if (request_dma(port->dma, port->name)) { + release_region(port->base, port->size); + if( port->modes & PARPORT_MODE_ECR ) + release_region(port->base+0x400, 3); + return NULL; + } + } + if (port->irq != PARPORT_IRQ_NONE) { + if (request_irq(port->irq, parport_null_intr_func, + SA_INTERRUPT, port->name, + port) != 0) { + release_region(port->base, port->size); + if( port->modes & PARPORT_MODE_ECR ) + release_region(port->base+0x400, 3); + if (port->dma >= 0) + free_dma(port->dma); + return NULL; + } + } + port->flags &= ~PARPORT_FLAG_COMA; + } + + tmp = kmalloc(sizeof(struct ppd), GFP_KERNEL); + tmp->name = (char *) name; + tmp->port = port; + tmp->preempt = pf; + tmp->wakeup = kf; + tmp->private = handle; + tmp->irq_func = irq_func; + tmp->ctr = port->ctr; + tmp->ecr = port->ecr; + + /* Chain this onto the list */ + tmp->prev = NULL; + tmp->next = port->devices; + if (port->devices) + port->devices->prev = tmp; + port->devices = tmp; + + if (flags & PARPORT_DEV_LURK) + port->lurker = tmp; + + inc_parport_count(); + + return tmp; +} + +void parport_unregister_device(struct ppd *dev) +{ + struct parport *port; + + if (!dev) { + printk(KERN_ERR "parport_unregister_device: passed NULL\n"); + return; + } + + port = dev->port; + + if (port->cad == dev) { + printk(KERN_INFO "%s: refused to unregister currently active device %s\n", port->name, dev->name); + return; + } + + if (port->lurker == dev) + port->lurker = NULL; + + if (dev->next) + dev->next->prev = dev->prev; + if (dev->prev) + dev->prev->next = dev->next; + else + port->devices = dev->next; + + kfree(dev); + + dec_parport_count(); + + /* If there are no more devices, put the port to sleep. */ + if (!port->devices) + parport_destroy(port); + + return; +} + +int parport_claim(struct ppd *dev) +{ + struct ppd *pd1; + + if (dev->port->cad == dev) { + printk(KERN_INFO "%s: %s already owner\n", + dev->port->name,dev->name); + return 0; + } + + /* Preempt any current device */ + pd1 = dev->port->cad; + if (dev->port->cad) { + if (dev->port->cad->preempt) { + /* Now try to preempt */ + if (dev->port->cad->preempt(dev->port->cad->private)) + return -EAGAIN; + + /* Save control registers */ + if (dev->port->modes & PARPORT_MODE_ECR) + dev->port->cad->ecr = dev->port->ecr = + r_ecr(dev->port); + if (dev->port->modes & PARPORT_MODE_SPP) + dev->port->cad->ctr = dev->port->ctr = + r_ctr(dev->port); + } else + return -EAGAIN; + } + + /* Watch out for bad things */ + if (dev->port->cad != pd1) { + printk(KERN_WARNING "%s: death while preempting %s\n", + dev->port->name, dev->name); + if (dev->port->cad) + return -EAGAIN; + } + + /* Now we do the change of devices */ + dev->port->cad = dev; + + if (dev->port->irq >= 0) { + free_irq(dev->port->irq, dev->port); + request_irq(dev->port->irq, dev->irq_func ? dev->irq_func : + parport_null_intr_func, SA_INTERRUPT, dev->name, + dev->port); + } + + /* Restore control registers */ + if (dev->port->modes & PARPORT_MODE_ECR) + if (dev->ecr != dev->port->ecr) w_ecr(dev->port, dev->ecr); + if (dev->port->modes & PARPORT_MODE_SPP) + if (dev->ctr != dev->port->ctr) w_ctr(dev->port, dev->ctr); + + return 0; +} + +void parport_release(struct ppd *dev) +{ + struct ppd *pd1; + + /* Make sure that dev is the current device */ + if (dev->port->cad != dev) { + printk(KERN_WARNING "%s: %s tried to release parport when not owner\n", dev->port->name, dev->name); + return; + } + dev->port->cad = NULL; + + /* Save control registers */ + if (dev->port->modes & PARPORT_MODE_ECR) + dev->ecr = dev->port->ecr = r_ecr(dev->port); + if (dev->port->modes & PARPORT_MODE_SPP) + dev->ctr = dev->port->ctr = r_ctr(dev->port); + + if (dev->port->irq >= 0) { + free_irq(dev->port->irq, dev->port); + request_irq(dev->port->irq, parport_null_intr_func, + SA_INTERRUPT, dev->port->name, dev->port); + } + + /* Walk the list, offering a wakeup callback to everybody other + * than the lurker and the device that called us. + */ + for (pd1 = dev->next; pd1; pd1 = pd1->next) { + if (!(pd1->flags & PARPORT_DEV_LURK)) { + if (pd1->wakeup) { + pd1->wakeup(pd1->private); + if (dev->port->cad) + return; + } + } + } + + for (pd1 = dev->port->devices; pd1 && pd1 != dev; pd1 = pd1->next) { + if (!(pd1->flags & PARPORT_DEV_LURK)) { + if (pd1->wakeup) { + pd1->wakeup(pd1->private); + if (dev->port->cad) + return; + } + } + } + + /* Now give the lurker a chance. + * There should be a wakeup callback because we checked for it + * at registration. + */ + if (dev->port->lurker && (dev->port->lurker != dev)) { + if (dev->port->lurker->wakeup) { + dev->port->lurker->wakeup(dev->port->lurker->private); + } +#ifdef PARPORT_PARANOID + else { /* can't happen */ + printk(KERN_DEBUG + "%s (%s): lurker's wakeup callback went away!\n", + dev->port->name, dev->name); + } +#endif + } +} + +/* The following read funktions are an implementation of a status readback + * and device id request confirming to IEEE1284-1994. + * + * These probably ought to go in some seperate file, so people like the SPARC + * don't have to pull them in. + */ + +/* Wait for Status line(s) to change in 35 ms - see IEEE1284-1994 page 24 to + * 25 for this. After this time we can create a timeout because the + * peripheral doesn't conform to IEEE1284. We want to save CPU time: we are + * waiting a maximum time of 500 us busy (this is for speed). If there is + * not the right answer in this time, we call schedule and other processes + * are able "to eat" the time up to 30ms. So the maximum load avarage can't + * get above 5% for a read even if the peripheral is really slow. (but your + * read gets very slow then - only about 10 characters per second. This + * should be tuneable). Thanks to Andreas who pointed me to this and ordered + * the documentation. + */ + +int parport_wait_peripheral(struct parport *port, unsigned char mask, + unsigned char result) +{ + int counter=0; + unsigned char status; + + do { + status = parport_r_status(port); + udelay(25); + counter++; + if (need_resched) + schedule(); + } while ( ((status & mask) != result) && (counter < 20) ); + if ( (counter == 20) && ((status & mask) != result) ) { + current->state=TASK_INTERRUPTIBLE; + current->timeout=jiffies+4; + schedule(); /* wait for 4 scheduler runs (40ms) */ + status = parport_r_status(port); + if ((status & mask) != result) return 1; /* timeout */ + } + return 0; /* okay right response from device */ +} + +/* Test if nibble mode for status readback is okay. Returns the value false + * if the printer doesn't support readback at all. If it supports readbacks + * and printer data is available the function returns 1, otherwise 2. The + * only valid values for "mode" are 0 and 4. 0 requests normal nibble mode, + * 4 is for "request device id using nibble mode". The request for the + * device id is best done in an ioctl (or at bootup time). There is no + * check for an invalid value, the only function using this call at the + * moment is lp_read and the ioctl LPGETDEVICEID both fixed calls from + * trusted kernel. + */ +int parport_ieee1284_nibble_mode_ok(struct parport *port, unsigned char mode) +{ + parport_w_data(port, mode); + udelay(5); + parport_w_ctrl(port, parport_r_ctrl(port) & ~8); /* SelectIN low */ + parport_w_ctrl(port, parport_r_ctrl(port) | 2); /* AutoFeed high */ + if (parport_wait_peripheral(port, 0x78, 0x38)) { /* timeout? */ + parport_w_ctrl(port, (parport_r_ctrl(port) & ~2) | 8); + return 0; /* first stage of negotiation failed, + * no IEEE1284 compliant device on this port + */ + } + parport_w_ctrl(port, parport_r_ctrl(port) | 1); /* Strobe high */ + udelay(5); /* Strobe wait */ + parport_w_ctrl(port, parport_r_ctrl(port) & ~1); /* Strobe low */ + udelay(5); + parport_w_ctrl(port, parport_r_ctrl(port) & ~2); /* AutoFeed low */ + return (parport_wait_peripheral(port, 0x20, 0))?2:1; +} |