1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * ke_counter.c 4 * Comedi driver for Kolter-Electronic PCI Counter 1 Card 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org> 8 */ 9 10 /* 11 * Driver: ke_counter 12 * Description: Driver for Kolter Electronic Counter Card 13 * Devices: [Kolter Electronic] PCI Counter Card (ke_counter) 14 * Author: Michael Hillmann 15 * Updated: Mon, 14 Apr 2008 15:42:42 +0100 16 * Status: tested 17 * 18 * Configuration Options: not applicable, uses PCI auto config 19 */ 20 21 #include <linux/module.h> 22 23 #include "../comedi_pci.h" 24 25 /* 26 * PCI BAR 0 Register I/O map 27 */ 28 #define KE_RESET_REG(x) (0x00 + ((x) * 0x20)) 29 #define KE_LATCH_REG(x) (0x00 + ((x) * 0x20)) 30 #define KE_LSB_REG(x) (0x04 + ((x) * 0x20)) 31 #define KE_MID_REG(x) (0x08 + ((x) * 0x20)) 32 #define KE_MSB_REG(x) (0x0c + ((x) * 0x20)) 33 #define KE_SIGN_REG(x) (0x10 + ((x) * 0x20)) 34 #define KE_OSC_SEL_REG 0xf8 35 #define KE_OSC_SEL_CLK(x) (((x) & 0x3) << 0) 36 #define KE_OSC_SEL_EXT KE_OSC_SEL_CLK(1) 37 #define KE_OSC_SEL_4MHZ KE_OSC_SEL_CLK(2) 38 #define KE_OSC_SEL_20MHZ KE_OSC_SEL_CLK(3) 39 #define KE_DO_REG 0xfc 40 41 static int ke_counter_insn_write(struct comedi_device *dev, 42 struct comedi_subdevice *s, 43 struct comedi_insn *insn, 44 unsigned int *data) 45 { 46 unsigned int chan = CR_CHAN(insn->chanspec); 47 unsigned int val; 48 int i; 49 50 for (i = 0; i < insn->n; i++) { 51 val = data[0]; 52 53 /* Order matters */ 54 outb((val >> 24) & 0xff, dev->iobase + KE_SIGN_REG(chan)); 55 outb((val >> 16) & 0xff, dev->iobase + KE_MSB_REG(chan)); 56 outb((val >> 8) & 0xff, dev->iobase + KE_MID_REG(chan)); 57 outb((val >> 0) & 0xff, dev->iobase + KE_LSB_REG(chan)); 58 } 59 60 return insn->n; 61 } 62 63 static int ke_counter_insn_read(struct comedi_device *dev, 64 struct comedi_subdevice *s, 65 struct comedi_insn *insn, 66 unsigned int *data) 67 { 68 unsigned int chan = CR_CHAN(insn->chanspec); 69 unsigned int val; 70 int i; 71 72 for (i = 0; i < insn->n; i++) { 73 /* Order matters */ 74 inb(dev->iobase + KE_LATCH_REG(chan)); 75 76 val = inb(dev->iobase + KE_LSB_REG(chan)); 77 val |= (inb(dev->iobase + KE_MID_REG(chan)) << 8); 78 val |= (inb(dev->iobase + KE_MSB_REG(chan)) << 16); 79 val |= (inb(dev->iobase + KE_SIGN_REG(chan)) << 24); 80 81 data[i] = val; 82 } 83 84 return insn->n; 85 } 86 87 static void ke_counter_reset(struct comedi_device *dev) 88 { 89 unsigned int chan; 90 91 for (chan = 0; chan < 3; chan++) 92 outb(0, dev->iobase + KE_RESET_REG(chan)); 93 } 94 95 static int ke_counter_insn_config(struct comedi_device *dev, 96 struct comedi_subdevice *s, 97 struct comedi_insn *insn, 98 unsigned int *data) 99 { 100 unsigned char src; 101 102 switch (data[0]) { 103 case INSN_CONFIG_SET_CLOCK_SRC: 104 switch (data[1]) { 105 case KE_CLK_20MHZ: /* default */ 106 src = KE_OSC_SEL_20MHZ; 107 break; 108 case KE_CLK_4MHZ: /* option */ 109 src = KE_OSC_SEL_4MHZ; 110 break; 111 case KE_CLK_EXT: /* Pin 21 on D-sub */ 112 src = KE_OSC_SEL_EXT; 113 break; 114 default: 115 return -EINVAL; 116 } 117 outb(src, dev->iobase + KE_OSC_SEL_REG); 118 break; 119 case INSN_CONFIG_GET_CLOCK_SRC: 120 src = inb(dev->iobase + KE_OSC_SEL_REG); 121 switch (src) { 122 case KE_OSC_SEL_20MHZ: 123 data[1] = KE_CLK_20MHZ; 124 data[2] = 50; /* 50ns */ 125 break; 126 case KE_OSC_SEL_4MHZ: 127 data[1] = KE_CLK_4MHZ; 128 data[2] = 250; /* 250ns */ 129 break; 130 case KE_OSC_SEL_EXT: 131 data[1] = KE_CLK_EXT; 132 data[2] = 0; /* Unknown */ 133 break; 134 default: 135 return -EINVAL; 136 } 137 break; 138 case INSN_CONFIG_RESET: 139 ke_counter_reset(dev); 140 break; 141 default: 142 return -EINVAL; 143 } 144 145 return insn->n; 146 } 147 148 static int ke_counter_do_insn_bits(struct comedi_device *dev, 149 struct comedi_subdevice *s, 150 struct comedi_insn *insn, 151 unsigned int *data) 152 { 153 if (comedi_dio_update_state(s, data)) 154 outb(s->state, dev->iobase + KE_DO_REG); 155 156 data[1] = s->state; 157 158 return insn->n; 159 } 160 161 static int ke_counter_auto_attach(struct comedi_device *dev, 162 unsigned long context_unused) 163 { 164 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 165 struct comedi_subdevice *s; 166 int ret; 167 168 ret = comedi_pci_enable(dev); 169 if (ret) 170 return ret; 171 dev->iobase = pci_resource_start(pcidev, 0); 172 173 ret = comedi_alloc_subdevices(dev, 2); 174 if (ret) 175 return ret; 176 177 s = &dev->subdevices[0]; 178 s->type = COMEDI_SUBD_COUNTER; 179 s->subdev_flags = SDF_READABLE; 180 s->n_chan = 3; 181 s->maxdata = 0x01ffffff; 182 s->range_table = &range_unknown; 183 s->insn_read = ke_counter_insn_read; 184 s->insn_write = ke_counter_insn_write; 185 s->insn_config = ke_counter_insn_config; 186 187 s = &dev->subdevices[1]; 188 s->type = COMEDI_SUBD_DO; 189 s->subdev_flags = SDF_WRITABLE; 190 s->n_chan = 3; 191 s->maxdata = 1; 192 s->range_table = &range_digital; 193 s->insn_bits = ke_counter_do_insn_bits; 194 195 outb(KE_OSC_SEL_20MHZ, dev->iobase + KE_OSC_SEL_REG); 196 197 ke_counter_reset(dev); 198 199 return 0; 200 } 201 202 static struct comedi_driver ke_counter_driver = { 203 .driver_name = "ke_counter", 204 .module = THIS_MODULE, 205 .auto_attach = ke_counter_auto_attach, 206 .detach = comedi_pci_detach, 207 }; 208 209 static int ke_counter_pci_probe(struct pci_dev *dev, 210 const struct pci_device_id *id) 211 { 212 return comedi_pci_auto_config(dev, &ke_counter_driver, 213 id->driver_data); 214 } 215 216 static const struct pci_device_id ke_counter_pci_table[] = { 217 { PCI_DEVICE(PCI_VENDOR_ID_KOLTER, 0x0014) }, 218 { 0 } 219 }; 220 MODULE_DEVICE_TABLE(pci, ke_counter_pci_table); 221 222 static struct pci_driver ke_counter_pci_driver = { 223 .name = "ke_counter", 224 .id_table = ke_counter_pci_table, 225 .probe = ke_counter_pci_probe, 226 .remove = comedi_pci_auto_unconfig, 227 }; 228 module_comedi_pci_driver(ke_counter_driver, ke_counter_pci_driver); 229 230 MODULE_AUTHOR("Comedi https://www.comedi.org"); 231 MODULE_DESCRIPTION("Comedi driver for Kolter Electronic Counter Card"); 232 MODULE_LICENSE("GPL"); 233