]>
Commit | Line | Data |
---|---|---|
e184e2be | 1 | // SPDX-License-Identifier: GPL-2.0+ |
00a22431 | 2 | /* |
96d6ed53 HS |
3 | * pcl726.c |
4 | * Comedi driver for 6/12-Channel D/A Output and DIO cards | |
5 | * | |
6 | * COMEDI - Linux Control and Measurement Device Interface | |
7 | * Copyright (C) 1998 David A. Schleef <ds@schleef.org> | |
96d6ed53 | 8 | */ |
00a22431 DS |
9 | |
10 | /* | |
96d6ed53 HS |
11 | * Driver: pcl726 |
12 | * Description: Advantech PCL-726 & compatibles | |
13 | * Author: David A. Schleef <ds@schleef.org> | |
14 | * Status: untested | |
7231e901 IA |
15 | * Devices: [Advantech] PCL-726 (pcl726), PCL-727 (pcl727), PCL-728 (pcl728), |
16 | * [ADLink] ACL-6126 (acl6126), ACL-6128 (acl6128) | |
96d6ed53 HS |
17 | * |
18 | * Configuration Options: | |
19 | * [0] - IO Base | |
20 | * [1] - IRQ (ACL-6126 only) | |
21 | * [2] - D/A output range for channel 0 | |
22 | * [3] - D/A output range for channel 1 | |
23 | * | |
24 | * Boards with > 2 analog output channels: | |
25 | * [4] - D/A output range for channel 2 | |
26 | * [5] - D/A output range for channel 3 | |
27 | * [6] - D/A output range for channel 4 | |
28 | * [7] - D/A output range for channel 5 | |
29 | * | |
30 | * Boards with > 6 analog output channels: | |
31 | * [8] - D/A output range for channel 6 | |
32 | * [9] - D/A output range for channel 7 | |
33 | * [10] - D/A output range for channel 8 | |
34 | * [11] - D/A output range for channel 9 | |
35 | * [12] - D/A output range for channel 10 | |
36 | * [13] - D/A output range for channel 11 | |
37 | * | |
38 | * For PCL-726 the D/A output ranges are: | |
39 | * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: unknown | |
40 | * | |
41 | * For PCL-727: | |
42 | * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: 4-20mA | |
43 | * | |
44 | * For PCL-728 and ACL-6128: | |
45 | * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: 0-20mA | |
46 | * | |
47 | * For ACL-6126: | |
48 | * 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA | |
49 | */ | |
00a22431 | 50 | |
ce157f80 | 51 | #include <linux/module.h> |
fff46207 | 52 | #include <linux/interrupt.h> |
00a22431 | 53 | |
fff46207 | 54 | #include "../comedidev.h" |
00a22431 | 55 | |
1821e389 HS |
56 | #define PCL726_AO_MSB_REG(x) (0x00 + ((x) * 2)) |
57 | #define PCL726_AO_LSB_REG(x) (0x01 + ((x) * 2)) | |
3f4d0101 HS |
58 | #define PCL726_DO_MSB_REG 0x0c |
59 | #define PCL726_DO_LSB_REG 0x0d | |
60 | #define PCL726_DI_MSB_REG 0x0e | |
61 | #define PCL726_DI_LSB_REG 0x0f | |
00a22431 | 62 | |
3f4d0101 HS |
63 | #define PCL727_DI_MSB_REG 0x00 |
64 | #define PCL727_DI_LSB_REG 0x01 | |
65 | #define PCL727_DO_MSB_REG 0x18 | |
66 | #define PCL727_DO_LSB_REG 0x19 | |
00a22431 | 67 | |
9ced1de6 | 68 | static const struct comedi_lrange *const rangelist_726[] = { |
08f91d18 HS |
69 | &range_unipolar5, |
70 | &range_unipolar10, | |
71 | &range_bipolar5, | |
72 | &range_bipolar10, | |
73 | &range_4_20mA, | |
74 | &range_unknown | |
00a22431 DS |
75 | }; |
76 | ||
9ced1de6 | 77 | static const struct comedi_lrange *const rangelist_727[] = { |
08f91d18 HS |
78 | &range_unipolar5, |
79 | &range_unipolar10, | |
00a22431 DS |
80 | &range_bipolar5, |
81 | &range_4_20mA | |
82 | }; | |
83 | ||
9ced1de6 | 84 | static const struct comedi_lrange *const rangelist_728[] = { |
08f91d18 HS |
85 | &range_unipolar5, |
86 | &range_unipolar10, | |
87 | &range_bipolar5, | |
88 | &range_bipolar10, | |
89 | &range_4_20mA, | |
90 | &range_0_20mA | |
00a22431 DS |
91 | }; |
92 | ||
747887a6 | 93 | struct pcl726_board { |
06b819ee | 94 | const char *name; |
16ee1e82 | 95 | unsigned long io_len; |
310923b7 | 96 | unsigned int irq_mask; |
08f91d18 HS |
97 | const struct comedi_lrange *const *ao_ranges; |
98 | int ao_num_ranges; | |
fc93af58 HS |
99 | int ao_nchan; |
100 | unsigned int have_dio:1; | |
101 | unsigned int is_pcl727:1; | |
747887a6 BP |
102 | }; |
103 | ||
1b0ef827 | 104 | static const struct pcl726_board pcl726_boards[] = { |
5cb404d9 HS |
105 | { |
106 | .name = "pcl726", | |
16ee1e82 | 107 | .io_len = 0x10, |
08f91d18 HS |
108 | .ao_ranges = &rangelist_726[0], |
109 | .ao_num_ranges = ARRAY_SIZE(rangelist_726), | |
fc93af58 HS |
110 | .ao_nchan = 6, |
111 | .have_dio = 1, | |
5cb404d9 HS |
112 | }, { |
113 | .name = "pcl727", | |
16ee1e82 | 114 | .io_len = 0x20, |
08f91d18 HS |
115 | .ao_ranges = &rangelist_727[0], |
116 | .ao_num_ranges = ARRAY_SIZE(rangelist_727), | |
fc93af58 HS |
117 | .ao_nchan = 12, |
118 | .have_dio = 1, | |
119 | .is_pcl727 = 1, | |
5cb404d9 HS |
120 | }, { |
121 | .name = "pcl728", | |
16ee1e82 | 122 | .io_len = 0x08, |
08f91d18 HS |
123 | .ao_num_ranges = ARRAY_SIZE(rangelist_728), |
124 | .ao_ranges = &rangelist_728[0], | |
fc93af58 | 125 | .ao_nchan = 2, |
5cb404d9 HS |
126 | }, { |
127 | .name = "acl6126", | |
16ee1e82 | 128 | .io_len = 0x10, |
310923b7 | 129 | .irq_mask = 0x96e8, |
08f91d18 HS |
130 | .ao_num_ranges = ARRAY_SIZE(rangelist_726), |
131 | .ao_ranges = &rangelist_726[0], | |
fc93af58 HS |
132 | .ao_nchan = 6, |
133 | .have_dio = 1, | |
5cb404d9 HS |
134 | }, { |
135 | .name = "acl6128", | |
16ee1e82 | 136 | .io_len = 0x08, |
08f91d18 HS |
137 | .ao_num_ranges = ARRAY_SIZE(rangelist_728), |
138 | .ao_ranges = &rangelist_728[0], | |
fc93af58 | 139 | .ao_nchan = 2, |
5cb404d9 | 140 | }, |
00a22431 DS |
141 | }; |
142 | ||
bf7f0610 | 143 | struct pcl726_private { |
9ced1de6 | 144 | const struct comedi_lrange *rangelist[12]; |
afee35c7 | 145 | unsigned int cmd_running:1; |
bf7f0610 BP |
146 | }; |
147 | ||
57225184 | 148 | static int pcl726_intr_insn_bits(struct comedi_device *dev, |
afee35c7 HS |
149 | struct comedi_subdevice *s, |
150 | struct comedi_insn *insn, | |
151 | unsigned int *data) | |
152 | { | |
153 | data[1] = 0; | |
154 | return insn->n; | |
155 | } | |
156 | ||
57225184 | 157 | static int pcl726_intr_cmdtest(struct comedi_device *dev, |
afee35c7 HS |
158 | struct comedi_subdevice *s, |
159 | struct comedi_cmd *cmd) | |
160 | { | |
161 | int err = 0; | |
162 | ||
163 | /* Step 1 : check if triggers are trivially valid */ | |
164 | ||
bdd62d8d IA |
165 | err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); |
166 | err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); | |
167 | err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); | |
168 | err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
169 | err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); | |
afee35c7 HS |
170 | |
171 | if (err) | |
172 | return 1; | |
173 | ||
174 | /* Step 2a : make sure trigger sources are unique */ | |
175 | /* Step 2b : and mutually compatible */ | |
176 | ||
afee35c7 HS |
177 | /* Step 3: check if arguments are trivially valid */ |
178 | ||
bdd62d8d IA |
179 | err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); |
180 | err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
181 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); | |
182 | err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, | |
183 | cmd->chanlist_len); | |
184 | err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); | |
afee35c7 HS |
185 | |
186 | if (err) | |
187 | return 3; | |
188 | ||
e24e9216 | 189 | /* Step 4: fix up any arguments */ |
afee35c7 | 190 | |
e24e9216 | 191 | /* Step 5: check channel list if it exists */ |
afee35c7 HS |
192 | |
193 | return 0; | |
194 | } | |
195 | ||
57225184 | 196 | static int pcl726_intr_cmd(struct comedi_device *dev, |
afee35c7 HS |
197 | struct comedi_subdevice *s) |
198 | { | |
199 | struct pcl726_private *devpriv = dev->private; | |
200 | ||
201 | devpriv->cmd_running = 1; | |
202 | ||
203 | return 0; | |
204 | } | |
205 | ||
57225184 | 206 | static int pcl726_intr_cancel(struct comedi_device *dev, |
afee35c7 HS |
207 | struct comedi_subdevice *s) |
208 | { | |
209 | struct pcl726_private *devpriv = dev->private; | |
210 | ||
211 | devpriv->cmd_running = 0; | |
212 | ||
213 | return 0; | |
214 | } | |
215 | ||
57225184 | 216 | static irqreturn_t pcl726_interrupt(int irq, void *d) |
fff46207 | 217 | { |
afee35c7 HS |
218 | struct comedi_device *dev = d; |
219 | struct comedi_subdevice *s = dev->read_subdev; | |
220 | struct pcl726_private *devpriv = dev->private; | |
221 | ||
222 | if (devpriv->cmd_running) { | |
57225184 | 223 | pcl726_intr_cancel(dev, s); |
afee35c7 | 224 | |
778d695e | 225 | comedi_buf_write_samples(s, &s->state, 1); |
ae4ac0db | 226 | comedi_handle_events(dev, s); |
afee35c7 HS |
227 | } |
228 | ||
fff46207 HS |
229 | return IRQ_HANDLED; |
230 | } | |
231 | ||
1821e389 HS |
232 | static int pcl726_ao_insn_write(struct comedi_device *dev, |
233 | struct comedi_subdevice *s, | |
234 | struct comedi_insn *insn, | |
235 | unsigned int *data) | |
00a22431 | 236 | { |
b4f58e1f HS |
237 | unsigned int chan = CR_CHAN(insn->chanspec); |
238 | unsigned int range = CR_RANGE(insn->chanspec); | |
1821e389 HS |
239 | int i; |
240 | ||
241 | for (i = 0; i < insn->n; i++) { | |
a0062e84 HS |
242 | unsigned int val = data[i]; |
243 | ||
244 | s->readback[chan] = val; | |
00a22431 | 245 | |
1821e389 | 246 | /* bipolar data to the DAC is two's complement */ |
b4f58e1f | 247 | if (comedi_chan_range_is_bipolar(s, chan, range)) |
1821e389 HS |
248 | val = comedi_offset_munge(s, val); |
249 | ||
250 | /* order is important, MSB then LSB */ | |
251 | outb((val >> 8) & 0xff, dev->iobase + PCL726_AO_MSB_REG(chan)); | |
252 | outb(val & 0xff, dev->iobase + PCL726_AO_LSB_REG(chan)); | |
00a22431 DS |
253 | } |
254 | ||
1821e389 | 255 | return insn->n; |
00a22431 DS |
256 | } |
257 | ||
0a85b6f0 MT |
258 | static int pcl726_di_insn_bits(struct comedi_device *dev, |
259 | struct comedi_subdevice *s, | |
3f4d0101 HS |
260 | struct comedi_insn *insn, |
261 | unsigned int *data) | |
00a22431 | 262 | { |
334e2f59 | 263 | const struct pcl726_board *board = dev->board_ptr; |
3f4d0101 HS |
264 | unsigned int val; |
265 | ||
266 | if (board->is_pcl727) { | |
267 | val = inb(dev->iobase + PCL727_DI_LSB_REG); | |
268 | val |= (inb(dev->iobase + PCL727_DI_MSB_REG) << 8); | |
269 | } else { | |
270 | val = inb(dev->iobase + PCL726_DI_LSB_REG); | |
271 | val |= (inb(dev->iobase + PCL726_DI_MSB_REG) << 8); | |
272 | } | |
d877269e | 273 | |
3f4d0101 | 274 | data[1] = val; |
00a22431 | 275 | |
a2714e3e | 276 | return insn->n; |
00a22431 DS |
277 | } |
278 | ||
0a85b6f0 MT |
279 | static int pcl726_do_insn_bits(struct comedi_device *dev, |
280 | struct comedi_subdevice *s, | |
b3ff824a HS |
281 | struct comedi_insn *insn, |
282 | unsigned int *data) | |
00a22431 | 283 | { |
334e2f59 | 284 | const struct pcl726_board *board = dev->board_ptr; |
3f4d0101 | 285 | unsigned long io = dev->iobase; |
b3ff824a HS |
286 | unsigned int mask; |
287 | ||
288 | mask = comedi_dio_update_state(s, data); | |
289 | if (mask) { | |
3f4d0101 HS |
290 | if (board->is_pcl727) { |
291 | if (mask & 0x00ff) | |
292 | outb(s->state & 0xff, io + PCL727_DO_LSB_REG); | |
293 | if (mask & 0xff00) | |
294 | outb((s->state >> 8), io + PCL727_DO_MSB_REG); | |
295 | } else { | |
296 | if (mask & 0x00ff) | |
297 | outb(s->state & 0xff, io + PCL726_DO_LSB_REG); | |
298 | if (mask & 0xff00) | |
299 | outb((s->state >> 8), io + PCL726_DO_MSB_REG); | |
300 | } | |
00a22431 | 301 | } |
00a22431 DS |
302 | |
303 | data[1] = s->state; | |
304 | ||
a2714e3e | 305 | return insn->n; |
00a22431 DS |
306 | } |
307 | ||
47825a9b HS |
308 | static int pcl726_attach(struct comedi_device *dev, |
309 | struct comedi_devconfig *it) | |
00a22431 | 310 | { |
334e2f59 | 311 | const struct pcl726_board *board = dev->board_ptr; |
9a1a6cf8 | 312 | struct pcl726_private *devpriv; |
34c43922 | 313 | struct comedi_subdevice *s; |
afee35c7 | 314 | int subdev; |
47825a9b HS |
315 | int ret; |
316 | int i; | |
00a22431 | 317 | |
16ee1e82 | 318 | ret = comedi_request_region(dev, it->options[0], board->io_len); |
99c671a6 HS |
319 | if (ret) |
320 | return ret; | |
00a22431 | 321 | |
0bdab509 | 322 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
323 | if (!devpriv) |
324 | return -ENOMEM; | |
00a22431 | 325 | |
fff46207 HS |
326 | /* |
327 | * Hook up the external trigger source interrupt only if the | |
328 | * user config option is valid and the board supports interrupts. | |
329 | */ | |
310923b7 | 330 | if (it->options[1] && (board->irq_mask & (1 << it->options[1]))) { |
57225184 | 331 | ret = request_irq(it->options[1], pcl726_interrupt, 0, |
fff46207 HS |
332 | dev->board_name, dev); |
333 | if (ret == 0) { | |
334 | /* External trigger source is from Pin-17 of CN3 */ | |
335 | dev->irq = it->options[1]; | |
00a22431 DS |
336 | } |
337 | } | |
338 | ||
716343c4 HS |
339 | /* setup the per-channel analog output range_table_list */ |
340 | for (i = 0; i < 12; i++) { | |
341 | unsigned int opt = it->options[2 + i]; | |
342 | ||
fc93af58 | 343 | if (opt < board->ao_num_ranges && i < board->ao_nchan) |
08f91d18 | 344 | devpriv->rangelist[i] = board->ao_ranges[opt]; |
716343c4 HS |
345 | else |
346 | devpriv->rangelist[i] = &range_unknown; | |
347 | } | |
348 | ||
afee35c7 HS |
349 | subdev = board->have_dio ? 3 : 1; |
350 | if (dev->irq) | |
351 | subdev++; | |
352 | ret = comedi_alloc_subdevices(dev, subdev); | |
8b6c5694 | 353 | if (ret) |
00a22431 DS |
354 | return ret; |
355 | ||
afee35c7 HS |
356 | subdev = 0; |
357 | ||
47825a9b | 358 | /* Analog Output subdevice */ |
afee35c7 | 359 | s = &dev->subdevices[subdev++]; |
47825a9b HS |
360 | s->type = COMEDI_SUBD_AO; |
361 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND; | |
fc93af58 | 362 | s->n_chan = board->ao_nchan; |
47825a9b | 363 | s->maxdata = 0x0fff; |
00a22431 | 364 | s->range_table_list = devpriv->rangelist; |
47825a9b | 365 | s->insn_write = pcl726_ao_insn_write; |
a0062e84 HS |
366 | |
367 | ret = comedi_alloc_subdev_readback(s); | |
368 | if (ret) | |
369 | return ret; | |
47825a9b HS |
370 | |
371 | if (board->have_dio) { | |
372 | /* Digital Input subdevice */ | |
afee35c7 | 373 | s = &dev->subdevices[subdev++]; |
47825a9b HS |
374 | s->type = COMEDI_SUBD_DI; |
375 | s->subdev_flags = SDF_READABLE; | |
376 | s->n_chan = 16; | |
377 | s->maxdata = 1; | |
378 | s->insn_bits = pcl726_di_insn_bits; | |
379 | s->range_table = &range_digital; | |
380 | ||
381 | /* Digital Output subdevice */ | |
afee35c7 | 382 | s = &dev->subdevices[subdev++]; |
47825a9b HS |
383 | s->type = COMEDI_SUBD_DO; |
384 | s->subdev_flags = SDF_WRITABLE; | |
385 | s->n_chan = 16; | |
386 | s->maxdata = 1; | |
387 | s->insn_bits = pcl726_do_insn_bits; | |
388 | s->range_table = &range_digital; | |
00a22431 DS |
389 | } |
390 | ||
afee35c7 HS |
391 | if (dev->irq) { |
392 | /* Digial Input subdevice - Interrupt support */ | |
393 | s = &dev->subdevices[subdev++]; | |
394 | dev->read_subdev = s; | |
395 | s->type = COMEDI_SUBD_DI; | |
396 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; | |
397 | s->n_chan = 1; | |
398 | s->maxdata = 1; | |
399 | s->range_table = &range_digital; | |
57225184 | 400 | s->insn_bits = pcl726_intr_insn_bits; |
f50cebb9 | 401 | s->len_chanlist = 1; |
57225184 HS |
402 | s->do_cmdtest = pcl726_intr_cmdtest; |
403 | s->do_cmd = pcl726_intr_cmd; | |
404 | s->cancel = pcl726_intr_cancel; | |
afee35c7 HS |
405 | } |
406 | ||
00a22431 DS |
407 | return 0; |
408 | } | |
409 | ||
294f930d | 410 | static struct comedi_driver pcl726_driver = { |
2bc62b67 HS |
411 | .driver_name = "pcl726", |
412 | .module = THIS_MODULE, | |
413 | .attach = pcl726_attach, | |
3d1fe3f7 | 414 | .detach = comedi_legacy_detach, |
1b0ef827 HS |
415 | .board_name = &pcl726_boards[0].name, |
416 | .num_names = ARRAY_SIZE(pcl726_boards), | |
2bc62b67 HS |
417 | .offset = sizeof(struct pcl726_board), |
418 | }; | |
294f930d | 419 | module_comedi_driver(pcl726_driver); |
2bc62b67 | 420 | |
90f703d3 | 421 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
71e460d2 | 422 | MODULE_DESCRIPTION("Comedi driver for Advantech PCL-726 & compatibles"); |
90f703d3 | 423 | MODULE_LICENSE("GPL"); |