1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * COMEDI driver for the Advantech PCI-1760 |
4 | * Copyright (C) 2015 H Hartley Sweeten <hsweeten@visionengravers.com> |
5 | * |
6 | * Based on the pci1760 support in the adv_pci_dio driver written by: |
7 | * Michal Dobes <dobes@tesnet.cz> |
8 | * |
9 | * COMEDI - Linux Control and Measurement Device Interface |
10 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
11 | */ |
12 | |
13 | /* |
14 | * Driver: adv_pci1760 |
15 | * Description: Advantech PCI-1760 Relay & Isolated Digital Input Card |
16 | * Devices: [Advantech] PCI-1760 (adv_pci1760) |
17 | * Author: H Hartley Sweeten <hsweeten@visionengravers.com> |
18 | * Updated: Fri, 13 Nov 2015 12:34:00 -0700 |
19 | * Status: untested |
20 | * |
21 | * Configuration Options: not applicable, uses PCI auto config |
22 | */ |
23 | |
24 | #include <linux/module.h> |
25 | #include <linux/comedi/comedi_pci.h> |
26 | |
27 | /* |
28 | * PCI-1760 Register Map |
29 | * |
30 | * Outgoing Mailbox Bytes |
31 | * OMB3: Not used (must be 0) |
32 | * OMB2: The command code to the PCI-1760 |
33 | * OMB1: The hi byte of the parameter for the command in OMB2 |
34 | * OMB0: The lo byte of the parameter for the command in OMB2 |
35 | * |
36 | * Incoming Mailbox Bytes |
37 | * IMB3: The Isolated Digital Input status (updated every 100us) |
38 | * IMB2: The current command (matches OMB2 when command is successful) |
39 | * IMB1: The hi byte of the feedback data for the command in OMB2 |
40 | * IMB0: The lo byte of the feedback data for the command in OMB2 |
41 | * |
42 | * Interrupt Control/Status |
43 | * INTCSR3: Not used (must be 0) |
44 | * INTCSR2: The interrupt status (read only) |
45 | * INTCSR1: Interrupt enable/disable |
46 | * INTCSR0: Not used (must be 0) |
47 | */ |
48 | #define PCI1760_OMB_REG(x) (0x0c + (x)) |
49 | #define PCI1760_IMB_REG(x) (0x1c + (x)) |
50 | #define PCI1760_INTCSR_REG(x) (0x38 + (x)) |
51 | #define PCI1760_INTCSR1_IRQ_ENA BIT(5) |
52 | #define PCI1760_INTCSR2_OMB_IRQ BIT(0) |
53 | #define PCI1760_INTCSR2_IMB_IRQ BIT(1) |
54 | #define PCI1760_INTCSR2_IRQ_STATUS BIT(6) |
55 | #define PCI1760_INTCSR2_IRQ_ASSERTED BIT(7) |
56 | |
57 | /* PCI-1760 command codes */ |
58 | #define PCI1760_CMD_CLR_IMB2 0x00 /* Clears IMB2 */ |
59 | #define PCI1760_CMD_SET_DO 0x01 /* Set output state */ |
60 | #define PCI1760_CMD_GET_DO 0x02 /* Read output status */ |
61 | #define PCI1760_CMD_GET_STATUS 0x07 /* Read current status */ |
62 | #define PCI1760_CMD_GET_FW_VER 0x0e /* Read firmware version */ |
63 | #define PCI1760_CMD_GET_HW_VER 0x0f /* Read hardware version */ |
64 | #define PCI1760_CMD_SET_PWM_HI(x) (0x10 + (x) * 2) /* Set "hi" period */ |
65 | #define PCI1760_CMD_SET_PWM_LO(x) (0x11 + (x) * 2) /* Set "lo" period */ |
66 | #define PCI1760_CMD_SET_PWM_CNT(x) (0x14 + (x)) /* Set burst count */ |
67 | #define PCI1760_CMD_ENA_PWM 0x1f /* Enable PWM outputs */ |
68 | #define PCI1760_CMD_ENA_FILT 0x20 /* Enable input filter */ |
69 | #define PCI1760_CMD_ENA_PAT_MATCH 0x21 /* Enable input pattern match */ |
70 | #define PCI1760_CMD_SET_PAT_MATCH 0x22 /* Set input pattern match */ |
71 | #define PCI1760_CMD_ENA_RISE_EDGE 0x23 /* Enable input rising edge */ |
72 | #define PCI1760_CMD_ENA_FALL_EDGE 0x24 /* Enable input falling edge */ |
73 | #define PCI1760_CMD_ENA_CNT 0x28 /* Enable counter */ |
74 | #define PCI1760_CMD_RST_CNT 0x29 /* Reset counter */ |
75 | #define PCI1760_CMD_ENA_CNT_OFLOW 0x2a /* Enable counter overflow */ |
76 | #define PCI1760_CMD_ENA_CNT_MATCH 0x2b /* Enable counter match */ |
77 | #define PCI1760_CMD_SET_CNT_EDGE 0x2c /* Set counter edge */ |
78 | #define PCI1760_CMD_GET_CNT 0x2f /* Reads counter value */ |
79 | #define PCI1760_CMD_SET_HI_SAMP(x) (0x30 + (x)) /* Set "hi" sample time */ |
80 | #define PCI1760_CMD_SET_LO_SAMP(x) (0x38 + (x)) /* Set "lo" sample time */ |
81 | #define PCI1760_CMD_SET_CNT(x) (0x40 + (x)) /* Set counter reset val */ |
82 | #define PCI1760_CMD_SET_CNT_MATCH(x) (0x48 + (x)) /* Set counter match val */ |
83 | #define PCI1760_CMD_GET_INT_FLAGS 0x60 /* Read interrupt flags */ |
84 | #define PCI1760_CMD_GET_INT_FLAGS_MATCH BIT(0) |
85 | #define PCI1760_CMD_GET_INT_FLAGS_COS BIT(1) |
86 | #define PCI1760_CMD_GET_INT_FLAGS_OFLOW BIT(2) |
87 | #define PCI1760_CMD_GET_OS 0x61 /* Read edge change flags */ |
88 | #define PCI1760_CMD_GET_CNT_STATUS 0x62 /* Read counter oflow/match */ |
89 | |
90 | #define PCI1760_CMD_TIMEOUT 250 /* 250 usec timeout */ |
91 | #define PCI1760_CMD_RETRIES 3 /* limit number of retries */ |
92 | |
93 | #define PCI1760_PWM_TIMEBASE 100000 /* 1 unit = 100 usec */ |
94 | |
95 | static int pci1760_send_cmd(struct comedi_device *dev, |
96 | unsigned char cmd, unsigned short val) |
97 | { |
98 | unsigned long timeout; |
99 | |
100 | /* send the command and parameter */ |
101 | outb(value: val & 0xff, port: dev->iobase + PCI1760_OMB_REG(0)); |
102 | outb(value: (val >> 8) & 0xff, port: dev->iobase + PCI1760_OMB_REG(1)); |
103 | outb(value: cmd, port: dev->iobase + PCI1760_OMB_REG(2)); |
104 | outb(value: 0, port: dev->iobase + PCI1760_OMB_REG(3)); |
105 | |
106 | /* datasheet says to allow up to 250 usec for the command to complete */ |
107 | timeout = jiffies + usecs_to_jiffies(PCI1760_CMD_TIMEOUT); |
108 | do { |
109 | if (inb(port: dev->iobase + PCI1760_IMB_REG(2)) == cmd) { |
110 | /* command success; return the feedback data */ |
111 | return inb(port: dev->iobase + PCI1760_IMB_REG(0)) | |
112 | (inb(port: dev->iobase + PCI1760_IMB_REG(1)) << 8); |
113 | } |
114 | cpu_relax(); |
115 | } while (time_before(jiffies, timeout)); |
116 | |
117 | return -EBUSY; |
118 | } |
119 | |
120 | static int pci1760_cmd(struct comedi_device *dev, |
121 | unsigned char cmd, unsigned short val) |
122 | { |
123 | int repeats; |
124 | int ret; |
125 | |
126 | /* send PCI1760_CMD_CLR_IMB2 between identical commands */ |
127 | if (inb(port: dev->iobase + PCI1760_IMB_REG(2)) == cmd) { |
128 | ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, val: 0); |
129 | if (ret < 0) { |
130 | /* timeout? try it once more */ |
131 | ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, val: 0); |
132 | if (ret < 0) |
133 | return -ETIMEDOUT; |
134 | } |
135 | } |
136 | |
137 | /* datasheet says to keep retrying the command */ |
138 | for (repeats = 0; repeats < PCI1760_CMD_RETRIES; repeats++) { |
139 | ret = pci1760_send_cmd(dev, cmd, val); |
140 | if (ret >= 0) |
141 | return ret; |
142 | } |
143 | |
144 | /* command failed! */ |
145 | return -ETIMEDOUT; |
146 | } |
147 | |
148 | static int pci1760_di_insn_bits(struct comedi_device *dev, |
149 | struct comedi_subdevice *s, |
150 | struct comedi_insn *insn, |
151 | unsigned int *data) |
152 | { |
153 | data[1] = inb(port: dev->iobase + PCI1760_IMB_REG(3)); |
154 | |
155 | return insn->n; |
156 | } |
157 | |
158 | static int pci1760_do_insn_bits(struct comedi_device *dev, |
159 | struct comedi_subdevice *s, |
160 | struct comedi_insn *insn, |
161 | unsigned int *data) |
162 | { |
163 | int ret; |
164 | |
165 | if (comedi_dio_update_state(s, data)) { |
166 | ret = pci1760_cmd(dev, PCI1760_CMD_SET_DO, val: s->state); |
167 | if (ret < 0) |
168 | return ret; |
169 | } |
170 | |
171 | data[1] = s->state; |
172 | |
173 | return insn->n; |
174 | } |
175 | |
176 | static int pci1760_pwm_ns_to_div(unsigned int flags, unsigned int ns) |
177 | { |
178 | unsigned int divisor; |
179 | |
180 | switch (flags) { |
181 | case CMDF_ROUND_NEAREST: |
182 | divisor = DIV_ROUND_CLOSEST(ns, PCI1760_PWM_TIMEBASE); |
183 | break; |
184 | case CMDF_ROUND_UP: |
185 | divisor = DIV_ROUND_UP(ns, PCI1760_PWM_TIMEBASE); |
186 | break; |
187 | case CMDF_ROUND_DOWN: |
188 | divisor = ns / PCI1760_PWM_TIMEBASE; |
189 | break; |
190 | default: |
191 | return -EINVAL; |
192 | } |
193 | |
194 | if (divisor < 1) |
195 | divisor = 1; |
196 | if (divisor > 0xffff) |
197 | divisor = 0xffff; |
198 | |
199 | return divisor; |
200 | } |
201 | |
202 | static int pci1760_pwm_enable(struct comedi_device *dev, |
203 | unsigned int chan, bool enable) |
204 | { |
205 | int ret; |
206 | |
207 | ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, PCI1760_CMD_ENA_PWM); |
208 | if (ret < 0) |
209 | return ret; |
210 | |
211 | if (enable) |
212 | ret |= BIT(chan); |
213 | else |
214 | ret &= ~BIT(chan); |
215 | |
216 | return pci1760_cmd(dev, PCI1760_CMD_ENA_PWM, val: ret); |
217 | } |
218 | |
219 | static int pci1760_pwm_insn_config(struct comedi_device *dev, |
220 | struct comedi_subdevice *s, |
221 | struct comedi_insn *insn, |
222 | unsigned int *data) |
223 | { |
224 | unsigned int chan = CR_CHAN(insn->chanspec); |
225 | int hi_div; |
226 | int lo_div; |
227 | int ret; |
228 | |
229 | switch (data[0]) { |
230 | case INSN_CONFIG_ARM: |
231 | ret = pci1760_pwm_enable(dev, chan, enable: false); |
232 | if (ret < 0) |
233 | return ret; |
234 | |
235 | if (data[1] > 0xffff) |
236 | return -EINVAL; |
237 | ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_CNT(chan), val: data[1]); |
238 | if (ret < 0) |
239 | return ret; |
240 | |
241 | ret = pci1760_pwm_enable(dev, chan, enable: true); |
242 | if (ret < 0) |
243 | return ret; |
244 | break; |
245 | case INSN_CONFIG_DISARM: |
246 | ret = pci1760_pwm_enable(dev, chan, enable: false); |
247 | if (ret < 0) |
248 | return ret; |
249 | break; |
250 | case INSN_CONFIG_PWM_OUTPUT: |
251 | ret = pci1760_pwm_enable(dev, chan, enable: false); |
252 | if (ret < 0) |
253 | return ret; |
254 | |
255 | hi_div = pci1760_pwm_ns_to_div(flags: data[1], ns: data[2]); |
256 | lo_div = pci1760_pwm_ns_to_div(flags: data[3], ns: data[4]); |
257 | if (hi_div < 0 || lo_div < 0) |
258 | return -EINVAL; |
259 | if ((hi_div * PCI1760_PWM_TIMEBASE) != data[2] || |
260 | (lo_div * PCI1760_PWM_TIMEBASE) != data[4]) { |
261 | data[2] = hi_div * PCI1760_PWM_TIMEBASE; |
262 | data[4] = lo_div * PCI1760_PWM_TIMEBASE; |
263 | return -EAGAIN; |
264 | } |
265 | ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_HI(chan), val: hi_div); |
266 | if (ret < 0) |
267 | return ret; |
268 | ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_LO(chan), val: lo_div); |
269 | if (ret < 0) |
270 | return ret; |
271 | break; |
272 | case INSN_CONFIG_GET_PWM_OUTPUT: |
273 | hi_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, |
274 | PCI1760_CMD_SET_PWM_HI(chan)); |
275 | lo_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, |
276 | PCI1760_CMD_SET_PWM_LO(chan)); |
277 | if (hi_div < 0 || lo_div < 0) |
278 | return -ETIMEDOUT; |
279 | |
280 | data[1] = hi_div * PCI1760_PWM_TIMEBASE; |
281 | data[2] = lo_div * PCI1760_PWM_TIMEBASE; |
282 | break; |
283 | case INSN_CONFIG_GET_PWM_STATUS: |
284 | ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, |
285 | PCI1760_CMD_ENA_PWM); |
286 | if (ret < 0) |
287 | return ret; |
288 | |
289 | data[1] = (ret & BIT(chan)) ? 1 : 0; |
290 | break; |
291 | default: |
292 | return -EINVAL; |
293 | } |
294 | |
295 | return insn->n; |
296 | } |
297 | |
298 | static void pci1760_reset(struct comedi_device *dev) |
299 | { |
300 | int i; |
301 | |
302 | /* disable interrupts (intcsr2 is read-only) */ |
303 | outb(value: 0, port: dev->iobase + PCI1760_INTCSR_REG(0)); |
304 | outb(value: 0, port: dev->iobase + PCI1760_INTCSR_REG(1)); |
305 | outb(value: 0, port: dev->iobase + PCI1760_INTCSR_REG(3)); |
306 | |
307 | /* disable counters */ |
308 | pci1760_cmd(dev, PCI1760_CMD_ENA_CNT, val: 0); |
309 | |
310 | /* disable overflow interrupts */ |
311 | pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_OFLOW, val: 0); |
312 | |
313 | /* disable match */ |
314 | pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_MATCH, val: 0); |
315 | |
316 | /* set match and counter reset values */ |
317 | for (i = 0; i < 8; i++) { |
318 | pci1760_cmd(dev, PCI1760_CMD_SET_CNT_MATCH(i), val: 0x8000); |
319 | pci1760_cmd(dev, PCI1760_CMD_SET_CNT(i), val: 0x0000); |
320 | } |
321 | |
322 | /* reset counters to reset values */ |
323 | pci1760_cmd(dev, PCI1760_CMD_RST_CNT, val: 0xff); |
324 | |
325 | /* set counter count edges */ |
326 | pci1760_cmd(dev, PCI1760_CMD_SET_CNT_EDGE, val: 0); |
327 | |
328 | /* disable input filters */ |
329 | pci1760_cmd(dev, PCI1760_CMD_ENA_FILT, val: 0); |
330 | |
331 | /* disable pattern matching */ |
332 | pci1760_cmd(dev, PCI1760_CMD_ENA_PAT_MATCH, val: 0); |
333 | |
334 | /* set pattern match value */ |
335 | pci1760_cmd(dev, PCI1760_CMD_SET_PAT_MATCH, val: 0); |
336 | } |
337 | |
338 | static int pci1760_auto_attach(struct comedi_device *dev, |
339 | unsigned long context) |
340 | { |
341 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
342 | struct comedi_subdevice *s; |
343 | int ret; |
344 | |
345 | ret = comedi_pci_enable(dev); |
346 | if (ret) |
347 | return ret; |
348 | dev->iobase = pci_resource_start(pcidev, 0); |
349 | |
350 | pci1760_reset(dev); |
351 | |
352 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
353 | if (ret) |
354 | return ret; |
355 | |
356 | /* Digital Input subdevice */ |
357 | s = &dev->subdevices[0]; |
358 | s->type = COMEDI_SUBD_DI; |
359 | s->subdev_flags = SDF_READABLE; |
360 | s->n_chan = 8; |
361 | s->maxdata = 1; |
362 | s->range_table = &range_digital; |
363 | s->insn_bits = pci1760_di_insn_bits; |
364 | |
365 | /* Digital Output subdevice */ |
366 | s = &dev->subdevices[1]; |
367 | s->type = COMEDI_SUBD_DO; |
368 | s->subdev_flags = SDF_WRITABLE; |
369 | s->n_chan = 8; |
370 | s->maxdata = 1; |
371 | s->range_table = &range_digital; |
372 | s->insn_bits = pci1760_do_insn_bits; |
373 | |
374 | /* get the current state of the outputs */ |
375 | ret = pci1760_cmd(dev, PCI1760_CMD_GET_DO, val: 0); |
376 | if (ret < 0) |
377 | return ret; |
378 | s->state = ret; |
379 | |
380 | /* PWM subdevice */ |
381 | s = &dev->subdevices[2]; |
382 | s->type = COMEDI_SUBD_PWM; |
383 | s->subdev_flags = SDF_PWM_COUNTER; |
384 | s->n_chan = 2; |
385 | s->insn_config = pci1760_pwm_insn_config; |
386 | |
387 | /* Counter subdevice */ |
388 | s = &dev->subdevices[3]; |
389 | s->type = COMEDI_SUBD_UNUSED; |
390 | |
391 | return 0; |
392 | } |
393 | |
394 | static struct comedi_driver pci1760_driver = { |
395 | .driver_name = "adv_pci1760" , |
396 | .module = THIS_MODULE, |
397 | .auto_attach = pci1760_auto_attach, |
398 | .detach = comedi_pci_detach, |
399 | }; |
400 | |
401 | static int pci1760_pci_probe(struct pci_dev *dev, |
402 | const struct pci_device_id *id) |
403 | { |
404 | return comedi_pci_auto_config(pcidev: dev, driver: &pci1760_driver, context: id->driver_data); |
405 | } |
406 | |
407 | static const struct pci_device_id pci1760_pci_table[] = { |
408 | { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1760) }, |
409 | { 0 } |
410 | }; |
411 | MODULE_DEVICE_TABLE(pci, pci1760_pci_table); |
412 | |
413 | static struct pci_driver pci1760_pci_driver = { |
414 | .name = "adv_pci1760" , |
415 | .id_table = pci1760_pci_table, |
416 | .probe = pci1760_pci_probe, |
417 | .remove = comedi_pci_auto_unconfig, |
418 | }; |
419 | module_comedi_pci_driver(pci1760_driver, pci1760_pci_driver); |
420 | |
421 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
422 | MODULE_DESCRIPTION("Comedi driver for Advantech PCI-1760" ); |
423 | MODULE_LICENSE("GPL" ); |
424 | |