/* IRctrl: irctrl.c: A linux kernel IR remote control driver Copyright (C) 1998 Alan Yates This program 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 program 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 this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef LINUX_VERSION_CODE # include #endif #ifndef VERSION_CODE # define VERSION_CODE(vers,rel,seq) (((vers)<<16)|((rel)<<8)|(seq)) #endif #if VERSION_CODE(2,2,0) < LINUX_VERSION_CODE # define __NEW_LINUX__ #endif #ifndef __KERNEL__ # define __KERNEL__ #endif #ifndef MODULE # define MODULE #endif #include #include #include #include #include #include #include #include #include #include #include /* version differences */ #ifdef __NEW_LINUX__ # include # include /* cool new junk */ MODULE_AUTHOR("Alan Yates "); MODULE_DESCRIPTION("IR remote control driver"); MODULE_SUPPORTED_DEVICE("Alan's IR modem"); MODULE_PARM(irctrl_base, "i"); MODULE_PARM_DESC(irctrl_base, "IRctrl port base address"); MODULE_PARM(irctrl_irq, "i"); MODULE_PARM_DESC(irctrl_irq, "IRctrl interrupt request line"); MODULE_PARM(irctrl_major, "i"); MODULE_PARM_DESC(irctrl_major, "IRctrl char device major"); #else /* __NEW_LINUX__ */ # include #define signal_pending(current) (current->signal & ~current->blocked) #define mdelay(x) udelay(1000*x) static inline unsigned long copy_to_user(void *to, const void *from, unsigned long n) { memcpy_tofs(to,from,n); return 0; } static inline unsigned long copy_from_user(void *to, const void *from, unsigned long n) { memcpy_fromfs(to,from,n); return 0; } #endif /* __NEW_LINUX__ */ /* defines */ #define irctrl_id "irctrl: irctrl 2.0 by Alan Yates \n" #define irctrl_bufsize 4096 /* one page buffers */ #define min(x,y) ((x)<(y)?(x):(y)) /* why isn't this standard */ #define digit(c) (((c)>='0')&&((c)<='9')) /* system resources */ int irctrl_base = 0x378; int irctrl_irq = 7; int irctrl_major = 0; struct wait_queue *irctrl_rq = 0; /* read sleeping queue */ struct wait_queue *irctrl_wq = 0; /* write sleeping queue */ /* buffer for data */ volatile int irctrl_txbusy = 0; /* tx lock */ volatile unsigned int irctrl_wcursor = 0; /* write cursor */ unsigned char irctrl_rxbuf[irctrl_bufsize]; /* IR RX output buffer */ unsigned long irctrl_txbuf[irctrl_bufsize]; /* IR TX timing buffer */ /* private flip data struct */ struct pdata { unsigned int irctrl_rcursor; }; /* IR leading edge interrupt handler */ static void irctrl_interrupt(int irq, void *regs) { struct timeval tv; char buf[40]; int size = 0, i = 0; if(!MOD_IN_USE) return; do_gettimeofday(&tv); size = sprintf(buf, "%9d %6d\n", (int)tv.tv_sec, (int)tv.tv_usec); /* circularly copy into the output buffer */ for(i = 0; i < size; i++) irctrl_rxbuf[(irctrl_wcursor+i)%irctrl_bufsize] = buf[i]; irctrl_wcursor += size; irctrl_wcursor %= irctrl_bufsize; wake_up_interruptible(&irctrl_rq); } /* generate a square waveform of length len and period t (us) */ static void irctrl_irtone(unsigned long len, unsigned long t) { int i = 0; t++; /* div by zero avoid */ for(i = 0; i < len/t; i++) { outb(0xff, irctrl_base); /* mark */ udelay(t/2); outb(0x00, irctrl_base); /* space */ udelay(t/2); } } /* TX driver */ void irctrl_dotx(int txs) { int i = 0; unsigned long flags = 0; if(txs > irctrl_bufsize) { printk(KERN_INFO "irctrl: tx buffer overrun!\n"); return; } /* clear interupts for timing accuracy */ save_flags(flags); cli(); /* do the TX of IR data */ for(i = 1; i < txs; i+=2) { /* data MARK - carrier on */ irctrl_irtone(irctrl_txbuf[i], irctrl_txbuf[0]); /* data SPACE - carrier off */ udelay(irctrl_txbuf[i+1]); } restore_flags(flags); } /* filenode open */ static int irctrl_open(struct inode *inode, struct file *flip) { MOD_INC_USE_COUNT; flip->private_data = kmalloc(sizeof(struct pdata), GFP_KERNEL); if(!flip->private_data) return -ENOMEM; ((struct pdata *)flip->private_data)->irctrl_rcursor = irctrl_wcursor; return 0; } /* filenode release */ #ifdef __NEW_LINUX__ static int #else static void #endif irctrl_release(struct inode *inode, struct file *flip) { kfree(flip->private_data); if(MOD_IN_USE) MOD_DEC_USE_COUNT; #ifdef __NEW_LINUX__ return 0; #endif } /* filenode read */ #ifdef __NEW_LINUX__ static size_t irctrl_read(struct file *flip, char *buf, size_t count, loff_t *offset) { #else static int irctrl_read(struct inode *inode, struct file *flip, char *buf, int count) { #endif int bytes = 0; struct pdata *pd = (struct pdata *) flip->private_data; while(pd->irctrl_rcursor == irctrl_wcursor) { if(flip->f_flags & O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&irctrl_rq); if(signal_pending(current)) return -ERESTARTSYS; } if(irctrl_wcursor > pd->irctrl_rcursor) bytes = min(count, irctrl_wcursor - pd->irctrl_rcursor); else /* write pointer wrap */ bytes = min(count, irctrl_bufsize - pd->irctrl_rcursor); if(copy_to_user(buf, &irctrl_rxbuf[pd->irctrl_rcursor], bytes)) return -EFAULT; pd->irctrl_rcursor += bytes; pd->irctrl_rcursor %= irctrl_bufsize; return bytes; } /* filenode write */ #ifdef __NEW_LINUX__ static size_t irctrl_write(struct file *flip, char *buf, size_t count, loff_t *offset) { #else static int irctrl_write(struct inode *inode, struct file *flip, char *buf, int count) { #endif char *kbuf = 0, *cp = 0; int i = 0, j = 0, bad = -1; while(irctrl_txbusy) { if(flip->f_flags & O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&irctrl_wq); if(signal_pending(current)) return -ERESTARTSYS; } irctrl_txbusy = 1; kbuf = kmalloc(count, GFP_KERNEL); if(!kbuf) return -ENOMEM; if(copy_from_user(kbuf, buf, count)) { kfree(kbuf); return -EFAULT; } /* get timing data */ cp = kbuf; for(j = 0; j < count; j++) if(cp[j] == '\n') cp[j] = 0; while((irctrl_txbuf[i++] = simple_strtoul(cp, &cp, 0)) > 0) { while(!digit(*cp)) cp++; if(cp > kbuf+count) break; } kfree(kbuf); /* check the values for sanity */ for(j = 1; j < i; j++) { if(irctrl_txbuf[j] <= 0) bad = j; if(irctrl_txbuf[j] > 999999) bad = j; } if(irctrl_txbuf[0] <= 0) bad = 1; /* do tx and awake any blocked writers */ if((i < 3) || (bad > 0)) { printk(KERN_INFO "irctrl: bad TX data: data[%i] = %i\n", bad, (int) irctrl_txbuf[bad]); } else irctrl_dotx(i); irctrl_txbusy = 0; wake_up_interruptible(&irctrl_wq); return count; } #ifdef __NEW_LINUX__ /* filenode poll */ static unsigned int irctrl_select(struct file *flip, poll_table *wait) { struct pdata *pd = (struct pdata *) flip->private_data; unsigned int mask = 0; poll_wait(flip, &irctrl_rq, wait); poll_wait(flip, &irctrl_wq, wait); if(pd->irctrl_rcursor != irctrl_wcursor) mask |= POLLIN | POLLRDNORM; if(!irctrl_txbusy) mask |= POLLOUT | POLLWRNORM; return mask; } #else /* filenode select */ static int irctrl_select(struct inode *inode, struct file *flip, int mode, select_table *table) { struct pdata *pd = (struct pdata *) flip->private_data; if(mode == SEL_IN) { if(pd->irctrl_rcursor != irctrl_wcursor) return 1; select_wait(&irctrl_rq, table); return 0; } if(mode == SEL_OUT) { if(!irctrl_txbusy) return 1; select_wait(&irctrl_wq, table); return 0; } /* no exceptions */ return 0; } #endif /* filenode operations struct */ static struct file_operations irctrl_fops = { 0, /* lseek */ (void *)irctrl_read, /* read */ (void *)irctrl_write, /* write */ 0, /* readdir */ (void *)irctrl_select, /* select/poll */ 0, /* ioctl */ 0, /* mmap */ (void *)irctrl_open, /* open */ #ifdef __NEW_LINUX__ 0, /* flush */ #endif (void *)irctrl_release, /* release */ 0, /* fsync */ 0, /* fasync */ }; /* module init */ int init_module(void) { int ret; printk(KERN_INFO irctrl_id); #ifndef __NEW_LINUX__ register_symtab(0); #endif if((ret = check_region(irctrl_base, 3)) < 0) { printk(KERN_INFO "irctrl: can't secure IO space 0x%x-0x%x, giving up\n", irctrl_base, irctrl_base+3); return ret; } request_region(irctrl_base, 3, "irctrl"); if((ret = request_irq(irctrl_irq, (void *)irctrl_interrupt, SA_INTERRUPT | SA_SAMPLE_RANDOM, "irctrl", 0)) < 0) { printk(KERN_INFO "irctrl: can't get IRQ %d, giving up\n", irctrl_irq); release_region(irctrl_base, 3); return ret; } /* enable parport interrupts */ outb(0x10, irctrl_base+2); if((ret = register_chrdev(irctrl_major, "irctrl", &irctrl_fops)) < 0) { printk(KERN_INFO "irctrl: can't register char dev %d\n", irctrl_major); outb(0x0, irctrl_base+2); release_region(irctrl_base, 3); return ret; } if(irctrl_major == 0) irctrl_major = ret; printk(KERN_INFO "irctrl: device major %d registered\n", irctrl_major); return 0; } /* module cleanup */ void cleanup_module(void) { printk(KERN_INFO "irctrl: unloading module\n"); unregister_chrdev(irctrl_major, "irctrl"); /* release IRQ */ outb(0x00, irctrl_base+2); free_irq(irctrl_irq, 0); release_region(irctrl_base, 3); }