1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * comedi/drivers/cb_pcimdda.c 4 * Computer Boards PCIM-DDA06-16 Comedi driver 5 * Author: Calin Culianu <calin@ajvar.org> 6 * 7 * COMEDI - Linux Control and Measurement Device Interface 8 * Copyright (C) 2000 David A. Schleef <ds@schleef.org> 9 */ 10/* 11 * Driver: cb_pcimdda 12 * Description: Measurement Computing PCIM-DDA06-16 13 * Devices: [Measurement Computing] PCIM-DDA06-16 (cb_pcimdda) 14 * Author: Calin Culianu <calin@ajvar.org> 15 * Updated: Mon, 14 Apr 2008 15:15:51 +0100 16 * Status: works 17 * 18 * All features of the PCIM-DDA06-16 board are supported. 19 * This board has 6 16-bit AO channels, and the usual 8255 DIO setup. 20 * (24 channels, configurable in banks of 8 and 4, etc.). 21 * This board does not support commands. 22 * 23 * The board has a peculiar way of specifying AO gain/range settings -- You have 24 * 1 jumper bank on the card, which either makes all 6 AO channels either 25 * 5 Volt unipolar, 5V bipolar, 10 Volt unipolar or 10V bipolar. 26 * 27 * Since there is absolutely _no_ way to tell in software how this jumper is set 28 * (well, at least according to the rather thin spec. from Measurement Computing 29 * that comes with the board), the driver assumes the jumper is at its factory 30 * default setting of +/-5V. 31 * 32 * Also of note is the fact that this board features another jumper, whose 33 * state is also completely invisible to software. It toggles two possible AO 34 * output modes on the board: 35 * 36 * - Update Mode: Writing to an AO channel instantaneously updates the actual 37 * signal output by the DAC on the board (this is the factory default). 38 * - Simultaneous XFER Mode: Writing to an AO channel has no effect until 39 * you read from any one of the AO channels. This is useful for loading 40 * all 6 AO values, and then reading from any one of the AO channels on the 41 * device to instantly update all 6 AO values in unison. Useful for some 42 * control apps, I would assume? If your jumper is in this setting, then you 43 * need to issue your comedi_data_write()s to load all the values you want, 44 * then issue one comedi_data_read() on any channel on the AO subdevice 45 * to initiate the simultaneous XFER. 46 * 47 * Configuration Options: not applicable, uses PCI auto config 48 */ 49 50/* 51 * This is a driver for the Computer Boards PCIM-DDA06-16 Analog Output 52 * card. This board has a unique register layout and as such probably 53 * deserves its own driver file. 54 * 55 * It is theoretically possible to integrate this board into the cb_pcidda 56 * file, but since that isn't my code, I didn't want to significantly 57 * modify that file to support this board (I thought it impolite to do so). 58 * 59 * At any rate, if you feel ambitious, please feel free to take 60 * the code out of this file and combine it with a more unified driver 61 * file. 62 * 63 * I would like to thank Timothy Curry <Timothy.Curry@rdec.redstone.army.mil> 64 * for lending me a board so that I could write this driver. 65 * 66 * -Calin Culianu <calin@ajvar.org> 67 */ 68 69#include <linux/module.h> 70 71#include "../comedi_pci.h" 72 73#include "8255.h" 74 75/* device ids of the cards we support -- currently only 1 card supported */ 76#define PCI_ID_PCIM_DDA06_16 0x0053 77 78/* 79 * Register map, 8-bit access only 80 */ 81#define PCIMDDA_DA_CHAN(x) (0x00 + (x) * 2) 82#define PCIMDDA_8255_BASE_REG 0x0c 83 84static int cb_pcimdda_ao_insn_write(struct comedi_device *dev, 85 struct comedi_subdevice *s, 86 struct comedi_insn *insn, 87 unsigned int *data) 88{ 89 unsigned int chan = CR_CHAN(insn->chanspec); 90 unsigned long offset = dev->iobase + PCIMDDA_DA_CHAN(chan); 91 unsigned int val = s->readback[chan]; 92 int i; 93 94 for (i = 0; i < insn->n; i++) { 95 val = data[i]; 96 97 /* 98 * Write the LSB then MSB. 99 * 100 * If the simultaneous xfer mode is selected by the 101 * jumper on the card, a read instruction is needed 102 * in order to initiate the simultaneous transfer. 103 * Otherwise, the DAC will be updated when the MSB 104 * is written. 105 */ 106 outb(val & 0x00ff, offset); 107 outb((val >> 8) & 0x00ff, offset + 1); 108 } 109 s->readback[chan] = val; 110 111 return insn->n; 112} 113 114static int cb_pcimdda_ao_insn_read(struct comedi_device *dev, 115 struct comedi_subdevice *s, 116 struct comedi_insn *insn, 117 unsigned int *data) 118{ 119 unsigned int chan = CR_CHAN(insn->chanspec); 120 121 /* Initiate the simultaneous transfer */ 122 inw(dev->iobase + PCIMDDA_DA_CHAN(chan)); 123 124 return comedi_readback_insn_read(dev, s, insn, data); 125} 126 127static int cb_pcimdda_auto_attach(struct comedi_device *dev, 128 unsigned long context_unused) 129{ 130 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 131 struct comedi_subdevice *s; 132 int ret; 133 134 ret = comedi_pci_enable(dev); 135 if (ret) 136 return ret; 137 dev->iobase = pci_resource_start(pcidev, 3); 138 139 ret = comedi_alloc_subdevices(dev, 2); 140 if (ret) 141 return ret; 142 143 s = &dev->subdevices[0]; 144 /* analog output subdevice */ 145 s->type = COMEDI_SUBD_AO; 146 s->subdev_flags = SDF_WRITABLE | SDF_READABLE; 147 s->n_chan = 6; 148 s->maxdata = 0xffff; 149 s->range_table = &range_bipolar5; 150 s->insn_write = cb_pcimdda_ao_insn_write; 151 s->insn_read = cb_pcimdda_ao_insn_read; 152 153 ret = comedi_alloc_subdev_readback(s); 154 if (ret) 155 return ret; 156 157 s = &dev->subdevices[1]; 158 /* digital i/o subdevice */ 159 return subdev_8255_init(dev, s, NULL, PCIMDDA_8255_BASE_REG); 160} 161 162static struct comedi_driver cb_pcimdda_driver = { 163 .driver_name = "cb_pcimdda", 164 .module = THIS_MODULE, 165 .auto_attach = cb_pcimdda_auto_attach, 166 .detach = comedi_pci_detach, 167}; 168 169static int cb_pcimdda_pci_probe(struct pci_dev *dev, 170 const struct pci_device_id *id) 171{ 172 return comedi_pci_auto_config(dev, &cb_pcimdda_driver, 173 id->driver_data); 174} 175 176static const struct pci_device_id cb_pcimdda_pci_table[] = { 177 { PCI_DEVICE(PCI_VENDOR_ID_CB, PCI_ID_PCIM_DDA06_16) }, 178 { 0 } 179}; 180MODULE_DEVICE_TABLE(pci, cb_pcimdda_pci_table); 181 182static struct pci_driver cb_pcimdda_driver_pci_driver = { 183 .name = "cb_pcimdda", 184 .id_table = cb_pcimdda_pci_table, 185 .probe = cb_pcimdda_pci_probe, 186 .remove = comedi_pci_auto_unconfig, 187}; 188module_comedi_pci_driver(cb_pcimdda_driver, cb_pcimdda_driver_pci_driver); 189 190MODULE_AUTHOR("Calin A. Culianu <calin@rtlab.org>"); 191MODULE_DESCRIPTION("Comedi low-level driver for the Computerboards PCIM-DDA series. Currently only supports PCIM-DDA06-16 (which also happens to be the only board in this series. :) ) "); 192MODULE_LICENSE("GPL"); 193