| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * 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> |
| */ |
| |
| /* |
| * 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 |
| */ |
| |
| #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> |
| #include <linux/uaccess.h> |
| |
| #include "../comedi_usb.h" |
| |
| enum { |
| DEVICE_VMK8055, |
| DEVICE_VMK8061 |
| }; |
| |
| #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 |
| |
| #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 |
| |
| #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 |
| |
| #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 |
| |
| #define IC3_VERSION BIT(0) |
| #define IC6_VERSION BIT(1) |
| |
| #define MIN_BUF_SIZE 64 |
| #define PACKET_TIMEOUT 10000 /* ms */ |
| |
| enum vmk80xx_model { |
| VMK8055_MODEL, |
| VMK8061_MODEL |
| }; |
| |
| static const struct comedi_lrange vmk8061_range = { |
| 2, { |
| UNI_RANGE(5), |
| UNI_RANGE(10) |
| } |
| }; |
| |
| struct vmk80xx_board { |
| const char *name; |
| enum vmk80xx_model model; |
| const struct comedi_lrange *range; |
| int ai_nchans; |
| unsigned int ai_maxdata; |
| int ao_nchans; |
| int di_nchans; |
| unsigned int cnt_maxdata; |
| int pwm_nchans; |
| unsigned int pwm_maxdata; |
| }; |
| |
| static const struct vmk80xx_board vmk80xx_boardinfo[] = { |
| [DEVICE_VMK8055] = { |
| .name = "K8055 (VM110)", |
| .model = VMK8055_MODEL, |
| .range = &range_unipolar5, |
| .ai_nchans = 2, |
| .ai_maxdata = 0x00ff, |
| .ao_nchans = 2, |
| .di_nchans = 6, |
| .cnt_maxdata = 0xffff, |
| }, |
| [DEVICE_VMK8061] = { |
| .name = "K8061 (VM140)", |
| .model = VMK8061_MODEL, |
| .range = &vmk8061_range, |
| .ai_nchans = 8, |
| .ai_maxdata = 0x03ff, |
| .ao_nchans = 8, |
| .di_nchans = 8, |
| .cnt_maxdata = 0, /* unknown, device is not writeable */ |
| .pwm_nchans = 1, |
| .pwm_maxdata = 0x03ff, |
| }, |
| }; |
| |
| struct vmk80xx_private { |
| 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; |
| enum vmk80xx_model model; |
| }; |
| |
| static void vmk80xx_do_bulk_msg(struct comedi_device *dev) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| struct usb_device *usb = comedi_to_usb_dev(dev); |
| __u8 tx_addr; |
| __u8 rx_addr; |
| unsigned int tx_pipe; |
| unsigned int rx_pipe; |
| size_t tx_size; |
| size_t rx_size; |
| |
| 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); |
| tx_size = usb_endpoint_maxp(devpriv->ep_tx); |
| rx_size = usb_endpoint_maxp(devpriv->ep_rx); |
| |
| usb_bulk_msg(usb, tx_pipe, devpriv->usb_tx_buf, tx_size, NULL, |
| PACKET_TIMEOUT); |
| |
| usb_bulk_msg(usb, rx_pipe, devpriv->usb_rx_buf, rx_size, NULL, |
| PACKET_TIMEOUT); |
| } |
| |
| static int vmk80xx_read_packet(struct comedi_device *dev) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| struct usb_device *usb = comedi_to_usb_dev(dev); |
| struct usb_endpoint_descriptor *ep; |
| unsigned int pipe; |
| |
| if (devpriv->model == VMK8061_MODEL) { |
| vmk80xx_do_bulk_msg(dev); |
| return 0; |
| } |
| |
| ep = devpriv->ep_rx; |
| pipe = usb_rcvintpipe(usb, ep->bEndpointAddress); |
| return usb_interrupt_msg(usb, pipe, devpriv->usb_rx_buf, |
| usb_endpoint_maxp(ep), NULL, |
| PACKET_TIMEOUT); |
| } |
| |
| static int vmk80xx_write_packet(struct comedi_device *dev, int cmd) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| struct usb_device *usb = comedi_to_usb_dev(dev); |
| struct usb_endpoint_descriptor *ep; |
| unsigned int pipe; |
| |
| devpriv->usb_tx_buf[0] = cmd; |
| |
| if (devpriv->model == VMK8061_MODEL) { |
| vmk80xx_do_bulk_msg(dev); |
| return 0; |
| } |
| |
| ep = devpriv->ep_tx; |
| pipe = usb_sndintpipe(usb, ep->bEndpointAddress); |
| return usb_interrupt_msg(usb, pipe, devpriv->usb_tx_buf, |
| usb_endpoint_maxp(ep), NULL, |
| PACKET_TIMEOUT); |
| } |
| |
| static int vmk80xx_reset_device(struct comedi_device *dev) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| size_t size; |
| int retval; |
| |
| size = usb_endpoint_maxp(devpriv->ep_tx); |
| memset(devpriv->usb_tx_buf, 0, size); |
| retval = vmk80xx_write_packet(dev, VMK8055_CMD_RST); |
| if (retval) |
| return retval; |
| /* set outputs to known state as we cannot read them */ |
| return vmk80xx_write_packet(dev, VMK8055_CMD_WRT_AD); |
| } |
| |
| static int vmk80xx_ai_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| int chan; |
| int reg[2]; |
| int n; |
| |
| down(&devpriv->limit_sem); |
| chan = CR_CHAN(insn->chanspec); |
| |
| switch (devpriv->model) { |
| case VMK8055_MODEL: |
| if (!chan) |
| reg[0] = VMK8055_AI1_REG; |
| else |
| reg[0] = VMK8055_AI2_REG; |
| break; |
| case VMK8061_MODEL: |
| default: |
| reg[0] = VMK8061_AI_REG1; |
| reg[1] = VMK8061_AI_REG2; |
| devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AI; |
| devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; |
| break; |
| } |
| |
| for (n = 0; n < insn->n; n++) { |
| if (vmk80xx_read_packet(dev)) |
| break; |
| |
| if (devpriv->model == VMK8055_MODEL) { |
| data[n] = devpriv->usb_rx_buf[reg[0]]; |
| continue; |
| } |
| |
| /* VMK8061_MODEL */ |
| data[n] = devpriv->usb_rx_buf[reg[0]] + 256 * |
| devpriv->usb_rx_buf[reg[1]]; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return n; |
| } |
| |
| static int vmk80xx_ao_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| int chan; |
| int cmd; |
| int reg; |
| int n; |
| |
| down(&devpriv->limit_sem); |
| chan = CR_CHAN(insn->chanspec); |
| |
| switch (devpriv->model) { |
| case VMK8055_MODEL: |
| cmd = VMK8055_CMD_WRT_AD; |
| if (!chan) |
| reg = VMK8055_AO1_REG; |
| else |
| reg = VMK8055_AO2_REG; |
| break; |
| default: /* NOTE: avoid compiler warnings */ |
| cmd = VMK8061_CMD_SET_AO; |
| reg = VMK8061_AO_REG; |
| devpriv->usb_tx_buf[VMK8061_CH_REG] = chan; |
| break; |
| } |
| |
| for (n = 0; n < insn->n; n++) { |
| devpriv->usb_tx_buf[reg] = data[n]; |
| |
| if (vmk80xx_write_packet(dev, cmd)) |
| break; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return n; |
| } |
| |
| static int vmk80xx_ao_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| int chan; |
| int reg; |
| int n; |
| |
| down(&devpriv->limit_sem); |
| chan = CR_CHAN(insn->chanspec); |
| |
| reg = VMK8061_AO_REG - 1; |
| |
| devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_AO; |
| |
| for (n = 0; n < insn->n; n++) { |
| if (vmk80xx_read_packet(dev)) |
| break; |
| |
| data[n] = devpriv->usb_rx_buf[reg + chan]; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return n; |
| } |
| |
| static int vmk80xx_di_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| unsigned char *rx_buf; |
| int reg; |
| int retval; |
| |
| down(&devpriv->limit_sem); |
| |
| rx_buf = devpriv->usb_rx_buf; |
| |
| if (devpriv->model == VMK8061_MODEL) { |
| reg = VMK8061_DI_REG; |
| devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_DI; |
| } else { |
| reg = VMK8055_DI_REG; |
| } |
| |
| retval = vmk80xx_read_packet(dev); |
| |
| if (!retval) { |
| if (devpriv->model == VMK8055_MODEL) |
| 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; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return retval; |
| } |
| |
| static int vmk80xx_do_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| unsigned char *rx_buf = devpriv->usb_rx_buf; |
| unsigned char *tx_buf = devpriv->usb_tx_buf; |
| int reg, cmd; |
| int ret = 0; |
| |
| 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; |
| } |
| |
| down(&devpriv->limit_sem); |
| |
| if (comedi_dio_update_state(s, data)) { |
| tx_buf[reg] = s->state; |
| ret = vmk80xx_write_packet(dev, cmd); |
| if (ret) |
| goto out; |
| } |
| |
| if (devpriv->model == VMK8061_MODEL) { |
| tx_buf[0] = VMK8061_CMD_RD_DO; |
| ret = vmk80xx_read_packet(dev); |
| if (ret) |
| goto out; |
| data[1] = rx_buf[reg]; |
| } else { |
| data[1] = s->state; |
| } |
| |
| out: |
| up(&devpriv->limit_sem); |
| |
| return ret ? ret : insn->n; |
| } |
| |
| static int vmk80xx_cnt_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| int chan; |
| int reg[2]; |
| int n; |
| |
| down(&devpriv->limit_sem); |
| chan = CR_CHAN(insn->chanspec); |
| |
| switch (devpriv->model) { |
| case VMK8055_MODEL: |
| if (!chan) |
| reg[0] = VMK8055_CNT1_REG; |
| else |
| reg[0] = VMK8055_CNT2_REG; |
| break; |
| case VMK8061_MODEL: |
| default: |
| reg[0] = VMK8061_CNT_REG; |
| reg[1] = VMK8061_CNT_REG; |
| devpriv->usb_tx_buf[0] = VMK8061_CMD_RD_CNT; |
| break; |
| } |
| |
| for (n = 0; n < insn->n; n++) { |
| if (vmk80xx_read_packet(dev)) |
| break; |
| |
| if (devpriv->model == VMK8055_MODEL) |
| data[n] = devpriv->usb_rx_buf[reg[0]]; |
| else /* VMK8061_MODEL */ |
| data[n] = devpriv->usb_rx_buf[reg[0] * (chan + 1) + 1] |
| + 256 * devpriv->usb_rx_buf[reg[1] * 2 + 2]; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return n; |
| } |
| |
| static int vmk80xx_cnt_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| int cmd; |
| int reg; |
| int ret; |
| |
| down(&devpriv->limit_sem); |
| 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; |
| } else { |
| cmd = VMK8061_CMD_RST_CNT; |
| } |
| ret = vmk80xx_write_packet(dev, cmd); |
| break; |
| default: |
| ret = -EINVAL; |
| break; |
| } |
| up(&devpriv->limit_sem); |
| |
| return ret ? ret : insn->n; |
| } |
| |
| static int vmk80xx_cnt_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| unsigned long debtime; |
| unsigned long val; |
| int chan; |
| int cmd; |
| int n; |
| |
| down(&devpriv->limit_sem); |
| 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]; |
| if (debtime == 0) |
| debtime = 1; |
| |
| /* TODO: Prevent overflows */ |
| if (debtime > 7450) |
| debtime = 7450; |
| |
| val = int_sqrt(debtime * 1000 / 115); |
| if (((val + 1) * val) < debtime * 1000 / 115) |
| val += 1; |
| |
| devpriv->usb_tx_buf[6 + chan] = val; |
| |
| if (vmk80xx_write_packet(dev, cmd)) |
| break; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return n; |
| } |
| |
| static int vmk80xx_pwm_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| unsigned char *tx_buf; |
| unsigned char *rx_buf; |
| int reg[2]; |
| int n; |
| |
| down(&devpriv->limit_sem); |
| |
| tx_buf = devpriv->usb_tx_buf; |
| rx_buf = devpriv->usb_rx_buf; |
| |
| reg[0] = VMK8061_PWM_REG1; |
| reg[1] = VMK8061_PWM_REG2; |
| |
| tx_buf[0] = VMK8061_CMD_RD_PWM; |
| |
| for (n = 0; n < insn->n; n++) { |
| if (vmk80xx_read_packet(dev)) |
| break; |
| |
| data[n] = rx_buf[reg[0]] + 4 * rx_buf[reg[1]]; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return n; |
| } |
| |
| static int vmk80xx_pwm_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| unsigned char *tx_buf; |
| int reg[2]; |
| int cmd; |
| int n; |
| |
| down(&devpriv->limit_sem); |
| |
| tx_buf = devpriv->usb_tx_buf; |
| |
| 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; |
| |
| if (vmk80xx_write_packet(dev, cmd)) |
| break; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return n; |
| } |
| |
| static int vmk80xx_find_usb_endpoints(struct comedi_device *dev) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| struct usb_interface *intf = comedi_to_usb_interface(dev); |
| struct usb_host_interface *iface_desc = intf->cur_altsetting; |
| struct usb_endpoint_descriptor *ep_desc; |
| int i; |
| |
| if (iface_desc->desc.bNumEndpoints != 2) |
| return -ENODEV; |
| |
| for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { |
| ep_desc = &iface_desc->endpoint[i].desc; |
| |
| if (usb_endpoint_is_int_in(ep_desc) || |
| usb_endpoint_is_bulk_in(ep_desc)) { |
| if (!devpriv->ep_rx) |
| devpriv->ep_rx = ep_desc; |
| continue; |
| } |
| |
| if (usb_endpoint_is_int_out(ep_desc) || |
| usb_endpoint_is_bulk_out(ep_desc)) { |
| if (!devpriv->ep_tx) |
| devpriv->ep_tx = ep_desc; |
| continue; |
| } |
| } |
| |
| if (!devpriv->ep_rx || !devpriv->ep_tx) |
| return -ENODEV; |
| |
| if (!usb_endpoint_maxp(devpriv->ep_rx) || !usb_endpoint_maxp(devpriv->ep_tx)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int vmk80xx_alloc_usb_buffers(struct comedi_device *dev) |
| { |
| struct vmk80xx_private *devpriv = dev->private; |
| size_t size; |
| |
| size = max(usb_endpoint_maxp(devpriv->ep_rx), MIN_BUF_SIZE); |
| devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL); |
| if (!devpriv->usb_rx_buf) |
| return -ENOMEM; |
| |
| size = max(usb_endpoint_maxp(devpriv->ep_rx), MIN_BUF_SIZE); |
| devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL); |
| if (!devpriv->usb_tx_buf) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| static int vmk80xx_init_subdevices(struct comedi_device *dev) |
| { |
| const struct vmk80xx_board *board = dev->board_ptr; |
| struct vmk80xx_private *devpriv = dev->private; |
| struct comedi_subdevice *s; |
| int n_subd; |
| int ret; |
| |
| down(&devpriv->limit_sem); |
| |
| if (devpriv->model == VMK8055_MODEL) |
| n_subd = 5; |
| else |
| n_subd = 6; |
| ret = comedi_alloc_subdevices(dev, n_subd); |
| if (ret) { |
| up(&devpriv->limit_sem); |
| return ret; |
| } |
| |
| /* Analog input subdevice */ |
| s = &dev->subdevices[0]; |
| s->type = COMEDI_SUBD_AI; |
| s->subdev_flags = SDF_READABLE | SDF_GROUND; |
| s->n_chan = board->ai_nchans; |
| s->maxdata = board->ai_maxdata; |
| s->range_table = board->range; |
| s->insn_read = vmk80xx_ai_insn_read; |
| |
| /* Analog output subdevice */ |
| s = &dev->subdevices[1]; |
| s->type = COMEDI_SUBD_AO; |
| s->subdev_flags = SDF_WRITABLE | SDF_GROUND; |
| s->n_chan = board->ao_nchans; |
| s->maxdata = 0x00ff; |
| s->range_table = board->range; |
| s->insn_write = vmk80xx_ao_insn_write; |
| if (devpriv->model == VMK8061_MODEL) { |
| s->subdev_flags |= SDF_READABLE; |
| s->insn_read = vmk80xx_ao_insn_read; |
| } |
| |
| /* Digital input subdevice */ |
| s = &dev->subdevices[2]; |
| s->type = COMEDI_SUBD_DI; |
| s->subdev_flags = SDF_READABLE; |
| s->n_chan = board->di_nchans; |
| s->maxdata = 1; |
| s->range_table = &range_digital; |
| s->insn_bits = vmk80xx_di_insn_bits; |
| |
| /* Digital output subdevice */ |
| s = &dev->subdevices[3]; |
| s->type = COMEDI_SUBD_DO; |
| s->subdev_flags = SDF_WRITABLE; |
| s->n_chan = 8; |
| s->maxdata = 1; |
| s->range_table = &range_digital; |
| s->insn_bits = vmk80xx_do_insn_bits; |
| |
| /* Counter subdevice */ |
| s = &dev->subdevices[4]; |
| s->type = COMEDI_SUBD_COUNTER; |
| s->subdev_flags = SDF_READABLE; |
| s->n_chan = 2; |
| s->maxdata = board->cnt_maxdata; |
| s->insn_read = vmk80xx_cnt_insn_read; |
| s->insn_config = vmk80xx_cnt_insn_config; |
| if (devpriv->model == VMK8055_MODEL) { |
| s->subdev_flags |= SDF_WRITABLE; |
| s->insn_write = vmk80xx_cnt_insn_write; |
| } |
| |
| /* PWM subdevice */ |
| if (devpriv->model == VMK8061_MODEL) { |
| s = &dev->subdevices[5]; |
| s->type = COMEDI_SUBD_PWM; |
| s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
| s->n_chan = board->pwm_nchans; |
| s->maxdata = board->pwm_maxdata; |
| s->insn_read = vmk80xx_pwm_insn_read; |
| s->insn_write = vmk80xx_pwm_insn_write; |
| } |
| |
| up(&devpriv->limit_sem); |
| |
| return 0; |
| } |
| |
| static int vmk80xx_auto_attach(struct comedi_device *dev, |
| unsigned long context) |
| { |
| struct usb_interface *intf = comedi_to_usb_interface(dev); |
| const struct vmk80xx_board *board = NULL; |
| struct vmk80xx_private *devpriv; |
| int ret; |
| |
| if (context < ARRAY_SIZE(vmk80xx_boardinfo)) |
| board = &vmk80xx_boardinfo[context]; |
| if (!board) |
| return -ENODEV; |
| dev->board_ptr = board; |
| dev->board_name = board->name; |
| |
| devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
| if (!devpriv) |
| return -ENOMEM; |
| |
| devpriv->model = board->model; |
| |
| sema_init(&devpriv->limit_sem, 8); |
| |
| ret = vmk80xx_find_usb_endpoints(dev); |
| if (ret) |
| return ret; |
| |
| ret = vmk80xx_alloc_usb_buffers(dev); |
| if (ret) |
| return ret; |
| |
| usb_set_intfdata(intf, devpriv); |
| |
| if (devpriv->model == VMK8055_MODEL) |
| vmk80xx_reset_device(dev); |
| |
| return vmk80xx_init_subdevices(dev); |
| } |
| |
| static void vmk80xx_detach(struct comedi_device *dev) |
| { |
| struct usb_interface *intf = comedi_to_usb_interface(dev); |
| struct vmk80xx_private *devpriv = dev->private; |
| |
| if (!devpriv) |
| return; |
| |
| down(&devpriv->limit_sem); |
| |
| usb_set_intfdata(intf, NULL); |
| |
| 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); |
| } |
| |
| 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); |
| |
| static struct usb_driver vmk80xx_usb_driver = { |
| .name = "vmk80xx", |
| .id_table = vmk80xx_usb_id_table, |
| .probe = vmk80xx_usb_probe, |
| .disconnect = comedi_usb_auto_unconfig, |
| }; |
| module_comedi_usb_driver(vmk80xx_driver, vmk80xx_usb_driver); |
| |
| MODULE_AUTHOR("Manuel Gebele <forensixs@gmx.de>"); |
| MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); |
| MODULE_LICENSE("GPL"); |