/* * sampler.c: A Linux kernel driver for the EA pocket sampler. * * By: Alan Yates */ #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,1,18) < LINUX_VERSION_CODE # define __NEW_LINUX__ #endif #define MODULE #define __KERNEL__ #include #include #include #include #include #include #include #include #include #ifdef __NEW_LINUX__ # include # include #else # include 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 #define min(x,y) ((x)<(y)?(x):(y)) /* why isn't this standard */ #define digit(c) (((c)>='0')&&((c)<='9')) /* globals */ int eapsampler_port = 0x378; int eapsampler_major = 0; char eapsampler_name[] = "eapsampler"; struct wait_queue *eapsampler_wq = 0; struct timer_list eapsampler_timer; int eapsampler_period = 0; char eapsampler_buf[PAGE_SIZE]; unsigned int eapsampler_wp = 0; struct eapsampler_file { unsigned int rp; int oneshot; }; #ifdef __NEW_LINUX__ MODULE_AUTHOR("Alan Yates "); MODULE_DESCRIPTION("EA Pocket Sampler Driver v2.0"); MODULE_SUPPORTED_DEVICE("EA Pocket Sampler"); MODULE_PARM(eapsampler_port, "i"); MODULE_PARM_DESC(eapsampler_port, "IRctrl port base address"); MODULE_PARM(eapsampler_major, "i"); MODULE_PARM_DESC(eapsampler_major, "sampler char device major"); #endif /* turn sampler power on */ static int eas_poweron(void) { outb(0xff, eapsampler_port); udelay(1000); return 0; } /* turn sampler power off */ static int eas_poweroff(void) { outb(0x00, eapsampler_port); udelay(1000); return 0; } /* read sampler scale switch setting */ static int eas_scale(void) { unsigned char nb = 0; /* switch off adc, enable high nible */ outb(0x0f, eapsampler_port+2); udelay(1000); /* get and mask switch pos bit */ nb = inb(eapsampler_port+1) & 0x80; if(nb) return 20; return 2; } /* take a sample */ static unsigned char eas_sample(void) { int loops = 0; unsigned char data = 0; /* toggle ~WR to start conversion */ outb(0x03, eapsampler_port+2); udelay(1000); outb(0x02, eapsampler_port+2); /* wait until EOC */ while(inb(eapsampler_port+1) & 0x08) { if(loops++ > 10000) { printk(KERN_ERR "eapsampler: warning: waited 10000 loops for EOC\n"); return 0; } } /* read data from ADC */ data = ((inb(eapsampler_port+1) & 0xf0) >> 4) ^ 0x08; outb(0x03, eapsampler_port+2); udelay(1000); data |= (inb(eapsampler_port+1) & 0xf0) ^ 0x80; return data; } static void eapsampler_takesample(void) { int i = 0, bytes = 0; unsigned long flags; struct timeval tv; char buf[40]; if(MOD_IN_USE) { save_flags(flags); cli(); do_gettimeofday(&tv); bytes = sprintf(buf, "%ld %ld %d %d\n", tv.tv_sec, tv.tv_usec, eas_scale(), eas_sample()); for(i = 0; i < bytes; i++) eapsampler_buf[(eapsampler_wp + i) % PAGE_SIZE] = buf[i]; eapsampler_wp += bytes; eapsampler_wp %= PAGE_SIZE; restore_flags(flags); } wake_up_interruptible(&eapsampler_wq); if(eapsampler_period) { init_timer(&eapsampler_timer); eapsampler_timer.function = (void *) eapsampler_takesample; eapsampler_timer.data = 0; eapsampler_timer.expires = jiffies + eapsampler_period; add_timer(&eapsampler_timer); } } /* file node open */ static int eapsampler_open(struct inode *inode, struct file *flip) { MOD_INC_USE_COUNT; flip->private_data = kmalloc(sizeof(struct eapsampler_file), GFP_KERNEL); if(!flip->private_data) return -ENOMEM; ((struct eapsampler_file *)flip->private_data)->rp = eapsampler_wp; ((struct eapsampler_file *)flip->private_data)->oneshot = 0; return 0; } /* file node release */ #ifdef __NEW_LINUX__ static int #else static void #endif eapsampler_release(struct inode *inode, struct file *flip) { MOD_DEC_USE_COUNT; kfree(flip->private_data); #ifdef __NEW_LINUX__ return 0; #endif } /* file node read */ #ifdef __NEW_LINUX__ static size_t eapsampler_read(struct file *flip, char *buf, size_t count, loff_t *off) { #else static int eapsampler_read(struct inode *inode, struct file *flip, char *buf, int count) { #endif int bytes = 0, i = 0; char *kbuf = (char *) get_free_page(GFP_KERNEL); struct eapsampler_file *sf = (struct eapsampler_file *) flip->private_data; if(!kbuf) return -ENOMEM; if(sf->oneshot) return 0; if(!eapsampler_period) { eapsampler_takesample(); sf->oneshot = 1; } while(-1) { (eapsampler_wp >= sf->rp) ? (bytes = eapsampler_wp - sf->rp) : (bytes = PAGE_SIZE - sf->rp); bytes = min(bytes, count); if(bytes) break; if(flip->f_flags & O_NONBLOCK) return -EAGAIN; interruptible_sleep_on(&eapsampler_wq); if(signal_pending(current)) return -ERESTARTSYS; } for(i = 0; i < bytes; i++) kbuf[i] = eapsampler_buf[(sf->rp + i) % PAGE_SIZE]; sf->rp += bytes; sf->rp %= PAGE_SIZE; if(copy_to_user(buf, kbuf, bytes)) return -EFAULT; free_page((unsigned long) kbuf); return bytes; } /* file node write */ #ifdef __NEW_LINUX__ static size_t eapsampler_write(struct file *flip, char *buf, size_t count, loff_t *off) { #else static int eapsampler_write(struct inode *inode, struct file *flip, char *buf, int count) { #endif int i; char *kbuf = kmalloc((unsigned int) count, GFP_KERNEL), *cp = 0; if(!kbuf) return -ENOMEM; if(copy_from_user(kbuf, buf, count) > 0) { kfree(kbuf); return -EFAULT; } cp = kbuf; for(i = 0; i < count; i++) if(cp[i] == '\n') cp[i] = 0; i = eapsampler_period; eapsampler_period = simple_strtoul(cp, &cp, 0); printk(KERN_INFO "eapsampler: sampler period set to %d ms\n", eapsampler_period * 10); if(eapsampler_period && !i) // if(eapsampler_period) /* subtle enter US with cli(); bug! */ eapsampler_takesample(); kfree(kbuf); return count; } #ifdef __NEW_LINUX__ /* filenode poll */ static unsigned int eapsampler_select(struct file *flip, poll_table *wait) { struct eapsampler_file *sf = (struct eapsampler_file *) flip->private_data; unsigned int mask = 0; poll_wait(flip, &eapsampler_wq, wait); if(sf->rp != eapsampler_wp) mask |= POLLIN | POLLRDNORM; return mask; } #else /* filenode select */ static int eapsampler_select(struct inode *inode, struct file *flip, int mode, select_table *table) { struct eapsampler_file *sf = (struct eapsampler_file *) flip->private_data; if(mode == SEL_IN) { if(sf->rp != eapsampler_wp) return 1; select_wait(&eapsampler_wq, table); return 0; } /* no write blocking or exceptions */ return 0; } #endif /* file node ops */ static struct file_operations eapsampler_fops = { 0, /* lseek */ (void *)eapsampler_read, /* read */ (void *)eapsampler_write, /* write */ 0, /* readdir */ (void *)eapsampler_select, /* select/poll */ 0, /* ioctl */ 0, /* mmap */ (void *)eapsampler_open, /* open */ #ifdef __NEW_LINUX__ 0, /* flush */ #endif (void *)eapsampler_release, /* release */ 0, /* fsync */ 0, /* fasync */ }; int init_module(void) { int err; printk(KERN_INFO "eapsampler: v2.0, Alan Yates \n"); #ifndef __NEW_LINUX__ register_symtab(0); #endif if((err = check_region(eapsampler_port, 3)) < 0) { printk(KERN_ERR "eapsampler: can't secure IO space 0x%x-0x%x, giving up\n", eapsampler_port, eapsampler_port+3); return err; } request_region(eapsampler_port, 3, eapsampler_name); if((err = register_chrdev(eapsampler_major, eapsampler_name, &eapsampler_fops)) < 0) { printk(KERN_ERR "eapsampler: can't register char dev %d\n", eapsampler_major); release_region(eapsampler_port, 3); return err; } if(!eapsampler_major) eapsampler_major = err; printk(KERN_INFO "eapsampler: device major %d registered\n", eapsampler_major); eas_poweron(); if(eapsampler_period) eapsampler_takesample(); return 0; } void cleanup_module(void) { eas_poweroff(); del_timer(&eapsampler_timer); release_region(eapsampler_port, 3); unregister_chrdev(eapsampler_major, eapsampler_name); printk(KERN_INFO "eapsampler: done, bye\n"); }