Commit f5864419 authored by Stéphane Doyon's avatar Stéphane Doyon Committed by Greg Kroah-Hartman

[PATCH] added Tieman Voyager USB Braille driver

 added Tieman Voyager USB Braille driver
parent 0ba1006e
...@@ -637,6 +637,11 @@ S: Northampton ...@@ -637,6 +637,11 @@ S: Northampton
S: NN1 3QT S: NN1 3QT
S: United Kingdom S: United Kingdom
N: Stephane Dalton
E: sdalton@videotron.ca
D: Tieman Voyager USB Braille display driver.
S: Qubec, Canada
N: Uwe Dannowski N: Uwe Dannowski
E: Uwe.Dannowski@ira.uka.de E: Uwe.Dannowski@ira.uka.de
W: http://i30www.ira.uka.de/~dannowsk/ W: http://i30www.ira.uka.de/~dannowsk/
...@@ -743,6 +748,11 @@ E: cort@fsmlabs.com ...@@ -743,6 +748,11 @@ E: cort@fsmlabs.com
W: http://www.fsmlabs.com/linuxppcbk.html W: http://www.fsmlabs.com/linuxppcbk.html
D: PowerPC D: PowerPC
N: Stphane Doyon
E: s.doyon@videotron.ca
D: Tieman Voyager USB Braille display driver.
S: Qubec, Canada
N: Oleg Drokin N: Oleg Drokin
E: green@ccssu.crimea.ua E: green@ccssu.crimea.ua
W: http://www.ccssu.crimea.ua/~green W: http://www.ccssu.crimea.ua/~green
......
Kernel Driver for the Tieman Voyager Braille Display (USB)
Authors:
Stéphane Dalton <sdalton@videotron.ca>
Stéphane Doyon <s.doyon@videotron.ca>
Version 0.8, April 17, 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
17th 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 <ademarte@students.cs.uu.nl> who got this whole
project started and beta-tested all our early buggy attempts.
...@@ -1514,6 +1514,13 @@ P: Julien Blache ...@@ -1514,6 +1514,13 @@ P: Julien Blache
M: jb@technologeek.org M: jb@technologeek.org
S: Maintained S: Maintained
TIEMAN VOYAGER USB BRAILLE DISPLAY DRIVER
P: Stephane Dalton
M: sdalton@videotron.ca
P: Stphane Doyon
M: s.doyon@videotron.ca
S: Maintained
TLAN NETWORK DRIVER TLAN NETWORK DRIVER
P: Torben Mathiasen P: Torben Mathiasen
M: torben.mathiasen@compaq.com M: torben.mathiasen@compaq.com
......
...@@ -32,6 +32,7 @@ if [ "$CONFIG_USB" = "y" -o "$CONFIG_USB" = "m" ]; then ...@@ -32,6 +32,7 @@ if [ "$CONFIG_USB" = "y" -o "$CONFIG_USB" = "m" ]; then
dep_tristate ' Texas Instruments Graph Link USB (aka SilverLink) cable support' CONFIG_USB_TIGL $CONFIG_USB dep_tristate ' Texas Instruments Graph Link USB (aka SilverLink) cable support' CONFIG_USB_TIGL $CONFIG_USB
dep_tristate ' USB Auerswald ISDN support (EXPERIMENTAL)' CONFIG_USB_AUERSWALD $CONFIG_USB $CONFIG_EXPERIMENTAL dep_tristate ' USB Auerswald ISDN support (EXPERIMENTAL)' CONFIG_USB_AUERSWALD $CONFIG_USB $CONFIG_EXPERIMENTAL
dep_tristate ' USB Diamond Rio500 support (EXPERIMENTAL)' CONFIG_USB_RIO500 $CONFIG_USB $CONFIG_EXPERIMENTAL dep_tristate ' USB Diamond Rio500 support (EXPERIMENTAL)' CONFIG_USB_RIO500 $CONFIG_USB $CONFIG_EXPERIMENTAL
dep_tristate ' Tieman Voyager USB Braille display support (EXPERIMENTAL)' CONFIG_USB_BRLVGER $CONFIG_USB $CONFIG_EXPERIMENTAL
fi fi
endmenu endmenu
...@@ -53,6 +53,7 @@ subdir-$(CONFIG_USB_SCANNER) += image ...@@ -53,6 +53,7 @@ subdir-$(CONFIG_USB_SCANNER) += image
subdir-$(CONFIG_USB_SERIAL) += serial subdir-$(CONFIG_USB_SERIAL) += serial
subdir-$(CONFIG_USB_AUERSWALD) += misc subdir-$(CONFIG_USB_AUERSWALD) += misc
subdir-$(CONFIG_USB_BRLVGER) += misc
subdir-$(CONFIG_USB_EMI26) += misc subdir-$(CONFIG_USB_EMI26) += misc
subdir-$(CONFIG_USB_RIO500) += misc subdir-$(CONFIG_USB_RIO500) += misc
subdir-$(CONFIG_USB_TIGL) += misc subdir-$(CONFIG_USB_TIGL) += misc
......
...@@ -7,6 +7,16 @@ CONFIG_USB_AUERSWALD ...@@ -7,6 +7,16 @@ CONFIG_USB_AUERSWALD
The module will be called auerswald.o. If you want to compile it as The module will be called auerswald.o. If you want to compile it as
a module, say M here and read <file:Documentation/modules.txt>. a module, say M here and read <file:Documentation/modules.txt>.
CONFIG_USB_BRLVOYAGER
Say Y here if you want to use the Voyager USB Braille display from
Tieman. See <file:Documentation/usb/brlvger.txt> 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 <file:Documentation/modules.txt>.
CONFIG_USB_EMI26 CONFIG_USB_EMI26
This driver loads firmware to Emagic EMI 2|6 low latency USB This driver loads firmware to Emagic EMI 2|6 low latency USB
Audio interface. Audio interface.
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
O_TARGET := misc.o O_TARGET := misc.o
obj-$(CONFIG_USB_AUERSWALD) += auerswald.o obj-$(CONFIG_USB_AUERSWALD) += auerswald.o
obj-$(CONFIG_USB_BRLVGER) += brlvger.o
obj-$(CONFIG_USB_EMI26) += emi26.o obj-$(CONFIG_USB_EMI26) += emi26.o
obj-$(CONFIG_USB_RIO500) += rio500.o obj-$(CONFIG_USB_RIO500) += rio500.o
obj-$(CONFIG_USB_TIGL) += tiglusb.o obj-$(CONFIG_USB_TIGL) += tiglusb.o
......
/*
* Tieman Voyager braille display USB driver.
*
* Copyright 2001-2002 Stephane Dalton <sdalton@videotron.ca>
* and Stphane Doyon <s.doyon@videotron.ca>
* Maintained by Stphane Doyon <s.doyon@videotron.ca>.
*/
/*
* 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 <sdalton@videotron.ca> " \
"and Stphane Doyon <s.doyon@videotron.ca>"
#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 <linux/module.h>
#include <linux/usb.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/poll.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/brlvger.h>
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 =
{
owner: THIS_MODULE,
name: "brlvger",
probe: brlvger_probe,
disconnect: brlvger_disconnect,
fops: &brlvger_fops,
minor: BRLVGER_MINOR,
num_minors: MAX_NR_BRLVGER_DEVS,
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, GFP_KERNEL);
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, GFP_KERNEL)) <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; i<len; i++)
buf[i] = rawbuf[2+2*i];
buf[i] = 0;
return 0;
}
static int
brlvger_get_serial(struct brlvger_priv *priv, unsigned char *buf)
{
unsigned char rawserial[BRLVGER_SERIAL_BIN_SIZE];
int i;
int r = rcvcontrolmsg(priv,
BRLVGER_GET_SERIAL, BRLVGER_READ_REQ, 0,
0, rawserial, sizeof(rawserial));
if(r<0)
return r;
for(i=0; i<BRLVGER_SERIAL_BIN_SIZE; i++) {
#define NUM_TO_HEX(n) (((n)>9) ? (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
/*
* Tieman Voyager braille display USB driver.
*
* Copyright 2001-2002 Stephane Dalton <sdalton@videotron.ca>
* and Stphane Doyon <s.doyon@videotron.ca>
* Maintained by Stphane Doyon <s.doyon@videotron.ca>.
*/
/*
* 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 2
/* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment