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:
|
||
*/
|