adl_pci6208.c revision b8f4ac237e382accd4b30c75043939f7ed9e79a6
1/* 2 comedi/drivers/adl_pci6208.c 3 4 Hardware driver for ADLink 6208 series cards: 5 card | voltage output | current output 6 -------------+-------------------+--------------- 7 PCI-6208V | 8 channels | - 8 PCI-6216V | 16 channels | - 9 PCI-6208A | 8 channels | 8 channels 10 11 COMEDI - Linux Control and Measurement Device Interface 12 Copyright (C) 2000 David A. Schleef <ds@schleef.org> 13 14 This program is free software; you can redistribute it and/or modify 15 it under the terms of the GNU General Public License as published by 16 the Free Software Foundation; either version 2 of the License, or 17 (at your option) any later version. 18 19 This program is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 GNU General Public License for more details. 23 24 You should have received a copy of the GNU General Public License 25 along with this program; if not, write to the Free Software 26 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 27*/ 28/* 29Driver: adl_pci6208 30Description: ADLink PCI-6208/6216 Series Multi-channel Analog Output Cards 31Devices: (ADLink) PCI-6208 [adl_pci6208] 32 (ADLink) PCI-6216 [adl_pci6216] 33Author: nsyeow <nsyeow@pd.jaring.my> 34Updated: Fri, 30 Jan 2004 14:44:27 +0800 35Status: untested 36 37Configuration Options: not applicable, uses PCI auto config 38 39References: 40 - ni_660x.c 41 - adl_pci9111.c copied the entire pci setup section 42 - adl_pci9118.c 43*/ 44 45#include <linux/pci.h> 46 47#include "../comedidev.h" 48 49/* 50 * ADLINK PCI Device ID's supported by this driver 51 */ 52#define PCI_DEVICE_ID_PCI6208 0x6208 53#define PCI_DEVICE_ID_PCI6216 0x6216 54 55/* 56 * PCI-6208/6216-GL register map 57 */ 58#define PCI6208_AO_CONTROL(x) (0x00 + (2 * (x))) 59#define PCI6208_AO_STATUS 0x00 60#define PCI6208_AO_STATUS_DATA_SEND (1 << 0) 61#define PCI6208_DIO 0x40 62#define PCI6208_DIO_DO_MASK (0x0f) 63#define PCI6208_DIO_DO_SHIFT (0) 64#define PCI6208_DIO_DI_MASK (0xf0) 65#define PCI6208_DIO_DI_SHIFT (4) 66 67#define PCI6208_MAX_AO_CHANNELS 16 68 69struct pci6208_board { 70 const char *name; 71 unsigned short dev_id; 72 int ao_chans; 73}; 74 75static const struct pci6208_board pci6208_boards[] = { 76 { 77 .name = "adl_pci6208", 78 .dev_id = PCI_DEVICE_ID_PCI6208, 79 .ao_chans = 8, 80 }, { 81 .name = "adl_pci6216", 82 .dev_id = PCI_DEVICE_ID_PCI6216, 83 .ao_chans = 16, 84 }, 85}; 86 87struct pci6208_private { 88 unsigned int ao_readback[PCI6208_MAX_AO_CHANNELS]; 89}; 90 91static int pci6208_ao_winsn(struct comedi_device *dev, 92 struct comedi_subdevice *s, 93 struct comedi_insn *insn, unsigned int *data) 94{ 95 struct pci6208_private *devpriv = dev->private; 96 int chan = CR_CHAN(insn->chanspec); 97 unsigned long invert = 1 << (16 - 1); 98 unsigned long value = 0; 99 unsigned short status; 100 int i; 101 102 for (i = 0; i < insn->n; i++) { 103 value = data[i] ^ invert; 104 105 do { 106 status = inw(dev->iobase + PCI6208_AO_STATUS); 107 } while (status & PCI6208_AO_STATUS_DATA_SEND); 108 109 outw(value, dev->iobase + PCI6208_AO_CONTROL(chan)); 110 } 111 devpriv->ao_readback[chan] = value; 112 113 return insn->n; 114} 115 116static int pci6208_ao_rinsn(struct comedi_device *dev, 117 struct comedi_subdevice *s, 118 struct comedi_insn *insn, unsigned int *data) 119{ 120 struct pci6208_private *devpriv = dev->private; 121 int chan = CR_CHAN(insn->chanspec); 122 int i; 123 124 for (i = 0; i < insn->n; i++) 125 data[i] = devpriv->ao_readback[chan]; 126 127 return insn->n; 128} 129 130static int pci6208_di_insn_bits(struct comedi_device *dev, 131 struct comedi_subdevice *s, 132 struct comedi_insn *insn, 133 unsigned int *data) 134{ 135 unsigned int val; 136 137 val = inw(dev->iobase + PCI6208_DIO); 138 val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT; 139 140 data[1] = val; 141 142 return insn->n; 143} 144 145static int pci6208_do_insn_bits(struct comedi_device *dev, 146 struct comedi_subdevice *s, 147 struct comedi_insn *insn, 148 unsigned int *data) 149{ 150 unsigned int mask = data[0]; 151 unsigned int bits = data[1]; 152 153 if (mask) { 154 s->state &= ~mask; 155 s->state |= (bits & mask); 156 157 outw(s->state, dev->iobase + PCI6208_DIO); 158 } 159 160 data[1] = s->state; 161 162 return insn->n; 163} 164 165static const void *pci6208_find_boardinfo(struct comedi_device *dev, 166 struct pci_dev *pcidev) 167{ 168 const struct pci6208_board *boardinfo; 169 int i; 170 171 for (i = 0; i < ARRAY_SIZE(pci6208_boards); i++) { 172 boardinfo = &pci6208_boards[i]; 173 if (boardinfo->dev_id == pcidev->device) 174 return boardinfo; 175 } 176 return NULL; 177} 178 179static int pci6208_auto_attach(struct comedi_device *dev, 180 unsigned long context_unused) 181{ 182 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 183 const struct pci6208_board *boardinfo; 184 struct pci6208_private *devpriv; 185 struct comedi_subdevice *s; 186 unsigned int val; 187 int ret; 188 189 boardinfo = pci6208_find_boardinfo(dev, pcidev); 190 if (!boardinfo) 191 return -ENODEV; 192 dev->board_ptr = boardinfo; 193 dev->board_name = boardinfo->name; 194 195 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); 196 if (!devpriv) 197 return -ENOMEM; 198 dev->private = devpriv; 199 200 ret = comedi_pci_enable(pcidev, dev->board_name); 201 if (ret) 202 return ret; 203 dev->iobase = pci_resource_start(pcidev, 2); 204 205 ret = comedi_alloc_subdevices(dev, 3); 206 if (ret) 207 return ret; 208 209 s = &dev->subdevices[0]; 210 /* analog output subdevice */ 211 s->type = COMEDI_SUBD_AO; 212 s->subdev_flags = SDF_WRITABLE; 213 s->n_chan = boardinfo->ao_chans; 214 s->maxdata = 0xffff; 215 s->range_table = &range_bipolar10; 216 s->insn_write = pci6208_ao_winsn; 217 s->insn_read = pci6208_ao_rinsn; 218 219 s = &dev->subdevices[1]; 220 /* digital input subdevice */ 221 s->type = COMEDI_SUBD_DI; 222 s->subdev_flags = SDF_READABLE; 223 s->n_chan = 4; 224 s->maxdata = 1; 225 s->range_table = &range_digital; 226 s->insn_bits = pci6208_di_insn_bits; 227 228 s = &dev->subdevices[2]; 229 /* digital output subdevice */ 230 s->type = COMEDI_SUBD_DO; 231 s->subdev_flags = SDF_WRITABLE; 232 s->n_chan = 4; 233 s->maxdata = 1; 234 s->range_table = &range_digital; 235 s->insn_bits = pci6208_do_insn_bits; 236 237 /* 238 * Get the read back signals from the digital outputs 239 * and save it as the initial state for the subdevice. 240 */ 241 val = inw(dev->iobase + PCI6208_DIO); 242 val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT; 243 s->state = val; 244 s->io_bits = 0x0f; 245 246 dev_info(dev->class_dev, "%s: %s, I/O base=0x%04lx\n", 247 dev->driver->driver_name, dev->board_name, dev->iobase); 248 249 return 0; 250} 251 252static void pci6208_detach(struct comedi_device *dev) 253{ 254 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 255 256 if (pcidev) { 257 if (dev->iobase) 258 comedi_pci_disable(pcidev); 259 } 260} 261 262static struct comedi_driver adl_pci6208_driver = { 263 .driver_name = "adl_pci6208", 264 .module = THIS_MODULE, 265 .auto_attach = pci6208_auto_attach, 266 .detach = pci6208_detach, 267}; 268 269static int adl_pci6208_pci_probe(struct pci_dev *dev, 270 const struct pci_device_id *id) 271{ 272 return comedi_pci_auto_config(dev, &adl_pci6208_driver, 273 id->driver_data); 274} 275 276static DEFINE_PCI_DEVICE_TABLE(adl_pci6208_pci_table) = { 277 { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI_DEVICE_ID_PCI6208) }, 278 { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, PCI_DEVICE_ID_PCI6216) }, 279 { 0 } 280}; 281MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table); 282 283static struct pci_driver adl_pci6208_pci_driver = { 284 .name = "adl_pci6208", 285 .id_table = adl_pci6208_pci_table, 286 .probe = adl_pci6208_pci_probe, 287 .remove = comedi_pci_auto_unconfig, 288}; 289module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver); 290 291MODULE_AUTHOR("Comedi http://www.comedi.org"); 292MODULE_DESCRIPTION("Comedi low-level driver"); 293MODULE_LICENSE("GPL"); 294