/* comedi/drivers/vmk80xx.c Velleman USB Board Low-Level Driver Copyright (C) 2009 Manuel Gebele , Germany COMEDI - Linux Control and Measurement Device Interface Copyright (C) 2000 David A. Schleef 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: vmk80xx Description: Velleman USB Board Low-Level Driver Devices: K8055/K8061 aka VM110/VM140 Author: Manuel Gebele Updated: Sun, 10 May 2009 11:14:59 +0200 Status: works Supports: - analog input - analog output - digital input - digital output - counter - pwm */ /* Changelog: 0.8.81 -3- code completely rewritten (adjust driver logic) 0.8.81 -2- full support for K8061 0.8.81 -1- fix some mistaken among others the number of supported boards and I/O handling 0.7.76 -4- renamed to vmk80xx 0.7.76 -3- detect K8061 (only theoretically supported) 0.7.76 -2- code completely rewritten (adjust driver logic) 0.7.76 -1- support for digital and counter subdevice */ #include #include #include #include #include #include #include #include #include #include "../comedidev.h" MODULE_AUTHOR("Manuel Gebele "); MODULE_DESCRIPTION("Velleman USB Board Low-Level Driver"); MODULE_SUPPORTED_DEVICE("K8055/K8061 aka VM110/VM140"); MODULE_VERSION("0.8.01"); MODULE_LICENSE("GPL"); enum { DEVICE_VMK8055, DEVICE_VMK8061 }; static struct usb_device_id vmk80xx_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}, {} /* terminating entry */ }; MODULE_DEVICE_TABLE(usb, vmk80xx_id_table); #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 VMK80XX_MAX_BOARDS COMEDI_NUM_BOARD_MINORS #define TRANS_OUT_BUSY 1 #define TRANS_IN_BUSY 2 #define TRANS_IN_RUNNING 3 #define IC3_VERSION (1 << 0) #define IC6_VERSION (1 << 1) #define URB_RCV_FLAG (1 << 0) #define URB_SND_FLAG (1 << 1) #define CONFIG_VMK80XX_DEBUG #undef CONFIG_VMK80XX_DEBUG #ifdef CONFIG_VMK80XX_DEBUG static int dbgvm = 1; #else static int dbgvm; #endif #ifdef CONFIG_COMEDI_DEBUG static int dbgcm = 1; #else static int dbgcm; #endif #define dbgvm(fmt, arg...) \ do { \ if (dbgvm) \ printk(KERN_DEBUG fmt, ##arg); \ } while (0) #define dbgcm(fmt, arg...) \ do { \ if (dbgcm) \ printk(KERN_DEBUG fmt, ##arg); \ } while (0) enum vmk80xx_model { VMK8055_MODEL, VMK8061_MODEL }; struct firmware_version { unsigned char ic3_vers[32]; /* USB-Controller */ unsigned char ic6_vers[32]; /* CPU */ }; static const struct comedi_lrange vmk8055_range = { 1, {UNI_RANGE(5)} }; 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; __u8 ai_chans; __le16 ai_bits; __u8 ao_chans; __le16 ao_bits; __u8 di_chans; __le16 di_bits; __u8 do_chans; __le16 do_bits; __u8 cnt_chans; __le16 cnt_bits; __u8 pwm_chans; __le16 pwm_bits; }; enum { VMK80XX_SUBD_AI, VMK80XX_SUBD_AO, VMK80XX_SUBD_DI, VMK80XX_SUBD_DO, VMK80XX_SUBD_CNT, VMK80XX_SUBD_PWM, }; struct vmk80xx_usb { struct usb_device *udev; struct usb_interface *intf; struct usb_endpoint_descriptor *ep_rx; struct usb_endpoint_descriptor *ep_tx; struct usb_anchor rx_anchor; struct usb_anchor tx_anchor; struct vmk80xx_board board; struct firmware_version fw; struct semaphore limit_sem; wait_queue_head_t read_wait; wait_queue_head_t write_wait; unsigned char *usb_rx_buf; unsigned char *usb_tx_buf; unsigned long flags; int probed; int attached; int count; }; static struct vmk80xx_usb vmb[VMK80XX_MAX_BOARDS]; static DEFINE_MUTEX(glb_mutex); static void vmk80xx_tx_callback(struct urb *urb) { struct vmk80xx_usb *dev = urb->context; int stat = urb->status; dbgvm("vmk80xx: %s\n", __func__); if (stat && !(stat == -ENOENT || stat == -ECONNRESET || stat == -ESHUTDOWN)) dbgcm("comedi#: vmk80xx: %s - nonzero urb status (%d)\n", __func__, stat); if (!test_bit(TRANS_OUT_BUSY, &dev->flags)) return; clear_bit(TRANS_OUT_BUSY, &dev->flags); wake_up_interruptible(&dev->write_wait); } static void vmk80xx_rx_callback(struct urb *urb) { struct vmk80xx_usb *dev = urb->context; int stat = urb->status; dbgvm("vmk80xx: %s\n", __func__); switch (stat) { case 0: break; case -ENOENT: case -ECONNRESET: case -ESHUTDOWN: break; default: dbgcm("comedi#: vmk80xx: %s - nonzero urb status (%d)\n", __func__, stat); goto resubmit; } goto exit; resubmit: if (test_bit(TRANS_IN_RUNNING, &dev->flags) && dev->intf) { usb_anchor_urb(urb, &dev->rx_anchor); if (!usb_submit_urb(urb, GFP_KERNEL)) goto exit; err("comedi#: vmk80xx: %s - submit urb failed\n", __func__); usb_unanchor_urb(urb); } exit: clear_bit(TRANS_IN_BUSY, &dev->flags); wake_up_interruptible(&dev->read_wait); } static int vmk80xx_check_data_link(struct vmk80xx_usb *dev) { unsigned int tx_pipe, rx_pipe; unsigned char tx[1], rx[2]; dbgvm("vmk80xx: %s\n", __func__); tx_pipe = usb_sndbulkpipe(dev->udev, 0x01); rx_pipe = usb_rcvbulkpipe(dev->udev, 0x81); tx[0] = VMK8061_CMD_RD_PWR_STAT; /* Check that IC6 (PIC16F871) is powered and * running and the data link between IC3 and * IC6 is working properly */ usb_bulk_msg(dev->udev, tx_pipe, tx, 1, NULL, dev->ep_tx->bInterval); usb_bulk_msg(dev->udev, rx_pipe, rx, 2, NULL, HZ * 10); return (int)rx[1]; } static void vmk80xx_read_eeprom(struct vmk80xx_usb *dev, int flag) { unsigned int tx_pipe, rx_pipe; unsigned char tx[1], rx[64]; int cnt; dbgvm("vmk80xx: %s\n", __func__); tx_pipe = usb_sndbulkpipe(dev->udev, 0x01); rx_pipe = usb_rcvbulkpipe(dev->udev, 0x81); tx[0] = VMK8061_CMD_RD_VERSION; /* Read the firmware version info of IC3 and * IC6 from the internal EEPROM of the IC */ usb_bulk_msg(dev->udev, tx_pipe, tx, 1, NULL, dev->ep_tx->bInterval); usb_bulk_msg(dev->udev, rx_pipe, rx, 64, &cnt, HZ * 10); rx[cnt] = '\0'; if (flag & IC3_VERSION) strncpy(dev->fw.ic3_vers, rx + 1, 24); else /* IC6_VERSION */ strncpy(dev->fw.ic6_vers, rx + 25, 24); } static int vmk80xx_reset_device(struct vmk80xx_usb *dev) { struct urb *urb; unsigned int tx_pipe; int ival; size_t size; dbgvm("vmk80xx: %s\n", __func__); urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; tx_pipe = usb_sndintpipe(dev->udev, 0x01); ival = dev->ep_tx->bInterval; size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); dev->usb_tx_buf[0] = VMK8055_CMD_RST; dev->usb_tx_buf[1] = 0x00; dev->usb_tx_buf[2] = 0x00; dev->usb_tx_buf[3] = 0x00; dev->usb_tx_buf[4] = 0x00; dev->usb_tx_buf[5] = 0x00; dev->usb_tx_buf[6] = 0x00; dev->usb_tx_buf[7] = 0x00; usb_fill_int_urb(urb, dev->udev, tx_pipe, dev->usb_tx_buf, size, vmk80xx_tx_callback, dev, ival); usb_anchor_urb(urb, &dev->tx_anchor); return usb_submit_urb(urb, GFP_KERNEL); } static void vmk80xx_build_int_urb(struct urb *urb, int flag) { struct vmk80xx_usb *dev = urb->context; __u8 rx_addr, tx_addr; unsigned int pipe; unsigned char *buf; size_t size; void (*callback) (struct urb *); int ival; dbgvm("vmk80xx: %s\n", __func__); if (flag & URB_RCV_FLAG) { rx_addr = dev->ep_rx->bEndpointAddress; pipe = usb_rcvintpipe(dev->udev, rx_addr); buf = dev->usb_rx_buf; size = le16_to_cpu(dev->ep_rx->wMaxPacketSize); callback = vmk80xx_rx_callback; ival = dev->ep_rx->bInterval; } else { /* URB_SND_FLAG */ tx_addr = dev->ep_tx->bEndpointAddress; pipe = usb_sndintpipe(dev->udev, tx_addr); buf = dev->usb_tx_buf; size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); callback = vmk80xx_tx_callback; ival = dev->ep_tx->bInterval; } usb_fill_int_urb(urb, dev->udev, pipe, buf, size, callback, dev, ival); } static void vmk80xx_do_bulk_msg(struct vmk80xx_usb *dev) { __u8 tx_addr, rx_addr; unsigned int tx_pipe, rx_pipe; size_t size; dbgvm("vmk80xx: %s\n", __func__); set_bit(TRANS_IN_BUSY, &dev->flags); set_bit(TRANS_OUT_BUSY, &dev->flags); tx_addr = dev->ep_tx->bEndpointAddress; rx_addr = dev->ep_rx->bEndpointAddress; tx_pipe = usb_sndbulkpipe(dev->udev, tx_addr); rx_pipe = usb_rcvbulkpipe(dev->udev, rx_addr); /* The max packet size attributes of the K8061 * input/output endpoints are identical */ size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); usb_bulk_msg(dev->udev, tx_pipe, dev->usb_tx_buf, size, NULL, dev->ep_tx->bInterval); usb_bulk_msg(dev->udev, rx_pipe, dev->usb_rx_buf, size, NULL, HZ * 10); clear_bit(TRANS_OUT_BUSY, &dev->flags); clear_bit(TRANS_IN_BUSY, &dev->flags); } static int vmk80xx_read_packet(struct vmk80xx_usb *dev) { struct urb *urb; int retval; dbgvm("vmk80xx: %s\n", __func__); if (!dev->intf) return -ENODEV; /* Only useful for interrupt transfers */ if (test_bit(TRANS_IN_BUSY, &dev->flags)) if (wait_event_interruptible(dev->read_wait, !test_bit(TRANS_IN_BUSY, &dev->flags))) return -ERESTART; if (dev->board.model == VMK8061_MODEL) { vmk80xx_do_bulk_msg(dev); return 0; } urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; urb->context = dev; vmk80xx_build_int_urb(urb, URB_RCV_FLAG); set_bit(TRANS_IN_RUNNING, &dev->flags); set_bit(TRANS_IN_BUSY, &dev->flags); usb_anchor_urb(urb, &dev->rx_anchor); retval = usb_submit_urb(urb, GFP_KERNEL); if (!retval) goto exit; clear_bit(TRANS_IN_RUNNING, &dev->flags); usb_unanchor_urb(urb); exit: usb_free_urb(urb); return retval; } static int vmk80xx_write_packet(struct vmk80xx_usb *dev, int cmd) { struct urb *urb; int retval; dbgvm("vmk80xx: %s\n", __func__); if (!dev->intf) return -ENODEV; if (test_bit(TRANS_OUT_BUSY, &dev->flags)) if (wait_event_interruptible(dev->write_wait, !test_bit(TRANS_OUT_BUSY, &dev->flags))) return -ERESTART; if (dev->board.model == VMK8061_MODEL) { dev->usb_tx_buf[0] = cmd; vmk80xx_do_bulk_msg(dev); return 0; } urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; urb->context = dev; vmk80xx_build_int_urb(urb, URB_SND_FLAG); set_bit(TRANS_OUT_BUSY, &dev->flags); usb_anchor_urb(urb, &dev->tx_anchor); dev->usb_tx_buf[0] = cmd; retval = usb_submit_urb(urb, GFP_KERNEL); if (!retval) goto exit; clear_bit(TRANS_OUT_BUSY, &dev->flags); usb_unanchor_urb(urb); exit: usb_free_urb(urb); return retval; } #define DIR_IN 1 #define DIR_OUT 2 #define rudimentary_check(dir) \ do { \ if (!dev) \ return -EFAULT; \ if (!dev->probed) \ return -ENODEV; \ if (!dev->attached) \ return -ENODEV; \ if ((dir) & DIR_IN) { \ if (test_bit(TRANS_IN_BUSY, &dev->flags)) \ return -EBUSY; \ } else { /* DIR_OUT */ \ if (test_bit(TRANS_OUT_BUSY, &dev->flags)) \ return -EBUSY; \ } \ } while (0) static int vmk80xx_ai_rinsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int chan, reg[2]; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_IN); down(&dev->limit_sem); chan = CR_CHAN(insn->chanspec); switch (dev->board.model) { case VMK8055_MODEL: if (!chan) reg[0] = VMK8055_AI1_REG; else reg[0] = VMK8055_AI2_REG; break; case VMK8061_MODEL: reg[0] = VMK8061_AI_REG1; reg[1] = VMK8061_AI_REG2; dev->usb_tx_buf[0] = VMK8061_CMD_RD_AI; dev->usb_tx_buf[VMK8061_CH_REG] = chan; break; } for (n = 0; n < insn->n; n++) { if (vmk80xx_read_packet(dev)) break; if (dev->board.model == VMK8055_MODEL) { data[n] = dev->usb_rx_buf[reg[0]]; continue; } /* VMK8061_MODEL */ data[n] = dev->usb_rx_buf[reg[0]] + 256 * dev->usb_rx_buf[reg[1]]; } up(&dev->limit_sem); return n; } static int vmk80xx_ao_winsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int chan, cmd, reg; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_OUT); down(&dev->limit_sem); chan = CR_CHAN(insn->chanspec); switch (dev->board.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; dev->usb_tx_buf[VMK8061_CH_REG] = chan; break; } for (n = 0; n < insn->n; n++) { dev->usb_tx_buf[reg] = data[n]; if (vmk80xx_write_packet(dev, cmd)) break; } up(&dev->limit_sem); return n; } static int vmk80xx_ao_rinsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int chan, reg; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_IN); down(&dev->limit_sem); chan = CR_CHAN(insn->chanspec); reg = VMK8061_AO_REG - 1; dev->usb_tx_buf[0] = VMK8061_CMD_RD_AO; for (n = 0; n < insn->n; n++) { if (vmk80xx_read_packet(dev)) break; data[n] = dev->usb_rx_buf[reg + chan]; } up(&dev->limit_sem); return n; } static int vmk80xx_di_rinsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int chan; unsigned char *rx_buf; int reg, inp; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_IN); down(&dev->limit_sem); chan = CR_CHAN(insn->chanspec); rx_buf = dev->usb_rx_buf; if (dev->board.model == VMK8061_MODEL) { reg = VMK8061_DI_REG; dev->usb_tx_buf[0] = VMK8061_CMD_RD_DI; } else reg = VMK8055_DI_REG; for (n = 0; n < insn->n; n++) { if (vmk80xx_read_packet(dev)) break; if (dev->board.model == VMK8055_MODEL) inp = (((rx_buf[reg] >> 4) & 0x03) | ((rx_buf[reg] << 2) & 0x04) | ((rx_buf[reg] >> 3) & 0x18)); else inp = rx_buf[reg]; data[n] = ((inp & (1 << chan)) > 0); } up(&dev->limit_sem); return n; } static int vmk80xx_do_winsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int chan; unsigned char *tx_buf; int reg, cmd; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_OUT); down(&dev->limit_sem); chan = CR_CHAN(insn->chanspec); tx_buf = dev->usb_tx_buf; for (n = 0; n < insn->n; n++) { if (dev->board.model == VMK8055_MODEL) { reg = VMK8055_DO_REG; cmd = VMK8055_CMD_WRT_AD; if (data[n] == 1) tx_buf[reg] |= (1 << chan); else tx_buf[reg] ^= (1 << chan); goto write_packet; } /* VMK8061_MODEL */ reg = VMK8061_DO_REG; if (data[n] == 1) { cmd = VMK8061_CMD_SET_DO; tx_buf[reg] = 1 << chan; } else { cmd = VMK8061_CMD_CLR_DO; tx_buf[reg] = 0xff - (1 << chan); } write_packet: if (vmk80xx_write_packet(dev, cmd)) break; } up(&dev->limit_sem); return n; } static int vmk80xx_do_rinsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int chan, reg, mask; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_IN); down(&dev->limit_sem); chan = CR_CHAN(insn->chanspec); reg = VMK8061_DO_REG; mask = 1 << chan; dev->usb_tx_buf[0] = VMK8061_CMD_RD_DO; for (n = 0; n < insn->n; n++) { if (vmk80xx_read_packet(dev)) break; data[n] = (dev->usb_rx_buf[reg] & mask) >> chan; } up(&dev->limit_sem); return n; } static int vmk80xx_cnt_rinsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int chan, reg[2]; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_IN); down(&dev->limit_sem); chan = CR_CHAN(insn->chanspec); switch (dev->board.model) { case VMK8055_MODEL: if (!chan) reg[0] = VMK8055_CNT1_REG; else reg[0] = VMK8055_CNT2_REG; break; case VMK8061_MODEL: reg[0] = VMK8061_CNT_REG; reg[1] = VMK8061_CNT_REG; dev->usb_tx_buf[0] = VMK8061_CMD_RD_CNT; break; } for (n = 0; n < insn->n; n++) { if (vmk80xx_read_packet(dev)) break; if (dev->board.model == VMK8055_MODEL) { data[n] = dev->usb_rx_buf[reg[0]]; continue; } /* VMK8061_MODEL */ data[n] = dev->usb_rx_buf[reg[0] * (chan + 1) + 1] + 256 * dev->usb_rx_buf[reg[1] * 2 + 2]; } up(&dev->limit_sem); return n; } static int vmk80xx_cnt_cinsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; unsigned int insn_cmd; int chan, cmd, reg; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_OUT); down(&dev->limit_sem); insn_cmd = data[0]; if (insn_cmd != INSN_CONFIG_RESET && insn_cmd != GPCT_RESET) return -EINVAL; chan = CR_CHAN(insn->chanspec); if (dev->board.model == VMK8055_MODEL) { if (!chan) { cmd = VMK8055_CMD_RST_CNT1; reg = VMK8055_CNT1_REG; } else { cmd = VMK8055_CMD_RST_CNT2; reg = VMK8055_CNT2_REG; } dev->usb_tx_buf[reg] = 0x00; } else cmd = VMK8061_CMD_RST_CNT; for (n = 0; n < insn->n; n++) if (vmk80xx_write_packet(dev, cmd)) break; up(&dev->limit_sem); return n; } static int vmk80xx_cnt_winsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; unsigned long debtime, val; int chan, cmd; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_OUT); down(&dev->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; dev->usb_tx_buf[6 + chan] = val; if (vmk80xx_write_packet(dev, cmd)) break; } up(&dev->limit_sem); return n; } static int vmk80xx_pwm_rinsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; int reg[2]; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_IN); down(&dev->limit_sem); reg[0] = VMK8061_PWM_REG1; reg[1] = VMK8061_PWM_REG2; dev->usb_tx_buf[0] = VMK8061_CMD_RD_PWM; for (n = 0; n < insn->n; n++) { if (vmk80xx_read_packet(dev)) break; data[n] = dev->usb_rx_buf[reg[0]] + 4 * dev->usb_rx_buf[reg[1]]; } up(&dev->limit_sem); return n; } static int vmk80xx_pwm_winsn(struct comedi_device *cdev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { struct vmk80xx_usb *dev = cdev->private; unsigned char *tx_buf; int reg[2], cmd; int n; dbgvm("vmk80xx: %s\n", __func__); rudimentary_check(DIR_OUT); down(&dev->limit_sem); tx_buf = dev->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(&dev->limit_sem); return n; } static int vmk80xx_attach(struct comedi_device *cdev, struct comedi_devconfig *it) { int i; struct vmk80xx_usb *dev; int n_subd; struct comedi_subdevice *s; int minor; dbgvm("vmk80xx: %s\n", __func__); mutex_lock(&glb_mutex); for (i = 0; i < VMK80XX_MAX_BOARDS; i++) if (vmb[i].probed && !vmb[i].attached) break; if (i == VMK80XX_MAX_BOARDS) { mutex_unlock(&glb_mutex); return -ENODEV; } dev = &vmb[i]; down(&dev->limit_sem); cdev->board_name = dev->board.name; cdev->private = dev; if (dev->board.model == VMK8055_MODEL) n_subd = 5; else n_subd = 6; if (alloc_subdevices(cdev, n_subd) < 0) { up(&dev->limit_sem); mutex_unlock(&glb_mutex); return -ENOMEM; } /* Analog input subdevice */ s = cdev->subdevices + VMK80XX_SUBD_AI; s->type = COMEDI_SUBD_AI; s->subdev_flags = SDF_READABLE | SDF_GROUND; s->n_chan = dev->board.ai_chans; s->maxdata = (1 << dev->board.ai_bits) - 1; s->range_table = dev->board.range; s->insn_read = vmk80xx_ai_rinsn; /* Analog output subdevice */ s = cdev->subdevices + VMK80XX_SUBD_AO; s->type = COMEDI_SUBD_AO; s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; s->n_chan = dev->board.ao_chans; s->maxdata = (1 << dev->board.ao_bits) - 1; s->range_table = dev->board.range; s->insn_write = vmk80xx_ao_winsn; if (dev->board.model == VMK8061_MODEL) { s->subdev_flags |= SDF_READABLE; s->insn_read = vmk80xx_ao_rinsn; } /* Digital input subdevice */ s = cdev->subdevices + VMK80XX_SUBD_DI; s->type = COMEDI_SUBD_DI; s->subdev_flags = SDF_READABLE | SDF_GROUND; s->n_chan = dev->board.di_chans; s->maxdata = (1 << dev->board.di_bits) - 1; s->insn_read = vmk80xx_di_rinsn; /* Digital output subdevice */ s = cdev->subdevices + VMK80XX_SUBD_DO; s->type = COMEDI_SUBD_DO; s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; s->n_chan = dev->board.do_chans; s->maxdata = (1 << dev->board.do_bits) - 1; s->insn_write = vmk80xx_do_winsn; if (dev->board.model == VMK8061_MODEL) { s->subdev_flags |= SDF_READABLE; s->insn_read = vmk80xx_do_rinsn; } /* Counter subdevice */ s = cdev->subdevices + VMK80XX_SUBD_CNT; s->type = COMEDI_SUBD_COUNTER; s->subdev_flags = SDF_READABLE; s->n_chan = dev->board.cnt_chans; s->insn_read = vmk80xx_cnt_rinsn; s->insn_config = vmk80xx_cnt_cinsn; if (dev->board.model == VMK8055_MODEL) { s->subdev_flags |= SDF_WRITEABLE; s->maxdata = (1 << dev->board.cnt_bits) - 1; s->insn_write = vmk80xx_cnt_winsn; } /* PWM subdevice */ if (dev->board.model == VMK8061_MODEL) { s = cdev->subdevices + VMK80XX_SUBD_PWM; s->type = COMEDI_SUBD_PWM; s->subdev_flags = SDF_READABLE | SDF_WRITEABLE; s->n_chan = dev->board.pwm_chans; s->maxdata = (1 << dev->board.pwm_bits) - 1; s->insn_read = vmk80xx_pwm_rinsn; s->insn_write = vmk80xx_pwm_winsn; } dev->attached = 1; minor = cdev->minor; printk(KERN_INFO "comedi%d: vmk80xx: board #%d [%s] attached to comedi\n", minor, dev->count, dev->board.name); up(&dev->limit_sem); mutex_unlock(&glb_mutex); return 0; } static int vmk80xx_detach(struct comedi_device *cdev) { struct vmk80xx_usb *dev; int minor; dbgvm("vmk80xx: %s\n", __func__); if (!cdev) return -EFAULT; dev = cdev->private; if (!dev) return -EFAULT; down(&dev->limit_sem); cdev->private = NULL; dev->attached = 0; minor = cdev->minor; printk(KERN_INFO "comedi%d: vmk80xx: board #%d [%s] detached from comedi\n", minor, dev->count, dev->board.name); up(&dev->limit_sem); return 0; } static int vmk80xx_probe(struct usb_interface *intf, const struct usb_device_id *id) { int i; struct vmk80xx_usb *dev; struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *ep_desc; size_t size; dbgvm("vmk80xx: %s\n", __func__); mutex_lock(&glb_mutex); for (i = 0; i < VMK80XX_MAX_BOARDS; i++) if (!vmb[i].probed) break; if (i == VMK80XX_MAX_BOARDS) { mutex_unlock(&glb_mutex); return -EMFILE; } dev = &vmb[i]; memset(dev, 0x00, sizeof(struct vmk80xx_usb)); dev->count = i; iface_desc = intf->cur_altsetting; if (iface_desc->desc.bNumEndpoints != 2) goto error; for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { ep_desc = &iface_desc->endpoint[i].desc; if (usb_endpoint_is_int_in(ep_desc)) { dev->ep_rx = ep_desc; continue; } if (usb_endpoint_is_int_out(ep_desc)) { dev->ep_tx = ep_desc; continue; } if (usb_endpoint_is_bulk_in(ep_desc)) { dev->ep_rx = ep_desc; continue; } if (usb_endpoint_is_bulk_out(ep_desc)) { dev->ep_tx = ep_desc; continue; } } if (!dev->ep_rx || !dev->ep_tx) goto error; size = le16_to_cpu(dev->ep_rx->wMaxPacketSize); dev->usb_rx_buf = kmalloc(size, GFP_KERNEL); if (!dev->usb_rx_buf) { mutex_unlock(&glb_mutex); return -ENOMEM; } size = le16_to_cpu(dev->ep_tx->wMaxPacketSize); dev->usb_tx_buf = kmalloc(size, GFP_KERNEL); if (!dev->usb_tx_buf) { kfree(dev->usb_rx_buf); mutex_unlock(&glb_mutex); return -ENOMEM; } dev->udev = interface_to_usbdev(intf); dev->intf = intf; sema_init(&dev->limit_sem, 8); init_waitqueue_head(&dev->read_wait); init_waitqueue_head(&dev->write_wait); init_usb_anchor(&dev->rx_anchor); init_usb_anchor(&dev->tx_anchor); usb_set_intfdata(intf, dev); switch (id->driver_info) { case DEVICE_VMK8055: dev->board.name = "K8055 (VM110)"; dev->board.model = VMK8055_MODEL; dev->board.range = &vmk8055_range; dev->board.ai_chans = 2; dev->board.ai_bits = 8; dev->board.ao_chans = 2; dev->board.ao_bits = 8; dev->board.di_chans = 5; dev->board.di_bits = 1; dev->board.do_chans = 8; dev->board.do_bits = 1; dev->board.cnt_chans = 2; dev->board.cnt_bits = 16; dev->board.pwm_chans = 0; dev->board.pwm_bits = 0; break; case DEVICE_VMK8061: dev->board.name = "K8061 (VM140)"; dev->board.model = VMK8061_MODEL; dev->board.range = &vmk8061_range; dev->board.ai_chans = 8; dev->board.ai_bits = 10; dev->board.ao_chans = 8; dev->board.ao_bits = 8; dev->board.di_chans = 8; dev->board.di_bits = 1; dev->board.do_chans = 8; dev->board.do_bits = 1; dev->board.cnt_chans = 2; dev->board.cnt_bits = 0; dev->board.pwm_chans = 1; dev->board.pwm_bits = 10; break; } if (dev->board.model == VMK8061_MODEL) { vmk80xx_read_eeprom(dev, IC3_VERSION); printk(KERN_INFO "comedi#: vmk80xx: %s\n", dev->fw.ic3_vers); if (vmk80xx_check_data_link(dev)) { vmk80xx_read_eeprom(dev, IC6_VERSION); printk(KERN_INFO "comedi#: vmk80xx: %s\n", dev->fw.ic6_vers); } else dbgcm("comedi#: vmk80xx: no conn. to CPU\n"); } if (dev->board.model == VMK8055_MODEL) vmk80xx_reset_device(dev); dev->probed = 1; printk(KERN_INFO "comedi#: vmk80xx: board #%d [%s] now attached\n", dev->count, dev->board.name); mutex_unlock(&glb_mutex); return 0; error: mutex_unlock(&glb_mutex); return -ENODEV; } static void vmk80xx_disconnect(struct usb_interface *intf) { struct vmk80xx_usb *dev = usb_get_intfdata(intf); dbgvm("vmk80xx: %s\n", __func__); if (!dev) return; mutex_lock(&glb_mutex); down(&dev->limit_sem); dev->probed = 0; usb_set_intfdata(dev->intf, NULL); usb_kill_anchored_urbs(&dev->rx_anchor); usb_kill_anchored_urbs(&dev->tx_anchor); kfree(dev->usb_rx_buf); kfree(dev->usb_tx_buf); printk(KERN_INFO "comedi#: vmk80xx: board #%d [%s] now detached\n", dev->count, dev->board.name); up(&dev->limit_sem); mutex_unlock(&glb_mutex); } /* TODO: Add support for suspend, resume, pre_reset, * post_reset and flush */ static struct usb_driver vmk80xx_driver = { .name = "vmk80xx", .probe = vmk80xx_probe, .disconnect = vmk80xx_disconnect, .id_table = vmk80xx_id_table }; static struct comedi_driver driver_vmk80xx = { .module = THIS_MODULE, .driver_name = "vmk80xx", .attach = vmk80xx_attach, .detach = vmk80xx_detach }; static int __init vmk80xx_init(void) { printk(KERN_INFO "vmk80xx: version 0.8.01 " "Manuel Gebele \n"); usb_register(&vmk80xx_driver); return comedi_driver_register(&driver_vmk80xx); } static void __exit vmk80xx_exit(void) { comedi_driver_unregister(&driver_vmk80xx); usb_deregister(&vmk80xx_driver); } module_init(vmk80xx_init); module_exit(vmk80xx_exit);