487 lines
13 KiB
C
487 lines
13 KiB
C
|
|
/*
|
|||
|
|
* par.c V0.32
|
|||
|
|
*
|
|||
|
|
* Copyright (C) by R.E.Wolff -- R.E.Wolff@BitWizard.nl
|
|||
|
|
*
|
|||
|
|
* This software may be used and distributed according to the terms
|
|||
|
|
* of the GNU Public License, incorporated herein by reference.
|
|||
|
|
*
|
|||
|
|
* I prefer if you try to contact me if you have enhancements,
|
|||
|
|
* instead of forking off a different branch.....
|
|||
|
|
*
|
|||
|
|
* date by what
|
|||
|
|
* Written: Apr 8 1996 REW initial revision
|
|||
|
|
* changed: Apr 11 1996 REW Added some more comments.
|
|||
|
|
* changed: May 12 1996 REW Finished the "read" code.
|
|||
|
|
* changed: May 15 1996 REW Fixed detection. Code cleanup.
|
|||
|
|
* changed: May 16 1996 PK Removed depreciated put_fs_byte call
|
|||
|
|
* Fixed the BIT(x) macro
|
|||
|
|
* The wake-up woke up the wrong process
|
|||
|
|
* Added and removed some debugging code
|
|||
|
|
* Init_module now initializes rstate
|
|||
|
|
* Locked out forbidden bits
|
|||
|
|
* Inverted inverting inputs and outputs
|
|||
|
|
* Made D0..D7 readable (renumbered others)
|
|||
|
|
* changed: May 18 1996 REW Incorporated above changes with cleanup.
|
|||
|
|
* fixed crash at rmmod time.
|
|||
|
|
* changed: May 23 1996 PK Added locking for outputs
|
|||
|
|
*
|
|||
|
|
* who-is-who:
|
|||
|
|
* initials full name Email address
|
|||
|
|
* REW Roger E. Wolff R.E.Wolff@BitWizard.nl
|
|||
|
|
* PK Peter Knoppers knop@duteca.et.tudelft.nl
|
|||
|
|
*
|
|||
|
|
* This kernel module can be used instead of lp or plip.
|
|||
|
|
* It will give you access to the bits on the parallel port.
|
|||
|
|
*
|
|||
|
|
* You can control external devices this way. As an example, here
|
|||
|
|
* is the schematic for a set of 8 leds:
|
|||
|
|
*
|
|||
|
|
* name pinno
|
|||
|
|
* / _____
|
|||
|
|
* D0 2 -----|>|---|_____|-------+
|
|||
|
|
* / _____ |
|
|||
|
|
* D1 3 -----|>|---|_____|-------+
|
|||
|
|
* / _____ |
|
|||
|
|
* D2 4 -----|>|---|_____|-------+
|
|||
|
|
* / _____ |
|
|||
|
|
* D3 5 -----|>|---|_____|-------+
|
|||
|
|
* / _____ |
|
|||
|
|
* D4 6 -----|>|---|_____|-------+
|
|||
|
|
* / _____ |
|
|||
|
|
* D5 7 -----|>|---|_____|-------+
|
|||
|
|
* / _____ |
|
|||
|
|
* D6 8 -----|>|---|_____|-------+
|
|||
|
|
* / _____ |
|
|||
|
|
* D7 9 -----|>|---|_____|-------+
|
|||
|
|
* |
|
|||
|
|
* GND 25 -------------------------+
|
|||
|
|
*
|
|||
|
|
* /
|
|||
|
|
* You can use any led ( -|>|- ) you can find. The resistors need
|
|||
|
|
* to be in the 200 to 1k region. 200 ohms will make them quite bright.
|
|||
|
|
* (14 ma current), I have them much weaker (3.5 ma current).
|
|||
|
|
* For testing on my laptop, I currently use 810 ohms..... A modern
|
|||
|
|
* datasheet mentions 14 ma as the source drive capability.
|
|||
|
|
*
|
|||
|
|
* Original Paralel ports are spec-ed to 2 ma at 2.4 volts. The IEEE1284
|
|||
|
|
* spec states 14ma. This is good enough for a led :-)
|
|||
|
|
*
|
|||
|
|
**************************************************************************
|
|||
|
|
*
|
|||
|
|
* For reference: the pinout of the par port:
|
|||
|
|
* data relative
|
|||
|
|
* pin pin direction minor
|
|||
|
|
* number name in/out number
|
|||
|
|
* 1 STROBE out ?
|
|||
|
|
* 2 D0 out 0
|
|||
|
|
* 3 D1 out 1
|
|||
|
|
* 4 D2 out 2
|
|||
|
|
* 5 D3 out 3
|
|||
|
|
* 6 D4 out 4
|
|||
|
|
* 7 D5 out 5
|
|||
|
|
* 8 D6 out 6
|
|||
|
|
* 9 D7 out 7
|
|||
|
|
* 10 ACK in
|
|||
|
|
* 11 BUSY in
|
|||
|
|
* 12 PAPOUT in
|
|||
|
|
* 13 SLCT in
|
|||
|
|
* 14 AUTOFD out ?
|
|||
|
|
* 15 ERROR in
|
|||
|
|
* 16 INIT out ?
|
|||
|
|
* 17 SLCTIN out ?
|
|||
|
|
* 18-25 GND
|
|||
|
|
*
|
|||
|
|
**************************************************************************
|
|||
|
|
*
|
|||
|
|
* The devices in /dev are:
|
|||
|
|
*
|
|||
|
|
* par0a - par0h (minors 0-7 ) -> D0-D7 on lp0,
|
|||
|
|
* par0i - par0l (minors 8-11) -> control outputs on lp0
|
|||
|
|
* par1a - par1h (minors 16-23) -> D0-D7 on lp1,
|
|||
|
|
* par1i - par1l (minors 24-27) -> control outputs on lp1
|
|||
|
|
* par2a - par2h (minors 32-39) -> D0-D7 on lp2,
|
|||
|
|
* par2i - par2l (minors 40-43) -> control outputs on lp2
|
|||
|
|
*
|
|||
|
|
* Most modern computers only have lp1.
|
|||
|
|
*
|
|||
|
|
* use
|
|||
|
|
* mknod parXY c 60 <minor>
|
|||
|
|
* where 60 is the (current) major number....
|
|||
|
|
*
|
|||
|
|
* insmod with "base=0xYYY" if you have a port at a weird address.
|
|||
|
|
* (-- untested: tell me if it works... :-)
|
|||
|
|
*
|
|||
|
|
* - Inputs.
|
|||
|
|
* A par port has about 5 inputs. They can be polled at say
|
|||
|
|
* 50 ms intervals without too much overhead. (My 386sx25 has
|
|||
|
|
* been doing that about 200 million times in 4 months)
|
|||
|
|
* the driver will implement blocking-read (no polling in userspace!).
|
|||
|
|
* DONE.
|
|||
|
|
**************************************************************************
|
|||
|
|
*
|
|||
|
|
* BUGS and restrictions:
|
|||
|
|
*
|
|||
|
|
* (bug me about it if you want it fixed/enhanced -- that's why
|
|||
|
|
* they are called bugs..... :-)
|
|||
|
|
*
|
|||
|
|
* -More than 5 inputs.
|
|||
|
|
* IF (notice the big if....) your port is 8255 compatible
|
|||
|
|
* without any buffering between the port and the chip, it
|
|||
|
|
* should be possible to reconfigure the chip to give about 13
|
|||
|
|
* inputs available on the connector.
|
|||
|
|
*
|
|||
|
|
*
|
|||
|
|
* -A major number should be allocated with the devices coordinator.
|
|||
|
|
* -- Under way....
|
|||
|
|
*
|
|||
|
|
* -Like lp.c and plip.c this doesn't allocate the region at open-time
|
|||
|
|
* but when the device is initialized. This means that you can't compile
|
|||
|
|
* two of these into the kernel. If I'm going to reconfigure the 8255,
|
|||
|
|
* it might not even be a bad idea to keep it this way.....
|
|||
|
|
*
|
|||
|
|
* -Cannot be compiled into the kernel. Only module.
|
|||
|
|
*
|
|||
|
|
* -driver interpreted scripts. Allows low-overhead and accurate
|
|||
|
|
* timing to be done....
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
#include <linux/module.h>
|
|||
|
|
#include <linux/module.h>
|
|||
|
|
#include <linux/errno.h>
|
|||
|
|
#include <linux/delay.h>
|
|||
|
|
#include <linux/sched.h>
|
|||
|
|
#include <linux/fs.h>
|
|||
|
|
#include <linux/kernel.h>
|
|||
|
|
#include <linux/ioport.h>
|
|||
|
|
|
|||
|
|
#include <asm/bitops.h>
|
|||
|
|
#include <asm/io.h>
|
|||
|
|
#include <asm/segment.h>
|
|||
|
|
|
|||
|
|
|
|||
|
|
/* Debugging stuff. */
|
|||
|
|
|
|||
|
|
#ifdef EBUG
|
|||
|
|
#define DEBUG
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
#ifdef DEBUG
|
|||
|
|
#define dprintk(a) printk a
|
|||
|
|
#else
|
|||
|
|
#define dprintk(f)
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
|
|||
|
|
/* Administratrivia */
|
|||
|
|
#ifndef PAR_MAJOR
|
|||
|
|
#define PAR_MAJOR 60
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
|
|||
|
|
/* Hardware related stuff */
|
|||
|
|
#define NR_BITS_PER_PORT 16 /* best if this is a power of two */
|
|||
|
|
#define ALLOWEDWRITEBITS 0x0fff /* three bits reserved, one bit enables IRQ */
|
|||
|
|
#define INVERTEDOUTPUTS 0x0b00 /* three bits are inverted in the hardware */
|
|||
|
|
#define ALLOWEDREADBITS 0xf8ff /* two bits reserved, one bit is IRQ status */
|
|||
|
|
#define INVERTEDINPUTBITS 0x8000 /* one bit is inverted in the hardware */
|
|||
|
|
|
|||
|
|
#define POLL_INTERVAL 5
|
|||
|
|
|
|||
|
|
#define NR_PAR 3
|
|||
|
|
|
|||
|
|
int base[NR_PAR]={0x3bc,0x378,0x278};
|
|||
|
|
int exists[NR_PAR]={0,};
|
|||
|
|
|
|||
|
|
|
|||
|
|
#if NR_BITS_PER_PORT != 16
|
|||
|
|
#warning "There are several places where NR_BITS_PER_PORT is fixed at 16"
|
|||
|
|
#warning "Just changing the define doesn't work."
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
/* Local variables. */
|
|||
|
|
static unsigned short state[NR_PAR]={0,};
|
|||
|
|
static int nreaders = 0;
|
|||
|
|
|
|||
|
|
static struct timer_list par_timer;
|
|||
|
|
|
|||
|
|
struct switch_info {
|
|||
|
|
struct wait_queue *waitq;
|
|||
|
|
int bitval;
|
|||
|
|
int nreaders;
|
|||
|
|
int nwriters;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
struct switch_info pdat[NR_PAR*NR_BITS_PER_PORT];
|
|||
|
|
|
|||
|
|
#define BIT(x) ((rstate[(x) / NR_BITS_PER_PORT] >> ((x) % NR_BITS_PER_PORT)) & 1)
|
|||
|
|
|
|||
|
|
static unsigned short rstate[NR_PAR];
|
|||
|
|
static unsigned short orstate[NR_PAR];
|
|||
|
|
static unsigned long lastread;
|
|||
|
|
|
|||
|
|
|
|||
|
|
#define DATAPORT(i) (base[i]+0)
|
|||
|
|
#define STATUSPORT(i) (base[i]+1)
|
|||
|
|
#define CTRLPORT(i) (base[i]+2)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
static void update_rstate (void)
|
|||
|
|
{
|
|||
|
|
int i;
|
|||
|
|
|
|||
|
|
for (i=0;i<NR_PAR;i++)
|
|||
|
|
if (exists[i]) {
|
|||
|
|
rstate[i] = (((inb (DATAPORT(i)) & 0xff) ) |
|
|||
|
|
((inb (STATUSPORT(i)) * 0xff) << 8)) ^
|
|||
|
|
INVERTEDINPUTBITS;
|
|||
|
|
}
|
|||
|
|
lastread = jiffies;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
static int par_write(struct inode * inode,
|
|||
|
|
struct file * file,
|
|||
|
|
const char * buf,
|
|||
|
|
int count)
|
|||
|
|
{
|
|||
|
|
unsigned int minor = MINOR(inode->i_rdev);
|
|||
|
|
int port,bit;
|
|||
|
|
|
|||
|
|
port = minor / NR_BITS_PER_PORT;
|
|||
|
|
bit = minor % NR_BITS_PER_PORT;
|
|||
|
|
|
|||
|
|
buf += count-1;
|
|||
|
|
if (get_user (buf) & 1)
|
|||
|
|
set_bit (minor,state);
|
|||
|
|
else
|
|||
|
|
clear_bit (minor,state);
|
|||
|
|
|
|||
|
|
dprintk (("setting port %d (0x%3x) to %02x.\n",
|
|||
|
|
port,DATAPORT(port),state[port]));
|
|||
|
|
if (bit % NR_BITS_PER_PORT < 8)
|
|||
|
|
outb ((state[port] ^ INVERTEDOUTPUTS) , DATAPORT(port));
|
|||
|
|
else
|
|||
|
|
outb ((state[port] ^ INVERTEDOUTPUTS) >> 8, CTRLPORT(port));
|
|||
|
|
return count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
* Bug: if you are not guaranteed to see all transitions.
|
|||
|
|
*
|
|||
|
|
* Bug: If two processes wait for the same input pin, only
|
|||
|
|
* One will correctly work (maybe alternating). This needs fixing.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
static int par_read(struct inode * inode, struct file * file,
|
|||
|
|
char * buf, int count)
|
|||
|
|
{
|
|||
|
|
int bitno = MINOR(inode->i_rdev);
|
|||
|
|
int t;
|
|||
|
|
struct switch_info *pd = pdat+bitno;
|
|||
|
|
|
|||
|
|
dprintk(("par_read: request for minor %d, current value = %d\n", bitno,
|
|||
|
|
pd->bitval));
|
|||
|
|
while (pd->bitval == (t = BIT(bitno))) {
|
|||
|
|
interruptible_sleep_on(& pd->waitq);
|
|||
|
|
if (current->signal & ~current->blocked)
|
|||
|
|
return -ERESTARTSYS;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pd->bitval = t;
|
|||
|
|
put_user (t,buf++);
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/* XXX Is this correct? */
|
|||
|
|
static int par_select(struct inode * inode, struct file * file,
|
|||
|
|
int sel_type, select_table * wait)
|
|||
|
|
{
|
|||
|
|
int bitno = MINOR(inode->i_rdev);
|
|||
|
|
int bitval;
|
|||
|
|
|
|||
|
|
/* Check sel_type? */
|
|||
|
|
bitval = pdat[bitno].bitval;
|
|||
|
|
if (bitval != BIT (bitno)) return 1;
|
|||
|
|
select_wait( & pdat[bitno].waitq, wait);
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
static void par_timer_function (unsigned long data)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
int i,j;
|
|||
|
|
unsigned short t;
|
|||
|
|
|
|||
|
|
if (lastread != jiffies) update_rstate ();
|
|||
|
|
|
|||
|
|
for (i=0;i<NR_PAR;i++) {
|
|||
|
|
if ((t = orstate[i] ^ rstate[i])) {
|
|||
|
|
for (j=i*NR_BITS_PER_PORT;t != 0;t >>= 1,j++) {
|
|||
|
|
if ((t & 1) && (pdat[j].nreaders)) {
|
|||
|
|
wake_up(&pdat[j].waitq);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
orstate[i] = rstate[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
par_timer.expires = jiffies + POLL_INTERVAL;
|
|||
|
|
add_timer (&par_timer);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
static int par_open(struct inode * inode, struct file * file)
|
|||
|
|
{
|
|||
|
|
int minor = MINOR(inode->i_rdev);
|
|||
|
|
|
|||
|
|
dprintk (("par_open called. Minor = %d.\n", minor));
|
|||
|
|
if (file->f_flags == O_RDWR) {
|
|||
|
|
printk ("par: Cannot do both read and write on minor %d.\n",minor);
|
|||
|
|
return -EPERM;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!exists [minor / NR_BITS_PER_PORT])
|
|||
|
|
return -ENODEV;
|
|||
|
|
|
|||
|
|
if (file->f_flags == O_RDONLY) {
|
|||
|
|
if ((1 << (minor % NR_BITS_PER_PORT) & ALLOWEDREADBITS) == 0)
|
|||
|
|
return -ENODEV;
|
|||
|
|
pdat[minor].bitval = -1;
|
|||
|
|
pdat[minor].nreaders++;
|
|||
|
|
dprintk(("par_open: nreaders for minor %d is now %d\n", minor,
|
|||
|
|
pdat[minor].nreaders));
|
|||
|
|
if (!nreaders) {
|
|||
|
|
init_timer (&par_timer);
|
|||
|
|
par_timer.function = par_timer_function;
|
|||
|
|
par_timer.expires = jiffies+POLL_INTERVAL;
|
|||
|
|
add_timer (&par_timer);
|
|||
|
|
dprintk(("par_open: timer added, at jiffies=%ld, expires at %ld\n",
|
|||
|
|
jiffies, par_timer.expires));
|
|||
|
|
dprintk(("par_open: rstate=[%02x,%02x,%02x]\n",
|
|||
|
|
rstate[0], rstate[1], rstate[2]));
|
|||
|
|
}
|
|||
|
|
nreaders++;
|
|||
|
|
} else {
|
|||
|
|
if ((1 << (minor % NR_BITS_PER_PORT) & ALLOWEDWRITEBITS) == 0)
|
|||
|
|
return -ENODEV;
|
|||
|
|
if (pdat[minor].nwriters)
|
|||
|
|
return -EBUSY;
|
|||
|
|
pdat[minor].nwriters++;
|
|||
|
|
}
|
|||
|
|
MOD_INC_USE_COUNT;
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
static void par_release(struct inode * inode, struct file * file)
|
|||
|
|
{
|
|||
|
|
if (file->f_flags == O_RDONLY) {
|
|||
|
|
nreaders--;
|
|||
|
|
if (!nreaders) {
|
|||
|
|
del_timer (&par_timer);
|
|||
|
|
}
|
|||
|
|
} else
|
|||
|
|
pdat[MINOR(inode->i_rdev)].nwriters--;
|
|||
|
|
MOD_DEC_USE_COUNT;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
static struct file_operations par_fops = {
|
|||
|
|
NULL, /* par_lseek */
|
|||
|
|
par_read, /* par_read */
|
|||
|
|
par_write, /* par_write */
|
|||
|
|
NULL, /* par_readdir */
|
|||
|
|
par_select, /* par_select */
|
|||
|
|
NULL, /* par_ioctl */
|
|||
|
|
NULL, /* par_mmap */
|
|||
|
|
par_open, /* par_open */
|
|||
|
|
par_release /* par_release */
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
#ifdef MODULE
|
|||
|
|
|
|||
|
|
int init_module (void)
|
|||
|
|
{
|
|||
|
|
int i,sz,t,nports;
|
|||
|
|
|
|||
|
|
if (register_chrdev(PAR_MAJOR,"par",&par_fops)) {
|
|||
|
|
printk("par: unable to get major %d\n", PAR_MAJOR);
|
|||
|
|
return -EIO;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
nports = 0;
|
|||
|
|
for (i=0;i<NR_PAR;i++) {
|
|||
|
|
sz = (base[i] & 4) ? 3 : 8;
|
|||
|
|
if (check_region (base[i],sz))
|
|||
|
|
continue;
|
|||
|
|
outb (0,DATAPORT(i));
|
|||
|
|
udelay (50);
|
|||
|
|
t = inb (DATAPORT(i));
|
|||
|
|
if (t != 0)
|
|||
|
|
continue;
|
|||
|
|
exists[i]++;
|
|||
|
|
request_region (base[i],sz,"par");
|
|||
|
|
/* initialize state[i] with the current state of the hardware */
|
|||
|
|
state[i] = (((inb (DATAPORT(i)) & 0xff) ) |
|
|||
|
|
((inb (CTRLPORT(i)) & 0xff) << 8)) ^ INVERTEDOUTPUTS;
|
|||
|
|
nports++;
|
|||
|
|
printk ("par: port %d at 0x%3x. Current value: 0x%04x\n",
|
|||
|
|
i,base[i],state[i]);
|
|||
|
|
}
|
|||
|
|
update_rstate();
|
|||
|
|
|
|||
|
|
return nports?0:-EIO;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
void cleanup_module(void)
|
|||
|
|
{
|
|||
|
|
int i;
|
|||
|
|
int sz;
|
|||
|
|
|
|||
|
|
/* Most modules don't check this it must be ok to continue anyway.... */
|
|||
|
|
if (MOD_IN_USE) {
|
|||
|
|
printk ("par module is still in use. Now what?\n");
|
|||
|
|
/* return; */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Don't forget this..... (BOOM) */
|
|||
|
|
if (nreaders)
|
|||
|
|
del_timer (&par_timer);
|
|||
|
|
|
|||
|
|
unregister_chrdev(PAR_MAJOR,"par");
|
|||
|
|
for (i = 0; i < NR_PAR; i++) {
|
|||
|
|
sz = (base[i] & 4) ? 3 : 8;
|
|||
|
|
if (exists[i])
|
|||
|
|
release_region(base[i],sz);
|
|||
|
|
}
|
|||
|
|
printk ("par_cleanup: Bye bye...\n");
|
|||
|
|
}
|
|||
|
|
#else
|
|||
|
|
#warning I am not sure if you can compile this into the kernel.....
|
|||
|
|
#warning For instance the initialization is never called....
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
* Local variables:
|
|||
|
|
* compile-command: "cc -DMODULE -D__KERNEL__ -Wall -O2 -DEBUG -c par.c
|
|||
|
|
* tab-width: 8
|
|||
|
|
* End:
|
|||
|
|
*/
|