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