Commit 500cc548 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

staging: comedi: drop serial2002 driver

There's not been any work on this driver since it was originally merged,
and it really needs to be rewritten to use the serdev layer instead if
people really need/want it.
Reported-by: default avatarChristoph Hellwig <hch@lst.de>
Cc: Ian Abbott <abbotti@mev.co.uk>
Cc: H Hartley Sweeten <hsweeten@visionengravers.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 24daf6a3
......@@ -75,14 +75,6 @@ config COMEDI_PARPORT
To compile this driver as a module, choose M here: the module will be
called comedi_parport.
config COMEDI_SERIAL2002
tristate "Driver for serial connected hardware"
---help---
Enable support for serial connected hardware
To compile this driver as a module, choose M here: the module will be
called serial2002.
config COMEDI_SSV_DNP
tristate "SSV Embedded Systems DIL/Net-PC support"
depends on X86_32 || COMPILE_TEST
......
......@@ -11,7 +11,6 @@ obj-$(CONFIG_COMEDI_ISADMA) += comedi_isadma.o
obj-$(CONFIG_COMEDI_BOND) += comedi_bond.o
obj-$(CONFIG_COMEDI_TEST) += comedi_test.o
obj-$(CONFIG_COMEDI_PARPORT) += comedi_parport.o
obj-$(CONFIG_COMEDI_SERIAL2002) += serial2002.o
# Comedi ISA drivers
obj-$(CONFIG_COMEDI_AMPLC_DIO200_ISA) += amplc_dio200.o
......
// SPDX-License-Identifier: GPL-2.0+
/*
* serial2002.c
* Comedi driver for serial connected hardware
*
* COMEDI - Linux Control and Measurement Device Interface
* Copyright (C) 2002 Anders Blomdell <anders.blomdell@control.lth.se>
*/
/*
* 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 <linux/module.h>
#include "../comedidev.h"
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/ktime.h>
#include <linux/termios.h>
#include <asm/ioctls.h>
#include <linux/serial.h>
#include <linux/poll.h>
struct serial2002_range_table_t {
/* HACK... */
int length;
struct comedi_krange range;
};
struct serial2002_private {
int port; /* /dev/ttyS<port> */
int speed; /* baudrate */
struct file *tty;
unsigned int 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];
struct serial2002_range_table_t in_range[32], out_range[32];
};
struct serial_data {
enum { is_invalid, is_digital, is_channel } kind;
int index;
unsigned long value;
};
/*
* The configuration serial_data.value read from the device is
* a bitmask that defines specific options of a channel:
*
* 4:0 - the channel to configure
* 7:5 - the kind of channel
* 9:8 - the command used to configure the channel
*
* The remaining bits vary in use depending on the command:
*
* BITS 15:10 - the channel bits (maxdata)
* MIN/MAX 12:10 - the units multiplier for the scale
* 13 - the sign of the scale
* 33:14 - the base value for the range
*/
#define S2002_CFG_CHAN(x) ((x) & 0x1f)
#define S2002_CFG_KIND(x) (((x) >> 5) & 0x7)
#define S2002_CFG_KIND_INVALID 0
#define S2002_CFG_KIND_DIGITAL_IN 1
#define S2002_CFG_KIND_DIGITAL_OUT 2
#define S2002_CFG_KIND_ANALOG_IN 3
#define S2002_CFG_KIND_ANALOG_OUT 4
#define S2002_CFG_KIND_ENCODER_IN 5
#define S2002_CFG_CMD(x) (((x) >> 8) & 0x3)
#define S2002_CFG_CMD_BITS 0
#define S2002_CFG_CMD_MIN 1
#define S2002_CFG_CMD_MAX 2
#define S2002_CFG_BITS(x) (((x) >> 10) & 0x3f)
#define S2002_CFG_UNITS(x) (((x) >> 10) & 0x7)
#define S2002_CFG_SIGN(x) (((x) >> 13) & 0x1)
#define S2002_CFG_BASE(x) (((x) >> 14) & 0xfffff)
static long serial2002_tty_ioctl(struct file *f, unsigned int op,
unsigned long param)
{
if (f->f_op->unlocked_ioctl)
return f->f_op->unlocked_ioctl(f, op, param);
return -ENOTTY;
}
static int serial2002_tty_write(struct file *f, unsigned char *buf, int count)
{
loff_t pos = 0;
return kernel_write(f, buf, count, &pos);
}
static void serial2002_tty_read_poll_wait(struct file *f, int timeout)
{
struct poll_wqueues table;
ktime_t start, now;
start = ktime_get();
poll_initwait(&table);
while (1) {
long elapsed;
__poll_t mask;
mask = vfs_poll(f, &table.pt);
if (mask & (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN |
EPOLLHUP | EPOLLERR)) {
break;
}
now = ktime_get();
elapsed = ktime_us_delta(now, start);
if (elapsed > timeout)
break;
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(((timeout - elapsed) * HZ) / 10000);
}
poll_freewait(&table);
}
static int serial2002_tty_read(struct file *f, int timeout)
{
unsigned char ch;
int result;
loff_t pos = 0;
result = -1;
if (!IS_ERR(f)) {
if (file_can_poll(f)) {
serial2002_tty_read_poll_wait(f, timeout);
if (kernel_read(f, &ch, 1, &pos) == 1)
result = ch;
} else {
/* Device does not support poll, busy wait */
int retries = 0;
while (1) {
retries++;
if (retries >= timeout)
break;
if (kernel_read(f, &ch, 1, &pos) == 1) {
result = ch;
break;
}
usleep_range(100, 1000);
}
}
}
return result;
}
static void serial2002_tty_setspeed(struct file *f, int speed)
{
struct termios termios;
struct serial_struct serial;
mm_segment_t oldfs;
oldfs = get_fs();
set_fs(KERNEL_DS);
/* Set speed */
serial2002_tty_ioctl(f, TCGETS, (unsigned long)&termios);
termios.c_iflag = 0;
termios.c_oflag = 0;
termios.c_lflag = 0;
termios.c_cflag = CLOCAL | CS8 | CREAD;
termios.c_cc[VMIN] = 0;
termios.c_cc[VTIME] = 0;
switch (speed) {
case 2400:
termios.c_cflag |= B2400;
break;
case 4800:
termios.c_cflag |= B4800;
break;
case 9600:
termios.c_cflag |= B9600;
break;
case 19200:
termios.c_cflag |= B19200;
break;
case 38400:
termios.c_cflag |= B38400;
break;
case 57600:
termios.c_cflag |= B57600;
break;
case 115200:
termios.c_cflag |= B115200;
break;
default:
termios.c_cflag |= B9600;
break;
}
serial2002_tty_ioctl(f, TCSETS, (unsigned long)&termios);
/* Set low latency */
serial2002_tty_ioctl(f, TIOCGSERIAL, (unsigned long)&serial);
serial.flags |= ASYNC_LOW_LATENCY;
serial2002_tty_ioctl(f, TIOCSSERIAL, (unsigned long)&serial);
set_fs(oldfs);
}
static void serial2002_poll_digital(struct file *f, int channel)
{
char cmd;
cmd = 0x40 | (channel & 0x1f);
serial2002_tty_write(f, &cmd, 1);
}
static void serial2002_poll_channel(struct file *f, int channel)
{
char cmd;
cmd = 0x60 | (channel & 0x1f);
serial2002_tty_write(f, &cmd, 1);
}
static struct serial_data serial2002_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 = serial2002_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 serial2002_write(struct file *f, struct serial_data data)
{
if (data.kind == is_digital) {
unsigned char ch =
((data.value << 5) & 0x20) | (data.index & 0x1f);
serial2002_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++;
serial2002_tty_write(f, ch, i);
}
}
struct config_t {
short int kind;
short int bits;
int min;
int max;
};
static int serial2002_setup_subdevice(struct comedi_subdevice *s,
struct config_t *cfg,
struct serial2002_range_table_t *range,
unsigned char *mapping,
int kind)
{
const struct comedi_lrange **range_table_list = NULL;
unsigned int *maxdata_list;
int j, chan;
for (chan = 0, j = 0; j < 32; j++) {
if (cfg[j].kind == kind)
chan++;
}
s->n_chan = chan;
s->maxdata = 0;
kfree(s->maxdata_list);
maxdata_list = kmalloc_array(s->n_chan, sizeof(unsigned int),
GFP_KERNEL);
if (!maxdata_list)
return -ENOMEM;
s->maxdata_list = maxdata_list;
kfree(s->range_table_list);
s->range_table = NULL;
s->range_table_list = NULL;
if (kind == 1 || kind == 2) {
s->range_table = &range_digital;
} else if (range) {
range_table_list = kmalloc_array(s->n_chan, sizeof(*range),
GFP_KERNEL);
if (!range_table_list)
return -ENOMEM;
s->range_table_list = range_table_list;
}
for (chan = 0, j = 0; j < 32; j++) {
if (cfg[j].kind == kind) {
if (mapping)
mapping[chan] = j;
if (range && range_table_list) {
range[j].length = 1;
range[j].range.min = cfg[j].min;
range[j].range.max = cfg[j].max;
range_table_list[chan] =
(const struct comedi_lrange *)&range[j];
}
if (cfg[j].bits < 32)
maxdata_list[chan] = (1u << cfg[j].bits) - 1;
else
maxdata_list[chan] = 0xffffffff;
chan++;
}
}
return 0;
}
static int serial2002_setup_subdevs(struct comedi_device *dev)
{
struct serial2002_private *devpriv = dev->private;
struct config_t *di_cfg;
struct config_t *do_cfg;
struct config_t *ai_cfg;
struct config_t *ao_cfg;
struct config_t *cfg;
struct comedi_subdevice *s;
int result = 0;
int i;
/* Allocate the temporary structs to hold the configuration data */
di_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
do_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
ai_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
ao_cfg = kcalloc(32, sizeof(*cfg), GFP_KERNEL);
if (!di_cfg || !do_cfg || !ai_cfg || !ao_cfg) {
result = -ENOMEM;
goto err_alloc_configs;
}
/* Read the configuration from the connected device */
serial2002_tty_setspeed(devpriv->tty, devpriv->speed);
serial2002_poll_channel(devpriv->tty, 31);
while (1) {
struct serial_data data = serial2002_read(devpriv->tty, 1000);
int kind = S2002_CFG_KIND(data.value);
int channel = S2002_CFG_CHAN(data.value);
int range = S2002_CFG_BASE(data.value);
int cmd = S2002_CFG_CMD(data.value);
if (data.kind != is_channel || data.index != 31 ||
kind == S2002_CFG_KIND_INVALID)
break;
switch (kind) {
case S2002_CFG_KIND_DIGITAL_IN:
cfg = di_cfg;
break;
case S2002_CFG_KIND_DIGITAL_OUT:
cfg = do_cfg;
break;
case S2002_CFG_KIND_ANALOG_IN:
cfg = ai_cfg;
break;
case S2002_CFG_KIND_ANALOG_OUT:
cfg = ao_cfg;
break;
case S2002_CFG_KIND_ENCODER_IN:
cfg = ai_cfg;
break;
default:
cfg = NULL;
break;
}
if (!cfg)
continue; /* unknown kind, skip it */
cfg[channel].kind = kind;
switch (cmd) {
case S2002_CFG_CMD_BITS:
cfg[channel].bits = S2002_CFG_BITS(data.value);
break;
case S2002_CFG_CMD_MIN:
case S2002_CFG_CMD_MAX:
switch (S2002_CFG_UNITS(data.value)) {
case 0:
range *= 1000000;
break;
case 1:
range *= 1000;
break;
case 2:
range *= 1;
break;
}
if (S2002_CFG_SIGN(data.value))
range = -range;
if (cmd == S2002_CFG_CMD_MIN)
cfg[channel].min = range;
else
cfg[channel].max = range;
break;
}
}
/* Fill in subdevice data */
for (i = 0; i <= 4; i++) {
unsigned char *mapping = NULL;
struct serial2002_range_table_t *range = NULL;
int kind = 0;
s = &dev->subdevices[i];
switch (i) {
case 0:
cfg = di_cfg;
mapping = devpriv->digital_in_mapping;
kind = S2002_CFG_KIND_DIGITAL_IN;
break;
case 1:
cfg = do_cfg;
mapping = devpriv->digital_out_mapping;
kind = S2002_CFG_KIND_DIGITAL_OUT;
break;
case 2:
cfg = ai_cfg;
mapping = devpriv->analog_in_mapping;
range = devpriv->in_range;
kind = S2002_CFG_KIND_ANALOG_IN;
break;
case 3:
cfg = ao_cfg;
mapping = devpriv->analog_out_mapping;
range = devpriv->out_range;
kind = S2002_CFG_KIND_ANALOG_OUT;
break;
case 4:
cfg = ai_cfg;
mapping = devpriv->encoder_in_mapping;
range = devpriv->in_range;
kind = S2002_CFG_KIND_ENCODER_IN;
break;
}
if (serial2002_setup_subdevice(s, cfg, range, mapping, kind))
break; /* err handled below */
}
if (i <= 4) {
/*
* Failed to allocate maxdata_list or range_table_list
* for a subdevice that needed it.
*/
result = -ENOMEM;
for (i = 0; i <= 4; i++) {
s = &dev->subdevices[i];
kfree(s->maxdata_list);
s->maxdata_list = NULL;
kfree(s->range_table_list);
s->range_table_list = NULL;
}
}
err_alloc_configs:
kfree(di_cfg);
kfree(do_cfg);
kfree(ai_cfg);
kfree(ao_cfg);
if (result) {
if (devpriv->tty) {
filp_close(devpriv->tty, NULL);
devpriv->tty = NULL;
}
}
return result;
}
static int serial2002_open(struct comedi_device *dev)
{
struct serial2002_private *devpriv = dev->private;
int result;
char port[20];
sprintf(port, "/dev/ttyS%d", devpriv->port);
devpriv->tty = filp_open(port, O_RDWR, 0);
if (IS_ERR(devpriv->tty)) {
result = (int)PTR_ERR(devpriv->tty);
dev_err(dev->class_dev, "file open error = %d\n", result);
} else {
result = serial2002_setup_subdevs(dev);
}
return result;
}
static void serial2002_close(struct comedi_device *dev)
{
struct serial2002_private *devpriv = dev->private;
if (!IS_ERR(devpriv->tty) && devpriv->tty)
filp_close(devpriv->tty, NULL);
}
static int serial2002_di_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct serial2002_private *devpriv = dev->private;
int n;
int chan;
chan = devpriv->digital_in_mapping[CR_CHAN(insn->chanspec)];
for (n = 0; n < insn->n; n++) {
struct serial_data read;
serial2002_poll_digital(devpriv->tty, chan);
while (1) {
read = serial2002_read(devpriv->tty, 1000);
if (read.kind != is_digital || read.index == chan)
break;
}
data[n] = read.value;
}
return n;
}
static int serial2002_do_insn_write(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct serial2002_private *devpriv = dev->private;
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];
serial2002_write(devpriv->tty, write);
}
return n;
}
static int serial2002_ai_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct serial2002_private *devpriv = dev->private;
int n;
int chan;
chan = devpriv->analog_in_mapping[CR_CHAN(insn->chanspec)];
for (n = 0; n < insn->n; n++) {
struct serial_data read;
serial2002_poll_channel(devpriv->tty, chan);
while (1) {
read = serial2002_read(devpriv->tty, 1000);
if (read.kind != is_channel || read.index == chan)
break;
}
data[n] = read.value;
}
return n;
}
static int serial2002_ao_insn_write(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct serial2002_private *devpriv = dev->private;
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];
serial2002_write(devpriv->tty, write);
devpriv->ao_readback[chan] = data[n];
}
return n;
}
static int serial2002_ao_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct serial2002_private *devpriv = dev->private;
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_encoder_insn_read(struct comedi_device *dev,
struct comedi_subdevice *s,
struct comedi_insn *insn,
unsigned int *data)
{
struct serial2002_private *devpriv = dev->private;
int n;
int chan;
chan = devpriv->encoder_in_mapping[CR_CHAN(insn->chanspec)];
for (n = 0; n < insn->n; n++) {
struct serial_data read;
serial2002_poll_channel(devpriv->tty, chan);
while (1) {
read = serial2002_read(devpriv->tty, 1000);
if (read.kind != is_channel || read.index == chan)
break;
}
data[n] = read.value;
}
return n;
}
static int serial2002_attach(struct comedi_device *dev,
struct comedi_devconfig *it)
{
struct serial2002_private *devpriv;
struct comedi_subdevice *s;
int ret;
devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
if (!devpriv)
return -ENOMEM;
devpriv->port = it->options[0];
devpriv->speed = it->options[1];
ret = comedi_alloc_subdevices(dev, 5);
if (ret)
return ret;
/* 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_insn_read;
/* digital output subdevice */
s = &dev->subdevices[1];
s->type = COMEDI_SUBD_DO;
s->subdev_flags = SDF_WRITABLE;
s->n_chan = 0;
s->maxdata = 1;
s->range_table = &range_digital;
s->insn_write = serial2002_do_insn_write;
/* 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 = NULL;
s->insn_read = serial2002_ai_insn_read;
/* analog output subdevice */
s = &dev->subdevices[3];
s->type = COMEDI_SUBD_AO;
s->subdev_flags = SDF_WRITABLE;
s->n_chan = 0;
s->maxdata = 1;
s->range_table = NULL;
s->insn_write = serial2002_ao_insn_write;
s->insn_read = serial2002_ao_insn_read;
/* 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 = NULL;
s->insn_read = serial2002_encoder_insn_read;
dev->open = serial2002_open;
dev->close = serial2002_close;
return 0;
}
static void serial2002_detach(struct comedi_device *dev)
{
struct comedi_subdevice *s;
int i;
for (i = 0; i < dev->n_subdevices; i++) {
s = &dev->subdevices[i];
kfree(s->maxdata_list);
kfree(s->range_table_list);
}
}
static struct comedi_driver serial2002_driver = {
.driver_name = "serial2002",
.module = THIS_MODULE,
.attach = serial2002_attach,
.detach = serial2002_detach,
};
module_comedi_driver(serial2002_driver);
MODULE_AUTHOR("Comedi http://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_LICENSE("GPL");
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