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(value: (val >> 24) & 0xff, port: dev->iobase + KE_SIGN_REG(chan)); |
54 | outb(value: (val >> 16) & 0xff, port: dev->iobase + KE_MSB_REG(chan)); |
55 | outb(value: (val >> 8) & 0xff, port: dev->iobase + KE_MID_REG(chan)); |
56 | outb(value: (val >> 0) & 0xff, port: 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(port: dev->iobase + KE_LATCH_REG(chan)); |
74 | |
75 | val = inb(port: dev->iobase + KE_LSB_REG(chan)); |
76 | val |= (inb(port: dev->iobase + KE_MID_REG(chan)) << 8); |
77 | val |= (inb(port: dev->iobase + KE_MSB_REG(chan)) << 16); |
78 | val |= (inb(port: 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(value: 0, port: 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(value: src, port: dev->iobase + KE_OSC_SEL_REG); |
117 | break; |
118 | case INSN_CONFIG_GET_CLOCK_SRC: |
119 | src = inb(port: 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(value: s->state, port: 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, num_subdevices: 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, port: 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(pcidev: dev, driver: &ke_counter_driver, |
212 | context: 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 | |