/* 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.o Description: Driver for serial connected hardware Devices: Author: Anders Blomdell Updated: Fri, 7 Jun 2002 12:56:45 -0700 Status: in development */ #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|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; 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);