# This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.410 -> 1.411 # MAINTAINERS 1.71 -> 1.72 # CREDITS 1.46 -> 1.47 # drivers/usb/Config.in 1.19 -> 1.20 # drivers/usb/Makefile 1.17 -> 1.18 # Documentation/Configure.help 1.103 -> 1.104 # (new) -> 1.1 include/linux/brlvger.h # (new) -> 1.1 drivers/usb/brlvger.c # (new) -> 1.1 Documentation/usb/brlvger.txt # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/05/03 s.doyon@videotron.ca 1.411 # [PATCH] add Tieman Voyager USB Braille display driver # # Adds Tieman Voyager USB Braille display driver to the tree # -------------------------------------------- # diff -Nru a/CREDITS b/CREDITS --- a/CREDITS Fri May 3 14:51:33 2002 +++ b/CREDITS Fri May 3 14:51:33 2002 @@ -635,6 +635,11 @@ S: NN1 3QT S: United Kingdom +N: Stephane Dalton +E: sdalton@videotron.ca +D: Tieman Voyager USB Braille display driver. +S: Québec, Canada + N: Uwe Dannowski E: Uwe.Dannowski@ira.uka.de W: http://i30www.ira.uka.de/~dannowsk/ @@ -740,6 +745,11 @@ E: cort@fsmlabs.com W: http://www.fsmlabs.com/linuxppcbk.html D: PowerPC + +N: Stéphane Doyon +E: s.doyon@videotron.ca +D: Tieman Voyager USB Braille display driver. +S: Québec, Canada N: Oleg Drokin E: green@ccssu.crimea.ua diff -Nru a/Documentation/Configure.help b/Documentation/Configure.help --- a/Documentation/Configure.help Fri May 3 14:51:33 2002 +++ b/Documentation/Configure.help Fri May 3 14:51:33 2002 @@ -13955,6 +13955,17 @@ The module will be called auerswald.o. If you want to compile it as a module, say M here and read . +Tieman Voyager USB Braille display support +CONFIG_USB_BRLVOYAGER + Say Y here if you want to use the Voyager USB Braille display from + Tieman. See for more + information. + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called brlvger.o. If you want to compile it as + a module, say M here and read . + D-Link DSB-R100 FM radio support CONFIG_USB_DSBR Say Y here if you want to connect this type of radio to your diff -Nru a/Documentation/usb/brlvger.txt b/Documentation/usb/brlvger.txt --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/Documentation/usb/brlvger.txt Fri May 3 14:51:33 2002 @@ -0,0 +1,36 @@ +Kernel Driver for the Tieman Voyager Braille Display (USB) + +Authors: +Stéphane Dalton +Stéphane Doyon + +Version 0.8, April 2002 + +The brlvger driver supports a Braille display (aka Braille terminal) +model Voyager from Tieman. + +The driver has been in heavy use for about six months now (as of April +2002) by a very few users (about 3-4), who say it has worked very well +for them. + +We have tested it with a Voyager 44, but it should also support +the Voyager 70. + +This driver implements a character device which allows userspace programs +access to the braille displays raw functions. You still need a userspace +program to perform the screen-review functions and control the +display. Get BRLTTY from http://mielke.cc/brltty/ (version 2.99.8 or +later). It has a Voyager driver which interfaces with this kernel driver. + +The interface is through a character device, major 180, minor 128, called +"brlvger" under devfs. + +Many thanks to the Tieman people: Corand van Strien, Ivar Illing, Daphne +Vogelaar and Ingrid Vogel. They provided us with a Braille display (as +well as programming information) so that we could write this driver. They +replaced the display when it broke and they answered our technical +questions. It is very motivating when companies take an interest in such +projects and are so supportive. + +Thanks to Andor Demarteau who got this whole +project started and beta-tested all our early buggy attempts. diff -Nru a/MAINTAINERS b/MAINTAINERS --- a/MAINTAINERS Fri May 3 14:51:33 2002 +++ b/MAINTAINERS Fri May 3 14:51:33 2002 @@ -1530,6 +1530,13 @@ M: hch@infradead.org S: Maintained +TIEMAN VOYAGER USB BRAILLE DISPLAY DRIVER +P: Stephane Dalton +M: sdalton@videotron.ca +P: Stéphane Doyon +M: s.doyon@videotron.ca +S: Maintained + TLAN NETWORK DRIVER P: Samuel Chessman M: chessman@tux.org diff -Nru a/drivers/usb/Config.in b/drivers/usb/Config.in --- a/drivers/usb/Config.in Fri May 3 14:51:33 2002 +++ b/drivers/usb/Config.in Fri May 3 14:51:33 2002 @@ -101,5 +101,6 @@ comment 'USB Miscellaneous drivers' dep_tristate ' USB Diamond Rio500 support (EXPERIMENTAL)' CONFIG_USB_RIO500 $CONFIG_USB $CONFIG_EXPERIMENTAL dep_tristate ' USB Auerswald ISDN support (EXPERIMENTAL)' CONFIG_USB_AUERSWALD $CONFIG_USB $CONFIG_EXPERIMENTAL + dep_tristate ' Tieman Voyager USB Braille display support (EXPERIMENTAL)' CONFIG_USB_BRLVGER $CONFIG_USB $CONFIG_EXPERIMENTAL fi endmenu diff -Nru a/drivers/usb/Makefile b/drivers/usb/Makefile --- a/drivers/usb/Makefile Fri May 3 14:51:33 2002 +++ b/drivers/usb/Makefile Fri May 3 14:51:33 2002 @@ -92,6 +92,7 @@ obj-$(CONFIG_USB_BLUETOOTH) += bluetooth.o obj-$(CONFIG_USB_USBNET) += usbnet.o obj-$(CONFIG_USB_AUERSWALD) += auerswald.o +obj-$(CONFIG_USB_BRLVGER) += brlvger.o # Object files in subdirectories mod-subdirs := serial hcd diff -Nru a/drivers/usb/brlvger.c b/drivers/usb/brlvger.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/brlvger.c Fri May 3 14:51:33 2002 @@ -0,0 +1,1034 @@ +/* + * 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(args...) \ + ({ printk(KERN_DEBUG "Voyager: " __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; + int rs, 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 diff -Nru a/include/linux/brlvger.h b/include/linux/brlvger.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/include/linux/brlvger.h Fri May 3 14:51:33 2002 @@ -0,0 +1,57 @@ +/* + * 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 + */ + +#ifndef _LINUX_BRLVGER_H +#define _LINUX_BRLVGER_H + +/* Ioctl request codes */ +#define BRLVGER_GET_INFO 0 +#define BRLVGER_DISPLAY_ON 2 +#define BRLVGER_DISPLAY_OFF 3 +#define BRLVGER_BUZZ 4 + +/* Number of supported devices, and range of covered minors */ +#define MAX_NR_BRLVGER_DEVS 4 + +/* Base minor for the char devices */ +#define BRLVGER_MINOR 128 + +/* Size of some fields */ +#define BRLVGER_HWVER_SIZE 2 +#define BRLVGER_FWVER_SIZE 200 /* arbitrary, a long string */ +#define BRLVGER_SERIAL_BIN_SIZE 8 +#define BRLVGER_SERIAL_SIZE ((2*BRLVGER_SERIAL_BIN_SIZE)+1) + +struct brlvger_info { + __u8 driver_version[12]; + __u8 driver_banner[200]; + + __u32 display_length; + /* All other char[] fields are strings except this one. + Hardware version: first byte is major, second byte is minor. */ + __u8 hwver[BRLVGER_HWVER_SIZE]; + __u8 fwver[BRLVGER_FWVER_SIZE]; + __u8 serialnum[BRLVGER_SERIAL_SIZE]; +}; + +#endif