2017-11-07 13:58:43 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2009-04-22 17:28:18 +00:00
|
|
|
/*
|
2015-08-14 21:35:28 +00:00
|
|
|
* vmk80xx.c
|
|
|
|
* Velleman USB Board Low-Level Driver
|
|
|
|
*
|
|
|
|
* Copyright (C) 2009 Manuel Gebele <forensixs@gmx.de>, Germany
|
|
|
|
*
|
|
|
|
* COMEDI - Linux Control and Measurement Device Interface
|
|
|
|
* Copyright (C) 2000 David A. Schleef <ds@schleef.org>
|
|
|
|
*/
|
2009-04-22 17:28:18 +00:00
|
|
|
|
|
|
|
/*
|
2015-01-05 17:55:13 +00:00
|
|
|
* Driver: vmk80xx
|
|
|
|
* Description: Velleman USB Board Low-Level Driver
|
|
|
|
* Devices: [Velleman] K8055 (K8055/VM110), K8061 (K8061/VM140),
|
|
|
|
* VM110 (K8055/VM110), VM140 (K8061/VM140)
|
|
|
|
* Author: Manuel Gebele <forensixs@gmx.de>
|
|
|
|
* Updated: Sun, 10 May 2009 11:14:59 +0200
|
|
|
|
* Status: works
|
|
|
|
*
|
|
|
|
* Supports:
|
|
|
|
* - analog input
|
|
|
|
* - analog output
|
|
|
|
* - digital input
|
|
|
|
* - digital output
|
|
|
|
* - counter
|
|
|
|
* - pwm
|
|
|
|
*/
|
2009-04-22 17:28:18 +00:00
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/poll.h>
|
2009-05-10 10:00:43 +00:00
|
|
|
#include <linux/uaccess.h>
|
2021-11-17 12:05:59 +00:00
|
|
|
#include <linux/comedi/comedi_usb.h>
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
enum {
|
|
|
|
DEVICE_VMK8055,
|
|
|
|
DEVICE_VMK8061
|
|
|
|
};
|
|
|
|
|
2015-08-14 21:35:29 +00:00
|
|
|
#define VMK8055_DI_REG 0x00
|
|
|
|
#define VMK8055_DO_REG 0x01
|
|
|
|
#define VMK8055_AO1_REG 0x02
|
|
|
|
#define VMK8055_AO2_REG 0x03
|
|
|
|
#define VMK8055_AI1_REG 0x02
|
|
|
|
#define VMK8055_AI2_REG 0x03
|
|
|
|
#define VMK8055_CNT1_REG 0x04
|
|
|
|
#define VMK8055_CNT2_REG 0x06
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2015-08-14 21:35:29 +00:00
|
|
|
#define VMK8061_CH_REG 0x01
|
|
|
|
#define VMK8061_DI_REG 0x01
|
|
|
|
#define VMK8061_DO_REG 0x01
|
|
|
|
#define VMK8061_PWM_REG1 0x01
|
|
|
|
#define VMK8061_PWM_REG2 0x02
|
|
|
|
#define VMK8061_CNT_REG 0x02
|
|
|
|
#define VMK8061_AO_REG 0x02
|
|
|
|
#define VMK8061_AI_REG1 0x02
|
|
|
|
#define VMK8061_AI_REG2 0x03
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2015-08-14 21:35:29 +00:00
|
|
|
#define VMK8055_CMD_RST 0x00
|
|
|
|
#define VMK8055_CMD_DEB1_TIME 0x01
|
|
|
|
#define VMK8055_CMD_DEB2_TIME 0x02
|
|
|
|
#define VMK8055_CMD_RST_CNT1 0x03
|
|
|
|
#define VMK8055_CMD_RST_CNT2 0x04
|
|
|
|
#define VMK8055_CMD_WRT_AD 0x05
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2015-08-14 21:35:29 +00:00
|
|
|
#define VMK8061_CMD_RD_AI 0x00
|
|
|
|
#define VMK8061_CMR_RD_ALL_AI 0x01 /* !non-active! */
|
|
|
|
#define VMK8061_CMD_SET_AO 0x02
|
|
|
|
#define VMK8061_CMD_SET_ALL_AO 0x03 /* !non-active! */
|
|
|
|
#define VMK8061_CMD_OUT_PWM 0x04
|
|
|
|
#define VMK8061_CMD_RD_DI 0x05
|
|
|
|
#define VMK8061_CMD_DO 0x06 /* !non-active! */
|
|
|
|
#define VMK8061_CMD_CLR_DO 0x07
|
|
|
|
#define VMK8061_CMD_SET_DO 0x08
|
|
|
|
#define VMK8061_CMD_RD_CNT 0x09 /* TODO: completely pointless? */
|
|
|
|
#define VMK8061_CMD_RST_CNT 0x0a /* TODO: completely pointless? */
|
|
|
|
#define VMK8061_CMD_RD_VERSION 0x0b /* internal usage */
|
|
|
|
#define VMK8061_CMD_RD_JMP_STAT 0x0c /* TODO: not implemented yet */
|
|
|
|
#define VMK8061_CMD_RD_PWR_STAT 0x0d /* internal usage */
|
|
|
|
#define VMK8061_CMD_RD_DO 0x0e
|
|
|
|
#define VMK8061_CMD_RD_AO 0x0f
|
|
|
|
#define VMK8061_CMD_RD_PWM 0x10
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2015-08-14 21:35:29 +00:00
|
|
|
#define IC3_VERSION BIT(0)
|
|
|
|
#define IC6_VERSION BIT(1)
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2021-10-25 11:45:30 +00:00
|
|
|
#define MIN_BUF_SIZE 64
|
2021-10-25 11:45:32 +00:00
|
|
|
#define PACKET_TIMEOUT 10000 /* ms */
|
2021-10-25 11:45:30 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
enum vmk80xx_model {
|
|
|
|
VMK8055_MODEL,
|
|
|
|
VMK8061_MODEL
|
|
|
|
};
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
static const struct comedi_lrange vmk8061_range = {
|
2013-02-06 00:30:21 +00:00
|
|
|
2, {
|
|
|
|
UNI_RANGE(5),
|
|
|
|
UNI_RANGE(10)
|
|
|
|
}
|
2009-05-10 10:00:43 +00:00
|
|
|
};
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
struct vmk80xx_board {
|
|
|
|
const char *name;
|
|
|
|
enum vmk80xx_model model;
|
|
|
|
const struct comedi_lrange *range;
|
2013-02-06 00:24:45 +00:00
|
|
|
int ai_nchans;
|
|
|
|
unsigned int ai_maxdata;
|
2013-02-06 00:25:07 +00:00
|
|
|
int ao_nchans;
|
2013-02-06 00:25:28 +00:00
|
|
|
int di_nchans;
|
2013-02-06 00:27:37 +00:00
|
|
|
unsigned int cnt_maxdata;
|
2013-02-06 00:27:56 +00:00
|
|
|
int pwm_nchans;
|
|
|
|
unsigned int pwm_maxdata;
|
2009-05-10 10:00:43 +00:00
|
|
|
};
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:18:52 +00:00
|
|
|
static const struct vmk80xx_board vmk80xx_boardinfo[] = {
|
|
|
|
[DEVICE_VMK8055] = {
|
|
|
|
.name = "K8055 (VM110)",
|
|
|
|
.model = VMK8055_MODEL,
|
2013-02-06 00:30:21 +00:00
|
|
|
.range = &range_unipolar5,
|
2013-02-06 00:24:45 +00:00
|
|
|
.ai_nchans = 2,
|
|
|
|
.ai_maxdata = 0x00ff,
|
2013-02-06 00:25:07 +00:00
|
|
|
.ao_nchans = 2,
|
2013-02-06 00:25:28 +00:00
|
|
|
.di_nchans = 6,
|
2013-02-06 00:27:37 +00:00
|
|
|
.cnt_maxdata = 0xffff,
|
2013-02-06 00:18:52 +00:00
|
|
|
},
|
|
|
|
[DEVICE_VMK8061] = {
|
|
|
|
.name = "K8061 (VM140)",
|
|
|
|
.model = VMK8061_MODEL,
|
|
|
|
.range = &vmk8061_range,
|
2013-02-06 00:24:45 +00:00
|
|
|
.ai_nchans = 8,
|
|
|
|
.ai_maxdata = 0x03ff,
|
2013-02-06 00:25:07 +00:00
|
|
|
.ao_nchans = 8,
|
2013-02-06 00:25:28 +00:00
|
|
|
.di_nchans = 8,
|
2013-02-06 00:27:37 +00:00
|
|
|
.cnt_maxdata = 0, /* unknown, device is not writeable */
|
2013-02-06 00:27:56 +00:00
|
|
|
.pwm_nchans = 1,
|
|
|
|
.pwm_maxdata = 0x03ff,
|
2013-02-06 00:18:52 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2013-02-06 00:20:22 +00:00
|
|
|
struct vmk80xx_private {
|
2009-05-10 10:00:43 +00:00
|
|
|
struct usb_endpoint_descriptor *ep_rx;
|
|
|
|
struct usb_endpoint_descriptor *ep_tx;
|
|
|
|
struct semaphore limit_sem;
|
|
|
|
unsigned char *usb_rx_buf;
|
|
|
|
unsigned char *usb_tx_buf;
|
2013-02-06 00:24:24 +00:00
|
|
|
enum vmk80xx_model model;
|
2009-05-10 10:00:43 +00:00
|
|
|
};
|
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
static void vmk80xx_do_bulk_msg(struct comedi_device *dev)
|
2009-05-10 10:00:43 +00:00
|
|
|
{
|
2013-05-20 21:24:32 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2013-05-20 21:26:01 +00:00
|
|
|
struct usb_device *usb = comedi_to_usb_dev(dev);
|
2011-05-12 18:09:57 +00:00
|
|
|
__u8 tx_addr;
|
|
|
|
__u8 rx_addr;
|
|
|
|
unsigned int tx_pipe;
|
|
|
|
unsigned int rx_pipe;
|
2021-10-25 11:45:31 +00:00
|
|
|
size_t tx_size;
|
|
|
|
size_t rx_size;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
tx_addr = devpriv->ep_tx->bEndpointAddress;
|
|
|
|
rx_addr = devpriv->ep_rx->bEndpointAddress;
|
|
|
|
tx_pipe = usb_sndbulkpipe(usb, tx_addr);
|
|
|
|
rx_pipe = usb_rcvbulkpipe(usb, rx_addr);
|
2021-10-25 11:45:31 +00:00
|
|
|
tx_size = usb_endpoint_maxp(devpriv->ep_tx);
|
|
|
|
rx_size = usb_endpoint_maxp(devpriv->ep_rx);
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2021-10-25 11:45:32 +00:00
|
|
|
usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf, tx_size, NULL,
|
|
|
|
PACKET_TIMEOUT);
|
2021-10-25 11:45:31 +00:00
|
|
|
|
2021-10-25 11:45:32 +00:00
|
|
|
usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, rx_size, NULL,
|
|
|
|
PACKET_TIMEOUT);
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
static int vmk80xx_read_packet(struct comedi_device *dev)
|
2009-04-22 17:28:18 +00:00
|
|
|
{
|
2013-05-20 21:24:32 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2013-05-20 21:26:01 +00:00
|
|
|
struct usb_device *usb = comedi_to_usb_dev(dev);
|
2013-02-18 11:15:55 +00:00
|
|
|
struct usb_endpoint_descriptor *ep;
|
|
|
|
unsigned int pipe;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL) {
|
2013-05-20 21:24:32 +00:00
|
|
|
vmk80xx_do_bulk_msg(dev);
|
2009-05-10 10:00:43 +00:00
|
|
|
return 0;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-18 11:15:55 +00:00
|
|
|
ep = devpriv->ep_rx;
|
|
|
|
pipe = usb_rcvintpipe(usb, ep->bEndpointAddress);
|
|
|
|
return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf,
|
2016-07-22 15:29:39 +00:00
|
|
|
usb_endpoint_maxp(ep), NULL,
|
2021-10-25 11:45:32 +00:00
|
|
|
PACKET_TIMEOUT);
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
static int vmk80xx_write_packet(struct comedi_device *dev, int cmd)
|
2009-04-22 17:28:18 +00:00
|
|
|
{
|
2013-05-20 21:24:32 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2013-05-20 21:26:01 +00:00
|
|
|
struct usb_device *usb = comedi_to_usb_dev(dev);
|
2013-02-18 11:15:55 +00:00
|
|
|
struct usb_endpoint_descriptor *ep;
|
|
|
|
unsigned int pipe;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-18 11:15:55 +00:00
|
|
|
devpriv->usb_tx_buf[0] = cmd;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL) {
|
2013-05-20 21:24:32 +00:00
|
|
|
vmk80xx_do_bulk_msg(dev);
|
2009-05-10 10:00:43 +00:00
|
|
|
return 0;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-18 11:15:55 +00:00
|
|
|
ep = devpriv->ep_tx;
|
|
|
|
pipe = usb_sndintpipe(usb, ep->bEndpointAddress);
|
|
|
|
return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf,
|
2016-07-22 15:29:39 +00:00
|
|
|
usb_endpoint_maxp(ep), NULL,
|
2021-10-25 11:45:32 +00:00
|
|
|
PACKET_TIMEOUT);
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
static int vmk80xx_reset_device(struct comedi_device *dev)
|
2013-02-14 16:42:14 +00:00
|
|
|
{
|
2013-05-20 21:24:32 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2013-02-14 16:42:14 +00:00
|
|
|
size_t size;
|
2013-02-14 16:42:15 +00:00
|
|
|
int retval;
|
2013-02-14 16:42:14 +00:00
|
|
|
|
2016-07-22 15:29:39 +00:00
|
|
|
size = usb_endpoint_maxp(devpriv->ep_tx);
|
2013-02-14 16:42:14 +00:00
|
|
|
memset(devpriv->usb_tx_buf, 0, size);
|
2013-05-20 21:24:32 +00:00
|
|
|
retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST);
|
2013-02-14 16:42:15 +00:00
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
/* set outputs to known state as we cannot read them */
|
2013-05-20 21:24:32 +00:00
|
|
|
return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD);
|
2013-02-14 16:42:14 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:24:45 +00:00
|
|
|
static int vmk80xx_ai_insn_read(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-04-22 17:28:18 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2011-05-12 18:09:57 +00:00
|
|
|
int chan;
|
|
|
|
int reg[2];
|
2009-05-10 10:00:43 +00:00
|
|
|
int n;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
chan = CR_CHAN(insn->chanspec);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
switch (devpriv->model) {
|
2009-05-10 10:00:43 +00:00
|
|
|
case VMK8055_MODEL:
|
|
|
|
if (!chan)
|
|
|
|
reg[0] = VMK8055_AI1_REG;
|
|
|
|
else
|
|
|
|
reg[0] = VMK8055_AI2_REG;
|
|
|
|
break;
|
|
|
|
case VMK8061_MODEL:
|
2012-09-06 18:21:48 +00:00
|
|
|
default:
|
2009-05-10 10:00:43 +00:00
|
|
|
reg[0] = VMK8061_AI_REG1;
|
|
|
|
reg[1] = VMK8061_AI_REG2;
|
2013-02-06 00:20:58 +00:00
|
|
|
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI;
|
|
|
|
devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
for (n = 0; n < insn->n; n++) {
|
2013-05-20 21:24:32 +00:00
|
|
|
if (vmk80xx_read_packet(dev))
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8055_MODEL) {
|
2013-02-06 00:20:58 +00:00
|
|
|
data[n] = devpriv->usb_rx_buf[reg[0]];
|
2009-05-10 10:00:43 +00:00
|
|
|
continue;
|
|
|
|
}
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
/* VMK8061_MODEL */
|
2013-02-06 00:20:58 +00:00
|
|
|
data[n] = devpriv->usb_rx_buf[reg[0]] + 256 *
|
|
|
|
devpriv->usb_rx_buf[reg[1]];
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
return n;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:25:07 +00:00
|
|
|
static int vmk80xx_ao_insn_write(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-04-22 17:28:18 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2011-05-12 18:09:57 +00:00
|
|
|
int chan;
|
|
|
|
int cmd;
|
|
|
|
int reg;
|
2009-05-10 10:00:43 +00:00
|
|
|
int n;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
chan = CR_CHAN(insn->chanspec);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
switch (devpriv->model) {
|
2009-05-10 10:00:43 +00:00
|
|
|
case VMK8055_MODEL:
|
|
|
|
cmd = VMK8055_CMD_WRT_AD;
|
|
|
|
if (!chan)
|
|
|
|
reg = VMK8055_AO1_REG;
|
|
|
|
else
|
|
|
|
reg = VMK8055_AO2_REG;
|
|
|
|
break;
|
2009-06-08 15:34:41 +00:00
|
|
|
default: /* NOTE: avoid compiler warnings */
|
2009-05-10 10:00:43 +00:00
|
|
|
cmd = VMK8061_CMD_SET_AO;
|
|
|
|
reg = VMK8061_AO_REG;
|
2013-02-06 00:20:58 +00:00
|
|
|
devpriv->usb_tx_buf[VMK8061_CH_REG] = chan;
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
for (n = 0; n < insn->n; n++) {
|
2013-02-06 00:20:58 +00:00
|
|
|
devpriv->usb_tx_buf[reg] = data[n];
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
if (vmk80xx_write_packet(dev, cmd))
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
return n;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:25:07 +00:00
|
|
|
static int vmk80xx_ao_insn_read(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-04-22 17:28:18 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2011-05-12 18:09:57 +00:00
|
|
|
int chan;
|
|
|
|
int reg;
|
2009-05-10 10:00:43 +00:00
|
|
|
int n;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
chan = CR_CHAN(insn->chanspec);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
reg = VMK8061_AO_REG - 1;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO;
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
for (n = 0; n < insn->n; n++) {
|
2013-05-20 21:24:32 +00:00
|
|
|
if (vmk80xx_read_packet(dev))
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
data[n] = devpriv->usb_rx_buf[reg + chan];
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
return n;
|
|
|
|
}
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:25:28 +00:00
|
|
|
static int vmk80xx_di_insn_bits(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2011-05-18 18:18:55 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2011-05-18 18:18:55 +00:00
|
|
|
unsigned char *rx_buf;
|
|
|
|
int reg;
|
|
|
|
int retval;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2011-05-18 18:18:55 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
rx_buf = devpriv->usb_rx_buf;
|
2011-05-18 18:18:55 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL) {
|
2011-05-18 18:18:55 +00:00
|
|
|
reg = VMK8061_DI_REG;
|
2013-02-06 00:20:58 +00:00
|
|
|
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI;
|
2011-05-18 18:18:55 +00:00
|
|
|
} else {
|
|
|
|
reg = VMK8055_DI_REG;
|
|
|
|
}
|
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
retval = vmk80xx_read_packet(dev);
|
2011-05-18 18:18:55 +00:00
|
|
|
|
|
|
|
if (!retval) {
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8055_MODEL)
|
2011-05-18 18:18:55 +00:00
|
|
|
data[1] = (((rx_buf[reg] >> 4) & 0x03) |
|
|
|
|
((rx_buf[reg] << 2) & 0x04) |
|
|
|
|
((rx_buf[reg] >> 3) & 0x18));
|
|
|
|
else
|
|
|
|
data[1] = rx_buf[reg];
|
|
|
|
|
|
|
|
retval = 2;
|
|
|
|
}
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2011-05-18 18:18:55 +00:00
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2013-02-06 00:25:49 +00:00
|
|
|
static int vmk80xx_do_insn_bits(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2011-05-18 18:18:55 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2013-08-30 18:06:58 +00:00
|
|
|
unsigned char *rx_buf = devpriv->usb_rx_buf;
|
|
|
|
unsigned char *tx_buf = devpriv->usb_tx_buf;
|
2013-02-18 11:15:55 +00:00
|
|
|
int reg, cmd;
|
2013-11-11 11:31:51 +00:00
|
|
|
int ret = 0;
|
2011-05-18 18:18:55 +00:00
|
|
|
|
2013-02-13 14:28:14 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL) {
|
|
|
|
reg = VMK8061_DO_REG;
|
|
|
|
cmd = VMK8061_CMD_DO;
|
|
|
|
} else { /* VMK8055_MODEL */
|
|
|
|
reg = VMK8055_DO_REG;
|
|
|
|
cmd = VMK8055_CMD_WRT_AD;
|
|
|
|
}
|
2011-05-18 18:18:55 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2011-05-18 18:18:55 +00:00
|
|
|
|
2013-08-30 18:06:58 +00:00
|
|
|
if (comedi_dio_update_state(s, data)) {
|
|
|
|
tx_buf[reg] = s->state;
|
|
|
|
ret = vmk80xx_write_packet(dev, cmd);
|
|
|
|
if (ret)
|
2011-05-18 18:18:55 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL) {
|
2011-05-18 18:18:55 +00:00
|
|
|
tx_buf[0] = VMK8061_CMD_RD_DO;
|
2013-08-30 18:06:58 +00:00
|
|
|
ret = vmk80xx_read_packet(dev);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
data[1] = rx_buf[reg];
|
2011-05-18 18:18:55 +00:00
|
|
|
} else {
|
2013-08-30 18:06:58 +00:00
|
|
|
data[1] = s->state;
|
2011-05-18 18:18:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2011-05-18 18:18:55 +00:00
|
|
|
|
2013-08-30 18:06:58 +00:00
|
|
|
return ret ? ret : insn->n;
|
2011-05-18 18:18:55 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:27:37 +00:00
|
|
|
static int vmk80xx_cnt_insn_read(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-05-10 10:00:43 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2011-05-12 18:09:57 +00:00
|
|
|
int chan;
|
|
|
|
int reg[2];
|
2009-05-10 10:00:43 +00:00
|
|
|
int n;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
chan = CR_CHAN(insn->chanspec);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
switch (devpriv->model) {
|
2009-05-10 10:00:43 +00:00
|
|
|
case VMK8055_MODEL:
|
|
|
|
if (!chan)
|
|
|
|
reg[0] = VMK8055_CNT1_REG;
|
|
|
|
else
|
|
|
|
reg[0] = VMK8055_CNT2_REG;
|
|
|
|
break;
|
|
|
|
case VMK8061_MODEL:
|
2012-09-06 18:21:48 +00:00
|
|
|
default:
|
2009-05-10 10:00:43 +00:00
|
|
|
reg[0] = VMK8061_CNT_REG;
|
|
|
|
reg[1] = VMK8061_CNT_REG;
|
2013-02-06 00:20:58 +00:00
|
|
|
devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT;
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
for (n = 0; n < insn->n; n++) {
|
2013-05-20 21:24:32 +00:00
|
|
|
if (vmk80xx_read_packet(dev))
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8055_MODEL)
|
2013-02-06 00:20:58 +00:00
|
|
|
data[n] = devpriv->usb_rx_buf[reg[0]];
|
2011-05-12 18:09:57 +00:00
|
|
|
else /* VMK8061_MODEL */
|
2013-02-06 00:20:58 +00:00
|
|
|
data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1]
|
|
|
|
+ 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2];
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
return n;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:27:37 +00:00
|
|
|
static int vmk80xx_cnt_insn_config(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-04-22 17:28:18 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2015-01-22 00:22:14 +00:00
|
|
|
unsigned int chan = CR_CHAN(insn->chanspec);
|
2011-05-12 18:09:57 +00:00
|
|
|
int cmd;
|
|
|
|
int reg;
|
2015-01-22 00:22:14 +00:00
|
|
|
int ret;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2015-01-22 00:22:14 +00:00
|
|
|
switch (data[0]) {
|
|
|
|
case INSN_CONFIG_RESET:
|
|
|
|
if (devpriv->model == VMK8055_MODEL) {
|
|
|
|
if (!chan) {
|
|
|
|
cmd = VMK8055_CMD_RST_CNT1;
|
|
|
|
reg = VMK8055_CNT1_REG;
|
|
|
|
} else {
|
|
|
|
cmd = VMK8055_CMD_RST_CNT2;
|
|
|
|
reg = VMK8055_CNT2_REG;
|
|
|
|
}
|
|
|
|
devpriv->usb_tx_buf[reg] = 0x00;
|
2009-05-10 10:00:43 +00:00
|
|
|
} else {
|
2015-01-22 00:22:14 +00:00
|
|
|
cmd = VMK8061_CMD_RST_CNT;
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
2015-01-22 00:22:14 +00:00
|
|
|
ret = vmk80xx_write_packet(dev, cmd);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
2011-05-12 18:09:57 +00:00
|
|
|
}
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2015-01-22 00:22:14 +00:00
|
|
|
return ret ? ret : insn->n;
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:27:37 +00:00
|
|
|
static int vmk80xx_cnt_insn_write(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-05-10 10:00:43 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2011-05-12 18:09:57 +00:00
|
|
|
unsigned long debtime;
|
|
|
|
unsigned long val;
|
|
|
|
int chan;
|
|
|
|
int cmd;
|
2009-05-10 10:00:43 +00:00
|
|
|
int n;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
chan = CR_CHAN(insn->chanspec);
|
|
|
|
|
|
|
|
if (!chan)
|
|
|
|
cmd = VMK8055_CMD_DEB1_TIME;
|
|
|
|
else
|
|
|
|
cmd = VMK8055_CMD_DEB2_TIME;
|
|
|
|
|
|
|
|
for (n = 0; n < insn->n; n++) {
|
|
|
|
debtime = data[n];
|
2009-04-22 17:28:18 +00:00
|
|
|
if (debtime == 0)
|
|
|
|
debtime = 1;
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
/* TODO: Prevent overflows */
|
|
|
|
if (debtime > 7450)
|
|
|
|
debtime = 7450;
|
|
|
|
|
2009-04-22 17:28:18 +00:00
|
|
|
val = int_sqrt(debtime * 1000 / 115);
|
|
|
|
if (((val + 1) * val) < debtime * 1000 / 115)
|
|
|
|
val += 1;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
devpriv->usb_tx_buf[6 + chan] = val;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
if (vmk80xx_write_packet(dev, cmd))
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
return n;
|
|
|
|
}
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:27:56 +00:00
|
|
|
static int vmk80xx_pwm_insn_read(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-05-10 10:00:43 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
|
|
|
unsigned char *tx_buf;
|
|
|
|
unsigned char *rx_buf;
|
2009-05-10 10:00:43 +00:00
|
|
|
int reg[2];
|
|
|
|
int n;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
|
|
|
|
|
|
|
tx_buf = devpriv->usb_tx_buf;
|
|
|
|
rx_buf = devpriv->usb_rx_buf;
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
reg[0] = VMK8061_PWM_REG1;
|
|
|
|
reg[1] = VMK8061_PWM_REG2;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
tx_buf[0] = VMK8061_CMD_RD_PWM;
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
for (n = 0; n < insn->n; n++) {
|
2013-05-20 21:24:32 +00:00
|
|
|
if (vmk80xx_read_packet(dev))
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]];
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
return n;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2013-02-06 00:27:56 +00:00
|
|
|
static int vmk80xx_pwm_insn_write(struct comedi_device *dev,
|
|
|
|
struct comedi_subdevice *s,
|
|
|
|
struct comedi_insn *insn,
|
|
|
|
unsigned int *data)
|
2009-05-10 10:00:43 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2009-05-10 10:00:43 +00:00
|
|
|
unsigned char *tx_buf;
|
2011-05-12 18:09:57 +00:00
|
|
|
int reg[2];
|
|
|
|
int cmd;
|
2009-05-10 10:00:43 +00:00
|
|
|
int n;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
tx_buf = devpriv->usb_tx_buf;
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
reg[0] = VMK8061_PWM_REG1;
|
|
|
|
reg[1] = VMK8061_PWM_REG2;
|
|
|
|
|
|
|
|
cmd = VMK8061_CMD_OUT_PWM;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The followin piece of code was translated from the inline
|
|
|
|
* assembler code in the DLL source code.
|
|
|
|
*
|
|
|
|
* asm
|
|
|
|
* mov eax, k ; k is the value (data[n])
|
|
|
|
* and al, 03h ; al are the lower 8 bits of eax
|
|
|
|
* mov lo, al ; lo is the low part (tx_buf[reg[0]])
|
|
|
|
* mov eax, k
|
|
|
|
* shr eax, 2 ; right shift eax register by 2
|
|
|
|
* mov hi, al ; hi is the high part (tx_buf[reg[1]])
|
|
|
|
* end;
|
|
|
|
*/
|
|
|
|
for (n = 0; n < insn->n; n++) {
|
|
|
|
tx_buf[reg[0]] = (unsigned char)(data[n] & 0x03);
|
|
|
|
tx_buf[reg[1]] = (unsigned char)(data[n] >> 2) & 0xff;
|
|
|
|
|
2013-05-20 21:24:32 +00:00
|
|
|
if (vmk80xx_write_packet(dev, cmd))
|
2009-05-10 10:00:43 +00:00
|
|
|
break;
|
|
|
|
}
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2009-05-10 10:00:43 +00:00
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2013-02-06 00:24:03 +00:00
|
|
|
static int vmk80xx_find_usb_endpoints(struct comedi_device *dev)
|
2013-02-06 00:21:19 +00:00
|
|
|
{
|
2013-02-06 00:24:03 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2013-05-20 21:27:13 +00:00
|
|
|
struct usb_interface *intf = comedi_to_usb_interface(dev);
|
2013-02-06 00:21:19 +00:00
|
|
|
struct usb_host_interface *iface_desc = intf->cur_altsetting;
|
2024-04-08 17:16:33 +00:00
|
|
|
struct usb_endpoint_descriptor *ep_rx_desc, *ep_tx_desc;
|
|
|
|
int ret;
|
2013-02-06 00:21:19 +00:00
|
|
|
|
2024-04-08 17:16:33 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL)
|
|
|
|
ret = usb_find_common_endpoints(iface_desc, &ep_rx_desc,
|
|
|
|
&ep_tx_desc, NULL, NULL);
|
|
|
|
else
|
|
|
|
ret = usb_find_common_endpoints(iface_desc, NULL, NULL,
|
|
|
|
&ep_rx_desc, &ep_tx_desc);
|
2013-02-06 00:21:19 +00:00
|
|
|
|
2024-04-08 17:16:33 +00:00
|
|
|
if (ret)
|
2013-02-06 00:21:19 +00:00
|
|
|
return -ENODEV;
|
|
|
|
|
2024-04-08 17:16:33 +00:00
|
|
|
devpriv->ep_rx = ep_rx_desc;
|
|
|
|
devpriv->ep_tx = ep_tx_desc;
|
|
|
|
|
2020-10-10 08:29:32 +00:00
|
|
|
if (!usb_endpoint_maxp(devpriv->ep_rx) || !usb_endpoint_maxp(devpriv->ep_tx))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2013-02-06 00:21:19 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-06 00:24:03 +00:00
|
|
|
static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev)
|
2013-02-06 00:21:40 +00:00
|
|
|
{
|
2013-02-06 00:24:03 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2013-02-06 00:21:40 +00:00
|
|
|
size_t size;
|
|
|
|
|
2021-10-25 11:45:30 +00:00
|
|
|
size = max(usb_endpoint_maxp(devpriv->ep_rx), MIN_BUF_SIZE);
|
2013-02-14 16:42:13 +00:00
|
|
|
devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
|
2013-02-06 00:21:40 +00:00
|
|
|
if (!devpriv->usb_rx_buf)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2022-06-07 17:18:19 +00:00
|
|
|
size = max(usb_endpoint_maxp(devpriv->ep_tx), MIN_BUF_SIZE);
|
2013-02-14 16:42:13 +00:00
|
|
|
devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
|
2019-04-15 11:52:30 +00:00
|
|
|
if (!devpriv->usb_tx_buf)
|
2013-02-06 00:21:40 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-06 00:31:10 +00:00
|
|
|
static int vmk80xx_init_subdevices(struct comedi_device *dev)
|
2009-04-22 17:28:18 +00:00
|
|
|
{
|
2015-06-18 17:54:56 +00:00
|
|
|
const struct vmk80xx_board *board = dev->board_ptr;
|
2013-02-06 00:24:03 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2009-04-22 17:29:33 +00:00
|
|
|
struct comedi_subdevice *s;
|
2013-02-06 00:24:03 +00:00
|
|
|
int n_subd;
|
2012-06-12 18:59:33 +00:00
|
|
|
int ret;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
down(&devpriv->limit_sem);
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8055_MODEL)
|
2009-05-10 10:00:43 +00:00
|
|
|
n_subd = 5;
|
|
|
|
else
|
|
|
|
n_subd = 6;
|
2013-02-06 00:20:58 +00:00
|
|
|
ret = comedi_alloc_subdevices(dev, n_subd);
|
2012-06-12 18:59:33 +00:00
|
|
|
if (ret) {
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2012-06-12 18:59:33 +00:00
|
|
|
return ret;
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
/* Analog input subdevice */
|
2013-02-06 00:20:58 +00:00
|
|
|
s = &dev->subdevices[0];
|
2013-02-06 00:24:45 +00:00
|
|
|
s->type = COMEDI_SUBD_AI;
|
|
|
|
s->subdev_flags = SDF_READABLE | SDF_GROUND;
|
2015-06-18 17:54:56 +00:00
|
|
|
s->n_chan = board->ai_nchans;
|
|
|
|
s->maxdata = board->ai_maxdata;
|
|
|
|
s->range_table = board->range;
|
2013-02-06 00:24:45 +00:00
|
|
|
s->insn_read = vmk80xx_ai_insn_read;
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
/* Analog output subdevice */
|
2013-02-06 00:20:58 +00:00
|
|
|
s = &dev->subdevices[1];
|
2013-02-06 00:25:07 +00:00
|
|
|
s->type = COMEDI_SUBD_AO;
|
2014-10-30 18:19:34 +00:00
|
|
|
s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
|
2015-06-18 17:54:56 +00:00
|
|
|
s->n_chan = board->ao_nchans;
|
2013-02-06 00:25:07 +00:00
|
|
|
s->maxdata = 0x00ff;
|
2015-06-18 17:54:56 +00:00
|
|
|
s->range_table = board->range;
|
2013-02-06 00:25:07 +00:00
|
|
|
s->insn_write = vmk80xx_ao_insn_write;
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL) {
|
2013-02-06 00:25:07 +00:00
|
|
|
s->subdev_flags |= SDF_READABLE;
|
|
|
|
s->insn_read = vmk80xx_ao_insn_read;
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
/* Digital input subdevice */
|
2013-02-06 00:20:58 +00:00
|
|
|
s = &dev->subdevices[2];
|
2013-02-06 00:25:28 +00:00
|
|
|
s->type = COMEDI_SUBD_DI;
|
|
|
|
s->subdev_flags = SDF_READABLE;
|
2015-06-18 17:54:56 +00:00
|
|
|
s->n_chan = board->di_nchans;
|
2013-02-06 00:25:28 +00:00
|
|
|
s->maxdata = 1;
|
|
|
|
s->range_table = &range_digital;
|
|
|
|
s->insn_bits = vmk80xx_di_insn_bits;
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
/* Digital output subdevice */
|
2013-02-06 00:20:58 +00:00
|
|
|
s = &dev->subdevices[3];
|
2013-02-06 00:25:49 +00:00
|
|
|
s->type = COMEDI_SUBD_DO;
|
2014-10-30 18:19:34 +00:00
|
|
|
s->subdev_flags = SDF_WRITABLE;
|
2013-02-06 00:25:49 +00:00
|
|
|
s->n_chan = 8;
|
|
|
|
s->maxdata = 1;
|
|
|
|
s->range_table = &range_digital;
|
|
|
|
s->insn_bits = vmk80xx_do_insn_bits;
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
/* Counter subdevice */
|
2013-02-06 00:20:58 +00:00
|
|
|
s = &dev->subdevices[4];
|
2013-02-06 00:27:37 +00:00
|
|
|
s->type = COMEDI_SUBD_COUNTER;
|
|
|
|
s->subdev_flags = SDF_READABLE;
|
|
|
|
s->n_chan = 2;
|
2015-06-18 17:54:56 +00:00
|
|
|
s->maxdata = board->cnt_maxdata;
|
2013-02-06 00:27:37 +00:00
|
|
|
s->insn_read = vmk80xx_cnt_insn_read;
|
|
|
|
s->insn_config = vmk80xx_cnt_insn_config;
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8055_MODEL) {
|
2014-10-30 18:19:34 +00:00
|
|
|
s->subdev_flags |= SDF_WRITABLE;
|
2013-02-06 00:27:37 +00:00
|
|
|
s->insn_write = vmk80xx_cnt_insn_write;
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2009-05-10 10:00:43 +00:00
|
|
|
/* PWM subdevice */
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8061_MODEL) {
|
2013-02-06 00:20:58 +00:00
|
|
|
s = &dev->subdevices[5];
|
2013-02-06 00:27:56 +00:00
|
|
|
s->type = COMEDI_SUBD_PWM;
|
2014-10-30 18:19:34 +00:00
|
|
|
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
|
2015-06-18 17:54:56 +00:00
|
|
|
s->n_chan = board->pwm_nchans;
|
|
|
|
s->maxdata = board->pwm_maxdata;
|
2013-02-06 00:27:56 +00:00
|
|
|
s->insn_read = vmk80xx_pwm_insn_read;
|
|
|
|
s->insn_write = vmk80xx_pwm_insn_write;
|
2009-05-10 10:00:43 +00:00
|
|
|
}
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
up(&devpriv->limit_sem);
|
2013-02-06 00:19:18 +00:00
|
|
|
|
2012-06-18 16:36:58 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
static int vmk80xx_auto_attach(struct comedi_device *dev,
|
2013-02-06 00:24:03 +00:00
|
|
|
unsigned long context)
|
2012-06-18 16:36:58 +00:00
|
|
|
{
|
2013-02-06 00:20:58 +00:00
|
|
|
struct usb_interface *intf = comedi_to_usb_interface(dev);
|
2015-06-18 17:54:56 +00:00
|
|
|
const struct vmk80xx_board *board = NULL;
|
2013-02-06 00:20:58 +00:00
|
|
|
struct vmk80xx_private *devpriv;
|
2013-02-06 00:21:19 +00:00
|
|
|
int ret;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2015-06-18 17:54:57 +00:00
|
|
|
if (context < ARRAY_SIZE(vmk80xx_boardinfo))
|
|
|
|
board = &vmk80xx_boardinfo[context];
|
|
|
|
if (!board)
|
|
|
|
return -ENODEV;
|
2015-06-18 17:54:56 +00:00
|
|
|
dev->board_ptr = board;
|
|
|
|
dev->board_name = board->name;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-06-24 23:55:44 +00:00
|
|
|
devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
|
2013-02-06 00:24:03 +00:00
|
|
|
if (!devpriv)
|
|
|
|
return -ENOMEM;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2015-06-18 17:54:56 +00:00
|
|
|
devpriv->model = board->model;
|
2009-05-10 10:00:43 +00:00
|
|
|
|
staging: comedi: vmk80xx: Fix use of uninitialized semaphore
If `vmk80xx_auto_attach()` returns an error, the core comedi module code
will call `vmk80xx_detach()` to clean up. If `vmk80xx_auto_attach()`
successfully allocated the comedi device private data,
`vmk80xx_detach()` assumes that a `struct semaphore limit_sem` contained
in the private data has been initialized and uses it. Unfortunately,
there are a couple of places where `vmk80xx_auto_attach()` can return an
error after allocating the device private data but before initializing
the semaphore, so this assumption is invalid. Fix it by initializing
the semaphore just after allocating the private data in
`vmk80xx_auto_attach()` before any other errors can be returned.
I believe this was the cause of the following syzbot crash report
<https://syzkaller.appspot.com/bug?extid=54c2f58f15fe6876b6ad>:
usb 1-1: config 0 has no interface number 0
usb 1-1: New USB device found, idVendor=10cf, idProduct=8068, bcdDevice=e6.8d
usb 1-1: New USB device strings: Mfr=0, Product=0, SerialNumber=0
usb 1-1: config 0 descriptor??
vmk80xx 1-1:0.117: driver 'vmk80xx' failed to auto-configure device.
INFO: trying to register non-static key.
the code is fine but needs lockdep annotation.
turning off the locking correctness validator.
CPU: 0 PID: 12 Comm: kworker/0:1 Not tainted 5.1.0-rc4-319354-g9a33b36 #3
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Workqueue: usb_hub_wq hub_event
Call Trace:
__dump_stack lib/dump_stack.c:77 [inline]
dump_stack+0xe8/0x16e lib/dump_stack.c:113
assign_lock_key kernel/locking/lockdep.c:786 [inline]
register_lock_class+0x11b8/0x1250 kernel/locking/lockdep.c:1095
__lock_acquire+0xfb/0x37c0 kernel/locking/lockdep.c:3582
lock_acquire+0x10d/0x2f0 kernel/locking/lockdep.c:4211
__raw_spin_lock_irqsave include/linux/spinlock_api_smp.h:110 [inline]
_raw_spin_lock_irqsave+0x44/0x60 kernel/locking/spinlock.c:152
down+0x12/0x80 kernel/locking/semaphore.c:58
vmk80xx_detach+0x59/0x100 drivers/staging/comedi/drivers/vmk80xx.c:829
comedi_device_detach+0xed/0x800 drivers/staging/comedi/drivers.c:204
comedi_device_cleanup.part.0+0x68/0x140 drivers/staging/comedi/comedi_fops.c:156
comedi_device_cleanup drivers/staging/comedi/comedi_fops.c:187 [inline]
comedi_free_board_dev.part.0+0x16/0x90 drivers/staging/comedi/comedi_fops.c:190
comedi_free_board_dev drivers/staging/comedi/comedi_fops.c:189 [inline]
comedi_release_hardware_device+0x111/0x140 drivers/staging/comedi/comedi_fops.c:2880
comedi_auto_config.cold+0x124/0x1b0 drivers/staging/comedi/drivers.c:1068
usb_probe_interface+0x31d/0x820 drivers/usb/core/driver.c:361
really_probe+0x2da/0xb10 drivers/base/dd.c:509
driver_probe_device+0x21d/0x350 drivers/base/dd.c:671
__device_attach_driver+0x1d8/0x290 drivers/base/dd.c:778
bus_for_each_drv+0x163/0x1e0 drivers/base/bus.c:454
__device_attach+0x223/0x3a0 drivers/base/dd.c:844
bus_probe_device+0x1f1/0x2a0 drivers/base/bus.c:514
device_add+0xad2/0x16e0 drivers/base/core.c:2106
usb_set_configuration+0xdf7/0x1740 drivers/usb/core/message.c:2021
generic_probe+0xa2/0xda drivers/usb/core/generic.c:210
usb_probe_device+0xc0/0x150 drivers/usb/core/driver.c:266
really_probe+0x2da/0xb10 drivers/base/dd.c:509
driver_probe_device+0x21d/0x350 drivers/base/dd.c:671
__device_attach_driver+0x1d8/0x290 drivers/base/dd.c:778
bus_for_each_drv+0x163/0x1e0 drivers/base/bus.c:454
__device_attach+0x223/0x3a0 drivers/base/dd.c:844
bus_probe_device+0x1f1/0x2a0 drivers/base/bus.c:514
device_add+0xad2/0x16e0 drivers/base/core.c:2106
usb_new_device.cold+0x537/0xccf drivers/usb/core/hub.c:2534
hub_port_connect drivers/usb/core/hub.c:5089 [inline]
hub_port_connect_change drivers/usb/core/hub.c:5204 [inline]
port_event drivers/usb/core/hub.c:5350 [inline]
hub_event+0x138e/0x3b00 drivers/usb/core/hub.c:5432
process_one_work+0x90f/0x1580 kernel/workqueue.c:2269
worker_thread+0x9b/0xe20 kernel/workqueue.c:2415
kthread+0x313/0x420 kernel/kthread.c:253
ret_from_fork+0x3a/0x50 arch/x86/entry/entry_64.S:352
Reported-by: syzbot+54c2f58f15fe6876b6ad@syzkaller.appspotmail.com
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2019-04-15 11:10:14 +00:00
|
|
|
sema_init(&devpriv->limit_sem, 8);
|
|
|
|
|
2013-02-06 00:24:03 +00:00
|
|
|
ret = vmk80xx_find_usb_endpoints(dev);
|
2013-02-06 00:22:27 +00:00
|
|
|
if (ret)
|
2013-02-06 00:24:03 +00:00
|
|
|
return ret;
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:03 +00:00
|
|
|
ret = vmk80xx_alloc_usb_buffers(dev);
|
2013-02-06 00:22:27 +00:00
|
|
|
if (ret)
|
2013-02-06 00:24:03 +00:00
|
|
|
return ret;
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2013-02-06 00:20:58 +00:00
|
|
|
usb_set_intfdata(intf, devpriv);
|
2009-05-10 10:00:43 +00:00
|
|
|
|
2013-02-06 00:24:24 +00:00
|
|
|
if (devpriv->model == VMK8055_MODEL)
|
2013-05-20 21:24:32 +00:00
|
|
|
vmk80xx_reset_device(dev);
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:31:10 +00:00
|
|
|
return vmk80xx_init_subdevices(dev);
|
2013-02-06 00:24:03 +00:00
|
|
|
}
|
2009-04-22 17:28:18 +00:00
|
|
|
|
2013-02-06 00:24:03 +00:00
|
|
|
static void vmk80xx_detach(struct comedi_device *dev)
|
|
|
|
{
|
2013-05-20 21:27:13 +00:00
|
|
|
struct usb_interface *intf = comedi_to_usb_interface(dev);
|
2013-02-06 00:24:03 +00:00
|
|
|
struct vmk80xx_private *devpriv = dev->private;
|
2011-05-18 18:18:56 +00:00
|
|
|
|
2013-02-06 00:24:03 +00:00
|
|
|
if (!devpriv)
|
|
|
|
return;
|
2013-02-06 00:22:27 +00:00
|
|
|
|
2013-02-06 00:24:03 +00:00
|
|
|
down(&devpriv->limit_sem);
|
|
|
|
|
2013-05-20 21:27:13 +00:00
|
|
|
usb_set_intfdata(intf, NULL);
|
2013-02-06 00:24:03 +00:00
|
|
|
|
|
|
|
kfree(devpriv->usb_rx_buf);
|
|
|
|
kfree(devpriv->usb_tx_buf);
|
|
|
|
|
|
|
|
up(&devpriv->limit_sem);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct comedi_driver vmk80xx_driver = {
|
|
|
|
.module = THIS_MODULE,
|
|
|
|
.driver_name = "vmk80xx",
|
|
|
|
.auto_attach = vmk80xx_auto_attach,
|
|
|
|
.detach = vmk80xx_detach,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int vmk80xx_usb_probe(struct usb_interface *intf,
|
|
|
|
const struct usb_device_id *id)
|
|
|
|
{
|
|
|
|
return comedi_usb_auto_config(intf, &vmk80xx_driver, id->driver_info);
|
2009-04-22 17:28:18 +00:00
|
|
|
}
|
|
|
|
|
2012-06-20 19:01:57 +00:00
|
|
|
static const struct usb_device_id vmk80xx_usb_id_table[] = {
|
|
|
|
{ USB_DEVICE(0x10cf, 0x5500), .driver_info = DEVICE_VMK8055 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x5501), .driver_info = DEVICE_VMK8055 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x5502), .driver_info = DEVICE_VMK8055 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x5503), .driver_info = DEVICE_VMK8055 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8061), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8062), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8063), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8064), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8065), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8066), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8067), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ USB_DEVICE(0x10cf, 0x8068), .driver_info = DEVICE_VMK8061 },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(usb, vmk80xx_usb_id_table);
|
|
|
|
|
2012-06-20 19:01:44 +00:00
|
|
|
static struct usb_driver vmk80xx_usb_driver = {
|
|
|
|
.name = "vmk80xx",
|
2012-06-20 19:01:57 +00:00
|
|
|
.id_table = vmk80xx_usb_id_table,
|
2013-02-06 00:18:26 +00:00
|
|
|
.probe = vmk80xx_usb_probe,
|
|
|
|
.disconnect = comedi_usb_auto_unconfig,
|
2009-04-22 17:28:18 +00:00
|
|
|
};
|
2012-06-20 19:01:44 +00:00
|
|
|
module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver);
|
2012-06-20 19:01:57 +00:00
|
|
|
|
|
|
|
MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>");
|
|
|
|
MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver");
|
|
|
|
MODULE_LICENSE("GPL");
|