/* * Tieman Voyager braille display USB driver. * * Copyright 2001-2002 Stephane Dalton * and Stéphane Doyon * Maintained by Stéphane Doyon . */ /* * 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 */ /* History: * 0.8 April 2002: Integration into the kernel tree. * 0.7 October 2001: First public release as a module, distributed with * the BRLTTY package (beta versions around 2.99y). */ #define DRIVER_VERSION "v0.8" #define DATE "April 2002" #define DRIVER_AUTHOR \ "Stephane Dalton " \ "and Stéphane Doyon " #define DRIVER_DESC "Tieman Voyager braille display USB driver for Linux 2.4" #define DRIVER_SHORTDESC "Voyager" #define BANNER \ KERN_INFO DRIVER_SHORTDESC " " DRIVER_VERSION " (" DATE ")\n" \ KERN_INFO " by " DRIVER_AUTHOR "\n" static const char longbanner[] = { DRIVER_DESC ", " DRIVER_VERSION " (" DATE "), by " DRIVER_AUTHOR }; #include #include #include #include #include #include #include #include #include #include MODULE_AUTHOR( DRIVER_AUTHOR ); MODULE_DESCRIPTION( DRIVER_DESC ); MODULE_LICENSE("GPL"); /* Module parameters */ static int debug = 1; MODULE_PARM(debug, "i"); MODULE_PARM_DESC(debug, "Debug level, 0-3"); static int write_repeats = 2; MODULE_PARM(write_repeats, "i"); MODULE_PARM_DESC(write_repeats, "Hack: repetitions for command to " "display braille pattern"); /* to get rid of weird extra dots (perhaps only on early hardware versions?) */ static int stall_tries = 3; MODULE_PARM(stall_tries, "i"); MODULE_PARM_DESC(stall_tries, "Hack: retransmits of stalled USB " "control messages"); /* broken early hardware versions? */ #define BRLVGER_RAW_VOLTAGE 89 /* from 0->300V to 255->200V, we are told 265V is normal operating voltage, but we don't know the scale. Assuming it is linear. */ static int raw_voltage = BRLVGER_RAW_VOLTAGE; MODULE_PARM(raw_voltage, "i"); MODULE_PARM_DESC(raw_voltage, "Parameter for the call to SET_DISPLAY_VOLTAGE"); /* protocol and display type defines */ #define MAX_BRLVGER_CELLS 72 #define MAX_INTERRUPT_DATA 8 /* control message request types */ #define BRLVGER_READ_REQ 0xC2 #define BRLVGER_WRITE_REQ 0x42 /* control message request codes */ #define BRLVGER_SET_DISPLAY_ON 0 #define BRLVGER_SET_DISPLAY_VOLTAGE 1 #define BRLVGER_GET_SERIAL 3 #define BRLVGER_GET_HWVERSION 4 #define BRLVGER_GET_FWVERSION 5 #define BRLVGER_GET_LENGTH 6 #define BRLVGER_SEND_BRAILLE 7 #define BRLVGER_BEEP 9 #if 0 /* not used and not sure they're working */ #define BRLVGER_GET_DISPLAY_VOLTAGE 2 #define BRLVGER_GET_CURRENT 8 #endif /* Prototypes */ static void *brlvger_probe (struct usb_device *dev, unsigned ifnum, const struct usb_device_id *id); static void brlvger_disconnect(struct usb_device *dev, void *ptr); static int brlvger_open(struct inode *inode, struct file *file); static int brlvger_release(struct inode *inode, struct file *file); static ssize_t brlvger_write(struct file *file, const char *buffer, size_t count, loff_t *pos); static ssize_t brlvger_read(struct file *file, char *buffer, size_t count, loff_t *unused_pos); static int brlvger_ioctl(struct inode *inode, struct file *file, unsigned cmd, unsigned long arg); static unsigned brlvger_poll(struct file *file, poll_table *wait); static loff_t brlvger_llseek(struct file * file, loff_t offset, int orig); static void intr_callback(struct urb *urb); struct brlvger_priv; static int brlvger_get_hw_version(struct brlvger_priv *priv, unsigned char *verbuf); static int brlvger_get_fw_version(struct brlvger_priv *priv, unsigned char *buf); static int brlvger_get_serial(struct brlvger_priv *priv, unsigned char *buf); static int brlvger_get_display_length(struct brlvger_priv *priv); static int brlvger_set_display_on_off(struct brlvger_priv *priv, __u16 on); static int brlvger_beep(struct brlvger_priv *priv, __u16 duration); static int brlvger_set_display_voltage(struct brlvger_priv *priv, __u16 voltage); static int mycontrolmsg(const char *funcname, struct brlvger_priv *priv, unsigned pipe_dir, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size); #define controlmsg(priv,pipe_dir,a,b,c,d,e,f) \ mycontrolmsg(__FUNCTION__, priv, pipe_dir, \ a,b,c,d,e,f) #define sndcontrolmsg(priv,a,b,c,d,e,f) \ controlmsg(priv, 0, a,b,c,d,e,f) #define rcvcontrolmsg(priv,a,b,c,d,e,f) \ controlmsg(priv, USB_DIR_IN, a,b,c,d,e,f) extern devfs_handle_t usb_devfs_handle; /* /dev/usb dir. */ /* ----------------------------------------------------------------------- */ /* Data */ /* key event queue size */ #define MAX_INTERRUPT_BUFFER 10 /* private state */ struct brlvger_priv { struct usb_device *dev; /* USB device handle */ struct usb_endpoint_descriptor *in_interrupt; struct urb *intr_urb; devfs_handle_t devfs; int subminor; /* which minor dev #? */ unsigned char hwver[BRLVGER_HWVER_SIZE]; /* hardware version */ unsigned char fwver[BRLVGER_FWVER_SIZE]; /* firmware version */ unsigned char serialnum[BRLVGER_SERIAL_SIZE]; int llength; /* logical length */ int plength; /* physical length */ __u8 obuf[MAX_BRLVGER_CELLS]; __u8 intr_buff[MAX_INTERRUPT_DATA]; __u8 event_queue[MAX_INTERRUPT_BUFFER][MAX_INTERRUPT_DATA]; atomic_t intr_idx, read_idx; spinlock_t intr_idx_lock; /* protects intr_idx */ wait_queue_head_t read_wait; int opened; struct semaphore open_sem; /* protects ->opened */ struct semaphore dev_sem; /* protects ->dev */ }; /* Globals */ /* Table of connected devices, a different minor for each. */ static struct brlvger_priv *display_table[ MAX_NR_BRLVGER_DEVS ]; /* Mutex for the operation of removing a device from display_table */ static DECLARE_MUTEX(disconnect_sem); /* For blocking open */ static DECLARE_WAIT_QUEUE_HEAD(open_wait); /* Some print macros */ #ifdef dbg #undef dbg #endif #ifdef info #undef info #endif #ifdef err #undef err #endif #define info(args...) \ ({ printk(KERN_INFO "Voyager: " args); \ printk("\n"); }) #define err(args...) \ ({ printk(KERN_ERR "Voyager: " args); \ printk("\n"); }) #define dbgprint(fmt, args...) \ ({ printk(KERN_DEBUG "Voyager: %s: " fmt, __FUNCTION__ , ##args); \ printk("\n"); }) #define dbg(args...) \ ({ if(debug >= 1) dbgprint(args); }) #define dbg2(args...) \ ({ if(debug >= 2) dbgprint(args); }) #define dbg3(args...) \ ({ if(debug >= 3) dbgprint(args); }) /* ----------------------------------------------------------------------- */ /* Driver registration */ static struct usb_device_id brlvger_ids [] = { { USB_DEVICE(0x0798, 0x0001) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, brlvger_ids); static struct file_operations brlvger_fops = { owner: THIS_MODULE, llseek: brlvger_llseek, read: brlvger_read, write: brlvger_write, ioctl: brlvger_ioctl, open: brlvger_open, release: brlvger_release, poll: brlvger_poll, }; static struct usb_driver brlvger_driver = { name: "brlvger", probe: brlvger_probe, disconnect: brlvger_disconnect, fops: &brlvger_fops, minor: BRLVGER_MINOR, id_table: brlvger_ids, }; static int __init brlvger_init (void) { printk(BANNER); if(stall_tries < 1 || write_repeats < 1) return -EINVAL; memset(display_table, 0, sizeof(display_table)); if (usb_register(&brlvger_driver)) { err("USB registration failed"); return -ENOSYS; } return 0; } static void __exit brlvger_cleanup (void) { usb_deregister (&brlvger_driver); dbg("Driver unregistered"); } module_init (brlvger_init); module_exit (brlvger_cleanup); /* ----------------------------------------------------------------------- */ /* Probe and disconnect functions */ static void * brlvger_probe (struct usb_device *dev, unsigned ifnum, const struct usb_device_id *id) { struct brlvger_priv *priv = NULL; int i; struct usb_endpoint_descriptor *endpoint; struct usb_interface_descriptor *actifsettings; /* protects against reentrance: once we've found a free slot we reserve it.*/ static DECLARE_MUTEX(reserve_sem); char devfs_name[16]; actifsettings = dev->actconfig->interface->altsetting; if( dev->descriptor.bNumConfigurations != 1 || dev->config->bNumInterfaces != 1 || actifsettings->bNumEndpoints != 1 ) { err ("Bogus braille display config info"); return NULL; } endpoint = actifsettings->endpoint; if (!(endpoint->bEndpointAddress & 0x80) || ((endpoint->bmAttributes & 3) != 0x03)) { err ("Bogus braille display config info, wrong endpoints"); return NULL; } down(&reserve_sem); for( i = 0; i < MAX_NR_BRLVGER_DEVS; i++ ) if( display_table[i] == NULL ) break; if( i == MAX_NR_BRLVGER_DEVS ) { err( "This driver cannot handle more than %d " "braille displays", MAX_NR_BRLVGER_DEVS); goto error; } if( !(priv = kmalloc (sizeof *priv, GFP_KERNEL)) ){ err("No more memory"); goto error; } memset(priv, 0, sizeof(*priv)); atomic_set(&priv->intr_idx, 0); atomic_set(&priv->read_idx, MAX_INTERRUPT_BUFFER-1); spin_lock_init(&priv->intr_idx_lock); init_waitqueue_head(&priv->read_wait); /* opened is memset'ed to 0 */ init_MUTEX(&priv->open_sem); init_MUTEX(&priv->dev_sem); priv->subminor = i; /* we found a interrupt in endpoint */ priv->in_interrupt = endpoint; priv->dev = dev; if(brlvger_get_hw_version(priv, priv->hwver) <0) { err("Unable to get hardware version"); goto error; } dbg("Hw ver %d.%d", priv->hwver[0], priv->hwver[1]); if(brlvger_get_fw_version(priv, priv->fwver) <0) { err("Unable to get firmware version"); goto error; } dbg("Fw ver: %s", priv->fwver); if(brlvger_get_serial(priv, priv->serialnum) <0) { err("Unable to get serial number"); goto error; } dbg("Serial number: %s", priv->serialnum); if( (priv->llength = brlvger_get_display_length(priv)) <0 ){ err("Unable to get display length"); goto error; } switch(priv->llength) { case 48: priv->plength = 44; break; case 72: priv->plength = 70; break; default: err("Unsupported display length: %d", priv->llength); goto error; }; dbg("Display length: %d", priv->plength); sprintf(devfs_name, "brlvger%d", priv->subminor); priv->devfs = devfs_register(usb_devfs_handle, devfs_name, DEVFS_FL_DEFAULT, USB_MAJOR, BRLVGER_MINOR+priv->subminor, S_IFCHR |S_IRUSR|S_IWUSR |S_IRGRP|S_IWGRP, &brlvger_fops, NULL); if (!priv->devfs) { #ifdef CONFIG_DEVFS_FS err("devfs node registration failed"); #endif } display_table[i] = priv; info( "Braille display %d is device major %d minor %d", i, USB_MAJOR, BRLVGER_MINOR + i); /* Tell anyone waiting on a blocking open */ wake_up_interruptible(&open_wait); goto out; error: if(priv) { kfree( priv ); priv = NULL; } out: up(&reserve_sem); return priv; } static void brlvger_disconnect(struct usb_device *dev, void *ptr) { struct brlvger_priv *priv = (struct brlvger_priv *)ptr; int r; if(priv){ info("Display %d disconnecting", priv->subminor); devfs_unregister(priv->devfs); down(&disconnect_sem); display_table[priv->subminor] = NULL; up(&disconnect_sem); down(&priv->open_sem); down(&priv->dev_sem); if(priv->opened) { /* Disable interrupts */ if((r = usb_unlink_urb(priv->intr_urb)) <0) err("usb_unlink_urb returns %d", r); usb_free_urb(priv->intr_urb); /* mark device as dead and prevent control messages to it */ priv->dev = NULL; /* Tell anyone hung up on a read that it won't be coming */ wake_up_interruptible(&priv->read_wait); up(&priv->dev_sem); up(&priv->open_sem); }else /* no corresponding up()s */ kfree(priv); } } /* ----------------------------------------------------------------------- */ /* fops implementation */ static int brlvger_open(struct inode *inode, struct file *file) { int devnum = MINOR (inode->i_rdev); struct brlvger_priv *priv; int n, ret; if (devnum < BRLVGER_MINOR || devnum >= (BRLVGER_MINOR + MAX_NR_BRLVGER_DEVS)) return -ENXIO; n = devnum - BRLVGER_MINOR; MOD_INC_USE_COUNT; do { down(&disconnect_sem); priv = display_table[n]; if(!priv) { up(&disconnect_sem); if (file->f_flags & O_NONBLOCK) { dbg3("Failing non-blocking open: " "device %d not connected", n); MOD_DEC_USE_COUNT; return -EAGAIN; } /* Blocking open. One global wait queue will suffice. We wait until a device for the selected minor is connected. */ dbg2("Waiting for device %d to be connected", n); ret = wait_event_interruptible(open_wait, display_table[n] != NULL); if(ret) { dbg2("Interrupted wait for device %d", n); MOD_DEC_USE_COUNT; return ret; } } } while(!priv); /* We grabbed an existing device. */ if(down_interruptible(&priv->open_sem)) return -ERESTARTSYS; up(&disconnect_sem); /* Only one process can open each device, no sharing. */ ret = -EBUSY; if(priv->opened) goto error; dbg("Opening display %d", priv->subminor); /* Setup interrupt handler for receiving key input */ priv->intr_urb = usb_alloc_urb(0); if(!priv->intr_urb) { err("Unable to allocate URB"); goto error; } FILL_INT_URB( priv->intr_urb, priv->dev, usb_rcvintpipe(priv->dev, priv->in_interrupt->bEndpointAddress), priv->intr_buff, sizeof(priv->intr_buff), intr_callback, priv, priv->in_interrupt->bInterval); if((ret = usb_submit_urb(priv->intr_urb)) <0){ err("Error %d while submitting URB", ret); goto error; } /* Set voltage */ if(brlvger_set_display_voltage(priv, raw_voltage) <0) { err("Unable to set voltage"); goto error; } /* Turn display on */ if((ret = brlvger_set_display_on_off(priv, 1)) <0) { err("Error %d while turning display on", ret); goto error; } /* Mark as opened, so disconnect cannot free priv. */ priv->opened = 1; file->private_data = priv; ret = 0; goto out; error: MOD_DEC_USE_COUNT; out: up(&priv->open_sem); return ret; } static int brlvger_release(struct inode *inode, struct file *file) { struct brlvger_priv *priv = file->private_data; int r; /* Turn display off. Safe even if disconnected. */ brlvger_set_display_on_off(priv, 0); /* mutex with disconnect and with open */ down(&priv->open_sem); if(!priv->dev) { dbg("Releasing disconnected device %d", priv->subminor); /* no up(&priv->open_sem) */ kfree(priv); }else{ dbg("Closing display %d", priv->subminor); /* Disable interrupts */ if((r = usb_unlink_urb(priv->intr_urb)) <0) err("usb_unlink_urb returns %d", r); usb_free_urb(priv->intr_urb); priv->opened = 0; up(&priv->open_sem); } MOD_DEC_USE_COUNT; return 0; } static ssize_t brlvger_write(struct file *file, const char *buffer, size_t count, loff_t *pos) { struct brlvger_priv *priv = file->private_data; char buf[MAX_BRLVGER_CELLS]; int ret; size_t rs; loff_t off; __u16 written; if(!priv->dev) return -ENOLINK; off = *pos; if(off > priv->plength) return -ESPIPE;; rs = priv->plength - off; if(count > rs) count = rs; written = count; if (copy_from_user (buf, buffer, count ) ) return -EFAULT; memset(priv->obuf, 0xaa, sizeof(priv->obuf)); /* Firmware supports multiples of 8cells, so some cells are absent and for some reason there actually are holes! euurkkk! */ if( priv->plength == 44 ) { /* Two ghost cells at the beginning of the display, plus two more after the sixth physical cell. */ if(off > 5) { off +=4; memcpy(priv->obuf, buf, count); }else{ int firstpart = 6 - off; #ifdef WRITE_DEBUG dbg3("off: %d, rs: %d, count: %d, firstpart: %d", off, rs, count, firstpart); #endif firstpart = (firstpart < count) ? firstpart : count; #ifdef WRITE_DEBUG dbg3("off: %d", off); dbg3("firstpart: %d", firstpart); #endif memcpy(priv->obuf, buf, firstpart); if(firstpart != count) { int secondpart = count - firstpart; #ifdef WRITE_DEBUG dbg3("secondpart: %d", secondpart); #endif memcpy(priv->obuf+(firstpart+2), buf+firstpart, secondpart); written +=2; } off +=2; #ifdef WRITE_DEBUG dbg3("off: %d, rs: %d, count: %d, firstpart: %d, " "written: %d", off, rs, count, firstpart, written); #endif } }else{ /* Two ghost cells at the beginningg of the display. */ memcpy(priv->obuf, buf, count); off += 2; } { int repeat = write_repeats; /* Dirty hack: sometimes some of the dots are wrong and somehow right themselves if the command is repeated. */ while(repeat--) { ret = sndcontrolmsg(priv, BRLVGER_SEND_BRAILLE, BRLVGER_WRITE_REQ, 0, off, priv->obuf, written); if(ret <0) return ret; } } return count; } static int read_index(struct brlvger_priv *priv) { int intr_idx, read_idx; read_idx = atomic_read(&priv->read_idx); read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx; intr_idx = atomic_read(&priv->intr_idx); return(read_idx == intr_idx ? -1 : read_idx); } static ssize_t brlvger_read(struct file *file, char *buffer, size_t count, loff_t *unused_pos) { struct brlvger_priv *priv = file->private_data; int read_idx; if(count != MAX_INTERRUPT_DATA) return -EINVAL; if(!priv->dev) return -ENOLINK; if((read_idx = read_index(priv)) == -1) { /* queue empty */ if (file->f_flags & O_NONBLOCK) return -EAGAIN; else{ int r = wait_event_interruptible(priv->read_wait, (!priv->dev || (read_idx = read_index(priv)) != -1)); if(!priv->dev) return -ENOLINK; if(r) return r; if(read_idx == -1) /* should not happen */ return 0; } } if (copy_to_user (buffer, priv->event_queue[read_idx], count) ) return( -EFAULT); atomic_set(&priv->read_idx, read_idx); /* Multiple opens are not allowed. Yet on SMP, two processes could read at the same time (on a shared file descriptor); then it is not deterministic whether or not they will get duplicates of a key event. */ return MAX_INTERRUPT_DATA; } static int brlvger_ioctl(struct inode *inode, struct file *file, unsigned cmd, unsigned long arg) { struct brlvger_priv *priv = file->private_data; if(!priv->dev) return -ENOLINK; switch(cmd) { case BRLVGER_GET_INFO: { struct brlvger_info vi; strncpy(vi.driver_version, DRIVER_VERSION, sizeof(vi.driver_version)); vi.driver_version[sizeof(vi.driver_version)-1] = 0; strncpy(vi.driver_banner, longbanner, sizeof(vi.driver_banner)); vi.driver_banner[sizeof(vi.driver_banner)-1] = 0; vi.display_length = priv->plength; memcpy(&vi.hwver, priv->hwver, BRLVGER_HWVER_SIZE); memcpy(&vi.fwver, priv->fwver, BRLVGER_FWVER_SIZE); memcpy(&vi.serialnum, priv->serialnum, BRLVGER_SERIAL_SIZE); if(copy_to_user((void *)arg, &vi, sizeof(vi))) return -EFAULT; return 0; } case BRLVGER_DISPLAY_ON: return brlvger_set_display_on_off(priv, 1); case BRLVGER_DISPLAY_OFF: return brlvger_set_display_on_off(priv, 0); case BRLVGER_BUZZ: { __u16 duration; if(get_user(duration, (__u16 *)arg)) return -EFAULT; return brlvger_beep(priv, duration); } #if 0 /* Underlying commands don't seem to work for some reason; not clear if we'd want to export these anyway. */ case BRLVGER_SET_VOLTAGE: { __u16 voltage; if(get_user(voltage, (__u16 *)arg)) return -EFAULT; return brlvger_set_display_voltage(priv, voltage); } case BRLVGER_GET_VOLTAGE: { __u8 voltage; int r = brlvger_get_display_voltage(priv); if(r <0) return r; voltage = r; if(put_user(voltage, (__u8 *)arg)) return -EFAULT; return 0; } #endif default: return -EINVAL; }; } static loff_t brlvger_llseek(struct file *file, loff_t offset, int orig) { struct brlvger_priv *priv = file->private_data; if(!priv->dev) return -ENOLINK; switch (orig) { case 0: /* nothing to do */ break; case 1: offset +=file->f_pos; break; case 2: offset += priv->plength; default: return -EINVAL; } if((offset >= priv->plength) || (offset < 0)) return -EINVAL; return (file->f_pos = offset); } static unsigned brlvger_poll(struct file *file, poll_table *wait) { struct brlvger_priv *priv = file->private_data; if(!priv->dev) return POLLERR | POLLHUP; poll_wait(file, &priv->read_wait, wait); if(!priv->dev) return POLLERR | POLLHUP; if(read_index(priv) != -1) return POLLIN | POLLRDNORM; return 0; } static void intr_callback(struct urb *urb) { struct brlvger_priv *priv = urb->context; int intr_idx, read_idx; if( urb->status ) { if(urb->status == -ETIMEDOUT) dbg2("Status -ETIMEDOUT, " "probably disconnected"); else if(urb->status != -ENOENT) err("Status: %d", urb->status); return; } read_idx = atomic_read(&priv->read_idx); spin_lock(&priv->intr_idx_lock); intr_idx = atomic_read(&priv->intr_idx); if(read_idx == intr_idx) { dbg2("Queue full, dropping braille display input"); spin_unlock(&priv->intr_idx_lock); return; /* queue full */ } memcpy(priv->event_queue[intr_idx], urb->transfer_buffer, MAX_INTERRUPT_DATA); intr_idx = (++intr_idx == MAX_INTERRUPT_BUFFER)? 0 : intr_idx; atomic_set(&priv->intr_idx, intr_idx); spin_unlock(&priv->intr_idx_lock); wake_up_interruptible(&priv->read_wait); } /* ----------------------------------------------------------------------- */ /* Hardware access functions */ static int mycontrolmsg(const char *funcname, struct brlvger_priv *priv, unsigned pipe_dir, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size) { int ret=0, tries = stall_tries; /* Make sure the device was not disconnected */ if(down_interruptible(&priv->dev_sem)) return -ERESTARTSYS; if(!priv->dev) { up(&priv->dev_sem); return -ENOLINK; } /* Dirty hack for retransmission: stalls and fails all the time without this on the hardware we tested. */ while(tries--) { ret = usb_control_msg(priv->dev, usb_sndctrlpipe(priv->dev,0) |pipe_dir, request, requesttype, value, index, data, size, HZ); if(ret != -EPIPE) break; dbg2("Stalled, remaining %d tries", tries); } up(&priv->dev_sem); if(ret <0) { err("%s: usb_control_msg returns %d", funcname, ret); return -EIO; } return 0; } static int brlvger_get_hw_version(struct brlvger_priv *priv, unsigned char *verbuf) { return rcvcontrolmsg(priv, BRLVGER_GET_HWVERSION, BRLVGER_READ_REQ, 0, 0, verbuf, BRLVGER_HWVER_SIZE); /* verbuf should be 2 bytes */ } static int brlvger_get_fw_version(struct brlvger_priv *priv, unsigned char *buf) { unsigned char rawbuf[(BRLVGER_FWVER_SIZE-1)*2+2]; int i, len; int r = rcvcontrolmsg(priv, BRLVGER_GET_FWVERSION, BRLVGER_READ_REQ, 0, 0, rawbuf, sizeof(rawbuf)); if(r<0) return r; /* If I guess correctly: succession of 16bit words, the string is formed of the first byte of each of these words. First byte in buffer indicates total length of data; not sure what second byte is for. */ len = rawbuf[0]-2; if(len<0) len = 0; else if(len+1 > BRLVGER_FWVER_SIZE) len = BRLVGER_FWVER_SIZE-1; for(i=0; i9) ? (n)+'A' : (n)+'0') buf[2*i] = NUM_TO_HEX(rawserial[i] >>4); buf[2*i+1] = NUM_TO_HEX(rawserial[i] &0xf); } buf[2*i] = 0; return 0; } static int brlvger_get_display_length(struct brlvger_priv *priv) { unsigned char data[2]; int ret = rcvcontrolmsg(priv, BRLVGER_GET_LENGTH, BRLVGER_READ_REQ, 0, 0, data, 2); if(ret<0) return ret; return data[1]; } static int brlvger_beep(struct brlvger_priv *priv, __u16 duration) { return sndcontrolmsg(priv, BRLVGER_BEEP, BRLVGER_WRITE_REQ, duration, 0, NULL, 0); } static int brlvger_set_display_on_off(struct brlvger_priv *priv, __u16 on) { dbg2("Turning display %s", ((on) ? "on" : "off")); return sndcontrolmsg(priv, BRLVGER_SET_DISPLAY_ON, BRLVGER_WRITE_REQ, on, 0, NULL, 0); } static int brlvger_set_display_voltage(struct brlvger_priv *priv, __u16 voltage) { dbg("SET_DISPLAY_VOLTAGE to %u", voltage); return sndcontrolmsg(priv, BRLVGER_SET_DISPLAY_VOLTAGE, BRLVGER_WRITE_REQ, voltage, 0, NULL, 0); } #if 0 /* Had problems testing these commands. Not particularly useful anyway.*/ static int brlvger_get_display_voltage(struct brlvger_priv *priv) { __u8 voltage = 0; int ret = rcvcontrolmsg(priv, BRLVGER_GET_DISPLAY_VOLTAGE, BRLVGER_READ_REQ, 0, 0, &voltage, 1); if(ret<0) return ret; return voltage; } static int brlvger_get_current(struct brlvger_priv *priv) { unsigned char data; int ret = rcvcontrolmsg(priv, BRLVGER_GET_CURRENT, BRLVGER_READ_REQ, 0, 0, &data, 1); if(ret<0) return ret; return data; } #endif