From: David Schleef Date: Mon, 20 May 2002 18:38:53 +0000 (+0000) Subject: new driver from Anders X-Git-Tag: r0_7_65~24 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=11dbaddac1037e53a8aead6ab53710a5358f6cbc;p=comedi.git new driver from Anders --- diff --git a/comedi/drivers/serial2002.c b/comedi/drivers/serial2002.c new file mode 100644 index 00000000..aa5a14ac --- /dev/null +++ b/comedi/drivers/serial2002.c @@ -0,0 +1,616 @@ +/* + 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. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 { + char *name; +} serial2002_board; + +serial2002_board serial2002_boards[] = { + { + name: "serial2002" + } +}; +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((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]; + 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, + 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 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; +} + + +static int tty_available(struct file *f) +{ + long result = 0; + mm_segment_t oldfs; + + oldfs = get_fs(); + set_fs(KERNEL_DS); + f->f_op->ioctl(f->f_dentry->d_inode, f, FIONREAD, (int)&result); + set_fs(oldfs); + return result; +} + +static int tty_read(struct file *f, int timeout) +{ + int result, retries; + mm_segment_t oldfs; + + result = -1; + if (!IS_ERR(f)) { + retries = 0; + oldfs = get_fs(); + set_fs(KERNEL_DS); + while (retries < timeout) { + retries++; + if (tty_available(f) > 0) { + int count; + unsigned char ch; + + f->f_pos = 0; + count = f->f_op->read(f, &ch, 1, &f->f_pos); + if (count == 1) { result = ch; } + break; + } + 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; + + f->f_op->ioctl(f->f_dentry->d_inode, f, TCGETS, (int)&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] = 1; + 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; + } + f->f_op->ioctl(f->f_dentry->d_inode, f, TCSETS, (int)&settings); +// printk("Speed: %d\n", settings.c_cflag & (CBAUD | CBAUDEX)); + } + { + // Set low latency + struct serial_struct settings; + int i; + f->f_op->ioctl(f->f_dentry->d_inode, f, TIOCGSERIAL, (int)&settings); + settings.flags |= ASYNC_LOW_LATENCY; + f->f_op->ioctl(f->f_dentry->d_inode, f, TIOCSSERIAL, (int)&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) { + 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 int 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 = %x\n", (int)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; + c = 0; // Counters/encoder not implemented yet + kind = 5; + } break; + default: { c = 0; } break; + } + if (c) { + comedi_subdevice *s; + 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 = 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 = + 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; + s->range_table_list[chan] = (comedi_lrange*)&range[j]; + } + s->maxdata_list[chan] = (1<tty) && (devpriv->tty != 0)) { + filp_close(devpriv->tty, 0); + } + return 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_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); + + dev->n_subdevices=4; + if(alloc_subdevices(dev)<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; + 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; + + printk("attached\n"); + + 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); + +