From d497d911acb7a2135ed85d25d09947b3614b495b Mon Sep 17 00:00:00 2001 From: Anders Blomdell Date: Thu, 12 Feb 2009 16:27:11 -0800 Subject: Staging: comedi: add serial2002 driver From: Anders Blomdell Driver for serial connected hardware From: Anders Blomdell Cc: David Schleef Cc: Frank Mori Hess Cc: Ian Abbott Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/drivers/serial2002.c | 864 ++++++++++++++++++++++++++++ 1 file changed, 864 insertions(+) create mode 100644 drivers/staging/comedi/drivers/serial2002.c --- /dev/null +++ b/drivers/staging/comedi/drivers/serial2002.c @@ -0,0 +1,864 @@ +/* + comedi/drivers/serial2002.c + Skeleton code for a Comedi driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2002 Anders Blomdell + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/* +Driver: serial2002 +Description: Driver for serial connected hardware +Devices: +Author: Anders Blomdell +Updated: Fri, 7 Jun 2002 12:56:45 -0700 +Status: in development + +*/ + +#include "../comedidev.h" + +#include +#include + +#include +#include +#include +#include + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +typedef struct serial2002_board_struct { + const char *name; +} serial2002_board; + +static const serial2002_board serial2002_boards[] = { + { + name: "serial2002"} +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const serial2002_board *)dev->board_ptr) + +typedef struct { + // HACK... + int length; + comedi_krange range; +} serial2002_range_table_t; + +typedef struct { + int port; // /dev/ttyS + int speed; // baudrate + struct file *tty; + lsampl_t ao_readback[32]; + unsigned char digital_in_mapping[32]; + unsigned char digital_out_mapping[32]; + unsigned char analog_in_mapping[32]; + unsigned char analog_out_mapping[32]; + unsigned char encoder_in_mapping[32]; + serial2002_range_table_t in_range[32], out_range[32]; +} serial2002_private; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((serial2002_private *)dev->private) + +static int serial2002_attach(comedi_device * dev, comedi_devconfig * it); +static int serial2002_detach(comedi_device * dev); +comedi_driver driver_serial2002 = { + driver_name:"serial2002", + module:THIS_MODULE, + attach:serial2002_attach, + detach:serial2002_detach, + board_name:&serial2002_boards[0].name, + offset:sizeof(serial2002_board), + num_names:sizeof(serial2002_boards) / sizeof(serial2002_board), +}; + +static int serial2002_di_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int serial2002_do_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int serial2002_ai_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int serial2002_ao_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int serial2002_ao_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); + +struct serial_data { + enum { is_invalid, is_digital, is_channel } kind; + int index; + unsigned long value; +}; + +static long tty_ioctl(struct file *f, unsigned op, unsigned long param) +{ +#ifdef HAVE_UNLOCKED_IOCTL + if (f->f_op->unlocked_ioctl) { + return f->f_op->unlocked_ioctl(f, op, param); + } +#endif + if (f->f_op->ioctl) { + return f->f_op->ioctl(f->f_dentry->d_inode, f, op, param); + } + return -ENOSYS; +} + +static int tty_write(struct file *f, unsigned char *buf, int count) +{ + int result; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + f->f_pos = 0; + result = f->f_op->write(f, buf, count, &f->f_pos); + set_fs(oldfs); + return result; +} + +#if 0 +/* + * On 2.6.26.3 this occaisonally gave me page faults, worked around by + * settings.c_cc[VMIN] = 0; settings.c_cc[VTIME] = 0 + */ +static int tty_available(struct file *f) +{ + long result = 0; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + tty_ioctl(f, FIONREAD, (unsigned long)&result); + set_fs(oldfs); + return result; +} +#endif + +static int tty_read(struct file *f, int timeout) +{ + int result; + + result = -1; + if (!IS_ERR(f)) { + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + if (f->f_op->poll) { + struct poll_wqueues table; + struct timeval start, now; + + do_gettimeofday(&start); + poll_initwait(&table); + while (1) { + long elapsed; + int mask; + + mask = f->f_op->poll(f, &table.pt); + if (mask & (POLLRDNORM | POLLRDBAND | POLLIN | + POLLHUP | POLLERR)) { + break; + } + do_gettimeofday(&now); + elapsed = + (1000000 * (now.tv_sec - start.tv_sec) + + now.tv_usec - start.tv_usec); + if (elapsed > timeout) { + break; + } + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(((timeout - + elapsed) * HZ) / 10000); + } + poll_freewait(&table); + { + unsigned char ch; + + f->f_pos = 0; + if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) { + result = ch; + } + } + } else { + /* Device does not support poll, busy wait */ + int retries = 0; + while (1) { + unsigned char ch; + + retries++; + if (retries >= timeout) { + break; + } + + f->f_pos = 0; + if (f->f_op->read(f, &ch, 1, &f->f_pos) == 1) { + result = ch; + break; + } + comedi_udelay(100); + } + } + set_fs(oldfs); + } + return result; +} + +static void tty_setspeed(struct file *f, int speed) +{ + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + { + // Set speed + struct termios settings; + + tty_ioctl(f, TCGETS, (unsigned long)&settings); +// printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); + settings.c_iflag = 0; + settings.c_oflag = 0; + settings.c_lflag = 0; + settings.c_cflag = CLOCAL | CS8 | CREAD; + settings.c_cc[VMIN] = 0; + settings.c_cc[VTIME] = 0; + switch (speed) { + case 2400:{ + settings.c_cflag |= B2400; + } + break; + case 4800:{ + settings.c_cflag |= B4800; + } + break; + case 9600:{ + settings.c_cflag |= B9600; + } + break; + case 19200:{ + settings.c_cflag |= B19200; + } + break; + case 38400:{ + settings.c_cflag |= B38400; + } + break; + case 57600:{ + settings.c_cflag |= B57600; + } + break; + case 115200:{ + settings.c_cflag |= B115200; + } + break; + default:{ + settings.c_cflag |= B9600; + } + break; + } + tty_ioctl(f, TCSETS, (unsigned long)&settings); +// printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); + } + { + // Set low latency + struct serial_struct settings; + + tty_ioctl(f, TIOCGSERIAL, (unsigned long)&settings); + settings.flags |= ASYNC_LOW_LATENCY; + tty_ioctl(f, TIOCSSERIAL, (unsigned long)&settings); + } + + set_fs(oldfs); +} + +static void poll_digital(struct file *f, int channel) +{ + char cmd; + + cmd = 0x40 | (channel & 0x1f); + tty_write(f, &cmd, 1); +} + +static void poll_channel(struct file *f, int channel) +{ + char cmd; + + cmd = 0x60 | (channel & 0x1f); + tty_write(f, &cmd, 1); +} + +static struct serial_data serial_read(struct file *f, int timeout) +{ + struct serial_data result; + int length; + + result.kind = is_invalid; + result.index = 0; + result.value = 0; + length = 0; + while (1) { + int data = tty_read(f, timeout); + + length++; + if (data < 0) { + printk("serial2002 error\n"); + break; + } else if (data & 0x80) { + result.value = (result.value << 7) | (data & 0x7f); + } else { + if (length == 1) { + switch ((data >> 5) & 0x03) { + case 0:{ + result.value = 0; + result.kind = is_digital; + } + break; + case 1:{ + result.value = 1; + result.kind = is_digital; + } + break; + } + } else { + result.value = + (result. + value << 2) | ((data & 0x60) >> 5); + result.kind = is_channel; + } + result.index = data & 0x1f; + break; + } + } + return result; + +} + +static void serial_write(struct file *f, struct serial_data data) +{ + if (data.kind == is_digital) { + unsigned char ch = + ((data.value << 5) & 0x20) | (data.index & 0x1f); + tty_write(f, &ch, 1); + } else { + unsigned char ch[6]; + int i = 0; + if (data.value >= (1L << 30)) { + ch[i] = 0x80 | ((data.value >> 30) & 0x03); + i++; + } + if (data.value >= (1L << 23)) { + ch[i] = 0x80 | ((data.value >> 23) & 0x7f); + i++; + } + if (data.value >= (1L << 16)) { + ch[i] = 0x80 | ((data.value >> 16) & 0x7f); + i++; + } + if (data.value >= (1L << 9)) { + ch[i] = 0x80 | ((data.value >> 9) & 0x7f); + i++; + } + ch[i] = 0x80 | ((data.value >> 2) & 0x7f); + i++; + ch[i] = ((data.value << 5) & 0x60) | (data.index & 0x1f); + i++; + tty_write(f, ch, i); + } +} + +static void serial_2002_open(comedi_device * dev) +{ + char port[20]; + + sprintf(port, "/dev/ttyS%d", devpriv->port); + devpriv->tty = filp_open(port, 0, O_RDWR); + if (IS_ERR(devpriv->tty)) { + printk("serial_2002: file open error = %ld\n", + PTR_ERR(devpriv->tty)); + } else { + typedef struct { + int kind; + int bits; + int min; + int max; + } config_t; + config_t dig_in_config[32]; + config_t dig_out_config[32]; + config_t chan_in_config[32]; + config_t chan_out_config[32]; + int i; + + for (i = 0; i < 32; i++) { + dig_in_config[i].kind = 0; + dig_in_config[i].bits = 0; + dig_in_config[i].min = 0; + dig_in_config[i].max = 0; + dig_out_config[i].kind = 0; + dig_out_config[i].bits = 0; + dig_out_config[i].min = 0; + dig_out_config[i].max = 0; + chan_in_config[i].kind = 0; + chan_in_config[i].bits = 0; + chan_in_config[i].min = 0; + chan_in_config[i].max = 0; + chan_out_config[i].kind = 0; + chan_out_config[i].bits = 0; + chan_out_config[i].min = 0; + chan_out_config[i].max = 0; + } + + tty_setspeed(devpriv->tty, devpriv->speed); + poll_channel(devpriv->tty, 31); // Start reading configuration + while (1) { + struct serial_data data; + + data = serial_read(devpriv->tty, 1000); + if (data.kind != is_channel || data.index != 31 + || !(data.value & 0xe0)) { + break; + } else { + int command, channel, kind; + config_t *cur_config = 0; + + channel = data.value & 0x1f; + kind = (data.value >> 5) & 0x7; + command = (data.value >> 8) & 0x3; + switch (kind) { + case 1:{ + cur_config = dig_in_config; + } + break; + case 2:{ + cur_config = dig_out_config; + } + break; + case 3:{ + cur_config = chan_in_config; + } + break; + case 4:{ + cur_config = chan_out_config; + } + break; + case 5:{ + cur_config = chan_in_config; + } + break; + } + + if (cur_config) { + cur_config[channel].kind = kind; + switch (command) { + case 0:{ + cur_config[channel]. + bits = + (data. + value >> 10) & + 0x3f; + } + break; + case 1:{ + int unit, sign, min; + unit = (data. + value >> 10) & + 0x7; + sign = (data. + value >> 13) & + 0x1; + min = (data. + value >> 14) & + 0xfffff; + + switch (unit) { + case 0:{ + min = min * 1000000; + } + break; + case 1:{ + min = min * 1000; + } + break; + case 2:{ + min = min * 1; + } + break; + } + if (sign) { + min = -min; + } + cur_config[channel]. + min = min; + } + break; + case 2:{ + int unit, sign, max; + unit = (data. + value >> 10) & + 0x7; + sign = (data. + value >> 13) & + 0x1; + max = (data. + value >> 14) & + 0xfffff; + + switch (unit) { + case 0:{ + max = max * 1000000; + } + break; + case 1:{ + max = max * 1000; + } + break; + case 2:{ + max = max * 1; + } + break; + } + if (sign) { + max = -max; + } + cur_config[channel]. + max = max; + } + break; + } + } + } + } + for (i = 0; i <= 4; i++) { + // Fill in subdev data + config_t *c; + unsigned char *mapping = 0; + serial2002_range_table_t *range = 0; + int kind = 0; + + switch (i) { + case 0:{ + c = dig_in_config; + mapping = devpriv->digital_in_mapping; + kind = 1; + } + break; + case 1:{ + c = dig_out_config; + mapping = devpriv->digital_out_mapping; + kind = 2; + } + break; + case 2:{ + c = chan_in_config; + mapping = devpriv->analog_in_mapping; + range = devpriv->in_range; + kind = 3; + } + break; + case 3:{ + c = chan_out_config; + mapping = devpriv->analog_out_mapping; + range = devpriv->out_range; + kind = 4; + } + break; + case 4:{ + c = chan_in_config; + mapping = devpriv->encoder_in_mapping; + range = devpriv->in_range; + kind = 5; + } + break; + default:{ + c = 0; + } + break; + } + if (c) { + comedi_subdevice *s; + const comedi_lrange **range_table_list = NULL; + unsigned int *maxdata_list; + int j, chan; + + for (chan = 0, j = 0; j < 32; j++) { + if (c[j].kind == kind) { + chan++; + } + } + s = &dev->subdevices[i]; + s->n_chan = chan; + s->maxdata = 0; + if (s->maxdata_list) { + kfree(s->maxdata_list); + } + s->maxdata_list = maxdata_list = + kmalloc(sizeof(lsampl_t) * s->n_chan, + GFP_KERNEL); + if (s->range_table_list) { + kfree(s->range_table_list); + } + if (range) { + s->range_table = 0; + s->range_table_list = range_table_list = + kmalloc(sizeof + (serial2002_range_table_t) * + s->n_chan, GFP_KERNEL); + } + for (chan = 0, j = 0; j < 32; j++) { + if (c[j].kind == kind) { + if (mapping) { + mapping[chan] = j; + } + if (range) { + range[j].length = 1; + range[j].range.min = + c[j].min; + range[j].range.max = + c[j].max; + range_table_list[chan] = + (const + comedi_lrange *) + &range[j]; + } + maxdata_list[chan] = + ((long long)1 << c[j]. + bits) - 1; + chan++; + } + } + } + } + } +} + +static void serial_2002_close(comedi_device * dev) +{ + if (!IS_ERR(devpriv->tty) && (devpriv->tty != 0)) { + filp_close(devpriv->tty, 0); + } +} + +static int serial2002_di_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n; + int chan; + + chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data read; + + poll_digital(devpriv->tty, chan); + while (1) { + read = serial_read(devpriv->tty, 1000); + if (read.kind != is_digital || read.index == chan) { + break; + } + } + data[n] = read.value; + } + return n; +} + +static int serial2002_do_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n; + int chan; + + chan = devpriv->digital_out_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data write; + + write.kind = is_digital; + write.index = chan; + write.value = data[n]; + serial_write(devpriv->tty, write); + } + return n; +} + +static int serial2002_ai_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n; + int chan; + + chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data read; + + poll_channel(devpriv->tty, chan); + while (1) { + read = serial_read(devpriv->tty, 1000); + if (read.kind != is_channel || read.index == chan) { + break; + } + } + data[n] = read.value; + } + return n; +} + +static int serial2002_ao_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n; + int chan; + + chan = devpriv->analog_out_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data write; + + write.kind = is_channel; + write.index = chan; + write.value = data[n]; + serial_write(devpriv->tty, write); + devpriv->ao_readback[chan] = data[n]; + } + return n; +} + +static int serial2002_ao_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n; + int chan = CR_CHAN(insn->chanspec); + + for (n = 0; n < insn->n; n++) { + data[n] = devpriv->ao_readback[chan]; + } + + return n; +} + +static int serial2002_ei_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n; + int chan; + + chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)]; + for (n = 0; n < insn->n; n++) { + struct serial_data read; + + poll_channel(devpriv->tty, chan); + while (1) { + read = serial_read(devpriv->tty, 1000); + if (read.kind != is_channel || read.index == chan) { + break; + } + } + data[n] = read.value; + } + return n; +} + +static int serial2002_attach(comedi_device * dev, comedi_devconfig * it) +{ + comedi_subdevice *s; + + printk("comedi%d: serial2002: ", dev->minor); + dev->board_name = thisboard->name; + if (alloc_private(dev, sizeof(serial2002_private)) < 0) { + return -ENOMEM; + } + dev->open = serial_2002_open; + dev->close = serial_2002_close; + devpriv->port = it->options[0]; + devpriv->speed = it->options[1]; + printk("/dev/ttyS%d @ %d\n", devpriv->port, devpriv->speed); + + if (alloc_subdevices(dev, 5) < 0) + return -ENOMEM; + + /* digital input subdevice */ + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_read = &serial2002_di_rinsn; + + /* digital output subdevice */ + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_write = &serial2002_do_winsn; + + /* analog input subdevice */ + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = 0; + s->insn_read = &serial2002_ai_rinsn; + + /* analog output subdevice */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = 0; + s->insn_write = &serial2002_ao_winsn; + s->insn_read = &serial2002_ao_rinsn; + + /* encoder input subdevice */ + s = dev->subdevices + 4; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_LSAMPL; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = 0; + s->insn_read = &serial2002_ei_rinsn; + + return 1; +} + +static int serial2002_detach(comedi_device * dev) +{ + comedi_subdevice *s; + int i; + + printk("comedi%d: serial2002: remove\n", dev->minor); + for (i = 0; i < 4; i++) { + s = &dev->subdevices[i]; + if (s->maxdata_list) { + kfree(s->maxdata_list); + } + if (s->range_table_list) { + kfree(s->range_table_list); + } + } + return 0; +} + +COMEDI_INITCLEANUP(driver_serial2002);