/* $Id: lirc_parallel.c,v 5.36 2007/02/13 06:45:16 lirc Exp $ */ /**************************************************************************** ** lirc_parallel.c ********************************************************* **************************************************************************** * * lirc_parallel - device driver for infra-red signal receiving and * transmitting unit built by the author * * Copyright (C) 1998 Christoph Bartelmus * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /*********************************************************************** ************************* Includes *********************** ***********************************************************************/ #ifdef HAVE_CONFIG_H # include #endif #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 2, 18) #error "**********************************************************" #error " Sorry, this driver needs kernel version 2.2.18 or higher " #error "**********************************************************" #endif #include #ifdef CONFIG_SMP #error "--- Sorry, this driver is not SMP safe. ---" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0) #include #endif #include #include #include #include "drivers/lirc.h" #include "drivers/kcompat.h" #include "drivers/lirc_dev/lirc_dev.h" #include "lirc_parallel.h" #define LIRC_DRIVER_NAME "lirc_parallel" #ifndef LIRC_IRQ #define LIRC_IRQ 7 #endif #ifndef LIRC_PORT #define LIRC_PORT 0x378 #endif #ifndef LIRC_TIMER #define LIRC_TIMER 65536 #endif /*********************************************************************** ************************* Globale Variablen *********************** ***********************************************************************/ static int debug = 0; unsigned int irq = LIRC_IRQ; unsigned int io = LIRC_PORT; #ifdef LIRC_TIMER unsigned int timer = 0; unsigned int default_timer = LIRC_TIMER; #endif #define WBUF_SIZE (256) #define RBUF_SIZE (256) /* this must be a power of 2 larger than 1 */ static lirc_t wbuf[WBUF_SIZE]; static lirc_t rbuf[RBUF_SIZE]; DECLARE_WAIT_QUEUE_HEAD(lirc_wait); unsigned int rptr=0,wptr=0; unsigned int lost_irqs=0; int is_open=0; struct parport *pport; struct pardevice *ppdevice; int is_claimed=0; unsigned int tx_mask = 1; /*********************************************************************** ************************* Interne Funktionen *********************** ***********************************************************************/ static unsigned int __inline__ in(int offset) { switch(offset) { case LIRC_LP_BASE: return(parport_read_data(pport)); case LIRC_LP_STATUS: return(parport_read_status(pport)); case LIRC_LP_CONTROL: return(parport_read_control(pport)); } return(0); /* make compiler happy */ } static void __inline__ out(int offset, int value) { switch(offset) { case LIRC_LP_BASE: parport_write_data(pport,value); break; case LIRC_LP_CONTROL: parport_write_control(pport,value); break; case LIRC_LP_STATUS: printk(KERN_INFO "%s: attempt to write to status register\n", LIRC_DRIVER_NAME); break; } } static unsigned int __inline__ lirc_get_timer(void) { return(in(LIRC_PORT_TIMER)&LIRC_PORT_TIMER_BIT); } static unsigned int __inline__ lirc_get_signal(void) { return(in(LIRC_PORT_SIGNAL)&LIRC_PORT_SIGNAL_BIT); } static void __inline__ lirc_on(void) { out(LIRC_PORT_DATA, tx_mask); } static void __inline__ lirc_off(void) { out(LIRC_PORT_DATA,0); } static unsigned int init_lirc_timer(void) { struct timeval tv,now; unsigned int level,newlevel,timeelapsed,newtimer; int count=0; do_gettimeofday(&tv); tv.tv_sec++; /* wait max. 1 sec. */ level=lirc_get_timer(); do { newlevel=lirc_get_timer(); if(level==0 && newlevel!=0) count++; level=newlevel; do_gettimeofday(&now); } while(count<1000 && (now.tv_sec=1000 && timeelapsed>0) { if(default_timer==0) /* autodetect timer */ { newtimer=(1000000*count)/timeelapsed; printk(KERN_INFO "%s: %u Hz timer detected\n", LIRC_DRIVER_NAME,newtimer); return(newtimer); } else { newtimer=(1000000*count)/timeelapsed; if(abs(newtimer-default_timer)> default_timer/10) /* bad timer */ { printk(KERN_NOTICE "%s: bad timer: %u Hz\n", LIRC_DRIVER_NAME,newtimer); printk(KERN_NOTICE "%s: using default timer: " "%u Hz\n", LIRC_DRIVER_NAME,default_timer); return(default_timer); } else { printk(KERN_INFO "%s: %u Hz timer detected\n", LIRC_DRIVER_NAME,newtimer); return(newtimer); /* use detected value */ } } } else { printk(KERN_NOTICE "%s: no timer detected\n",LIRC_DRIVER_NAME); return(0); } } static int lirc_claim(void) { if(parport_claim(ppdevice)!=0) { printk(KERN_WARNING "%s: could not claim port\n", LIRC_DRIVER_NAME); printk(KERN_WARNING "%s: waiting for port becoming available" "\n",LIRC_DRIVER_NAME); if(parport_claim_or_block(ppdevice)<0) { printk(KERN_NOTICE "%s: could not claim port, giving" " up\n",LIRC_DRIVER_NAME); return(0); } } out(LIRC_LP_CONTROL,LP_PSELECP|LP_PINITP); is_claimed=1; return(1); } /*********************************************************************** ************************* interrupt handler ************************ ***********************************************************************/ static inline void rbuf_write(lirc_t signal) { unsigned int nwptr; nwptr=(wptr+1) & (RBUF_SIZE-1); if(nwptr==rptr) /* no new signals will be accepted */ { lost_irqs++; printk(KERN_NOTICE "%s: buffer overrun\n",LIRC_DRIVER_NAME); return; } rbuf[wptr]=signal; wptr=nwptr; } static void irq_handler(int i,void *blah,struct pt_regs * regs) { struct timeval tv; static struct timeval lasttv; static int init=0; long signal; lirc_t data; unsigned int level,newlevel; unsigned int timeout; if(!MOD_IN_USE) return; if(!is_claimed) { return; } /* disable interrupt */ /* disable_irq(irq); out(LIRC_PORT_IRQ,in(LIRC_PORT_IRQ)&(~LP_PINTEN)); */ if(in(1)&LP_PSELECD) { return; } #ifdef LIRC_TIMER if(init) { do_gettimeofday(&tv); signal=tv.tv_sec-lasttv.tv_sec; if(signal>15) { data=PULSE_MASK; /* really long time */ } else { data=(lirc_t) (signal*1000000+ tv.tv_usec-lasttv.tv_usec+ LIRC_SFH506_DELAY); }; rbuf_write(data); /* space */ } else { if(timer==0) /* wake up; we'll lose this signal but it will be garbage if the device is turned on anyway */ { timer=init_lirc_timer(); /* enable_irq(irq); */ return; } init=1; } timeout=timer/10; /* timeout after 1/10 sec. */ signal=1; level=lirc_get_timer(); do{ newlevel=lirc_get_timer(); if(level==0 && newlevel!=0) signal++; level=newlevel; /* giving up */ if(signal>timeout || (in(1)&LP_PSELECD)) { signal=0; printk(KERN_NOTICE "%s: timeout\n",LIRC_DRIVER_NAME); break; } } while(lirc_get_signal()); if(signal!=0) { /* ajust value to usecs */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0) unsigned long long helper; helper = ((unsigned long long) signal)*1000000; do_div(helper, timer); signal = (long) helper; #else signal=(long) ((((double) signal)*1000000)/timer); #endif if(signal>LIRC_SFH506_DELAY) { data=signal-LIRC_SFH506_DELAY; } else { data=1; } rbuf_write(PULSE_BIT|data); /* pulse */ } do_gettimeofday(&lasttv); #else /* add your code here */ #endif wake_up_interruptible(&lirc_wait); /* enable interrupt */ /* enable_irq(irq); out(LIRC_PORT_IRQ,in(LIRC_PORT_IRQ)|LP_PINTEN); */ } /*********************************************************************** ************************** file_operations ************************ ***********************************************************************/ static loff_t lirc_lseek(struct file *filep,loff_t offset,int orig) { return(-ESPIPE); } static ssize_t lirc_read(struct file *filep,char *buf,size_t n,loff_t *ppos) { int result=0; int count=0; DECLARE_WAITQUEUE(wait, current); if(n%sizeof(lirc_t)) return(-EINVAL); add_wait_queue(&lirc_wait,&wait); set_current_state(TASK_INTERRUPTIBLE); while(countf_flags & O_NONBLOCK) { result=-EAGAIN; break; } if (signal_pending(current)) { result=-ERESTARTSYS; break; } schedule(); set_current_state(TASK_INTERRUPTIBLE); } } remove_wait_queue(&lirc_wait,&wait); set_current_state(TASK_RUNNING); return(count ? count:result); } static ssize_t lirc_write(struct file *filep,const char *buf,size_t n, loff_t *ppos) { int count; unsigned int i; unsigned int level,newlevel; unsigned long flags; lirc_t counttimer; if(!is_claimed) { return(-EBUSY); } if(n%sizeof(lirc_t)) return(-EINVAL); count=n/sizeof(lirc_t); if(count>WBUF_SIZE || count%2==0) return(-EINVAL); if(copy_from_user(wbuf,buf,n)) return -EFAULT; #ifdef LIRC_TIMER if(timer==0) /* try again if device is ready */ { timer=init_lirc_timer(); if(timer==0) return(-EIO); } /* ajust values from usecs */ for(i=0;i= KERNEL_VERSION(2, 4, 0) unsigned long long helper; helper = ((unsigned long long) wbuf[i])*timer; do_div(helper, 1000000); wbuf[i] = (lirc_t) helper; #else wbuf[i]=(lirc_t) (((double) wbuf[i])*timer/1000000); #endif } local_irq_save(flags); i=0; while(i KERNEL_VERSION(2, 6, 3) pport=parport_find_base(io); #else pport=parport_enumerate(); while(pport!=NULL) { if(pport->base==io) { break; } pport=pport->next; } #endif if(pport==NULL) { printk(KERN_NOTICE "%s: no port at %x found\n", LIRC_DRIVER_NAME,io); return(-ENXIO); } ppdevice=parport_register_device(pport,LIRC_DRIVER_NAME, pf,kf,irq_handler,0,NULL); #if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 3) parport_put_port(pport); #endif if(ppdevice==NULL) { printk(KERN_NOTICE "%s: parport_register_device() failed\n", LIRC_DRIVER_NAME); return(-ENXIO); } if(parport_claim(ppdevice)!=0) goto skip_init; is_claimed=1; out(LIRC_LP_CONTROL,LP_PSELECP|LP_PINITP); #ifdef LIRC_TIMER if(debug) { out(LIRC_PORT_DATA, tx_mask); } timer=init_lirc_timer(); # if 0 /* continue even if device is offline */ if(timer==0) { is_claimed=0; parport_release(pport); parport_unregister_device(ppdevice); return(-EIO); } # endif if(debug) { out(LIRC_PORT_DATA,0); } #endif is_claimed=0; parport_release(ppdevice); skip_init: if ((plugin.minor = lirc_register_plugin(&plugin)) < 0) { printk(KERN_NOTICE "%s: register_chrdev() failed\n",LIRC_DRIVER_NAME); parport_unregister_device(ppdevice); return(-EIO); } printk(KERN_INFO "%s: installed using port 0x%04x irq %d\n",LIRC_DRIVER_NAME,io,irq); return(0); } void cleanup_module(void) { parport_unregister_device(ppdevice); lirc_unregister_plugin(plugin.minor); } MODULE_DESCRIPTION("Infrared receiver driver for parallel ports."); MODULE_AUTHOR("Christoph Bartelmus"); MODULE_LICENSE("GPL"); module_param(io, int, 0444); MODULE_PARM_DESC(io, "I/O address base (0x3bc, 0x378 or 0x278)"); module_param(irq, int, 0444); MODULE_PARM_DESC(irq, "Interrupt (7 or 5)"); module_param(tx_mask, int, 0444); MODULE_PARM_DESC(tx_maxk, "Transmitter mask (default: 0x01)"); module_param(debug, bool, 0644); MODULE_PARM_DESC(debug, "Enable debugging messages"); EXPORT_NO_SYMBOLS; #endif /* MODULE */