1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#include <linux/module.h>
30#include <linux/delay.h>
31#include <linux/interrupt.h>
32
33#include "../comedidev.h"
34
35#include "comedi_8254.h"
36
37
38
39
40#define PCL711_TIMER_BASE 0x00
41#define PCL711_AI_LSB_REG 0x04
42#define PCL711_AI_MSB_REG 0x05
43#define PCL711_AI_MSB_DRDY BIT(4)
44#define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2))
45#define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2))
46#define PCL711_DI_LSB_REG 0x06
47#define PCL711_DI_MSB_REG 0x07
48#define PCL711_INT_STAT_REG 0x08
49#define PCL711_INT_STAT_CLR (0 << 0)
50#define PCL711_AI_GAIN_REG 0x09
51#define PCL711_AI_GAIN(x) (((x) & 0xf) << 0)
52#define PCL711_MUX_REG 0x0a
53#define PCL711_MUX_CHAN(x) (((x) & 0xf) << 0)
54#define PCL711_MUX_CS0 BIT(4)
55#define PCL711_MUX_CS1 BIT(5)
56#define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1)
57#define PCL711_MODE_REG 0x0b
58#define PCL711_MODE(x) (((x) & 0x7) << 0)
59#define PCL711_MODE_DEFAULT PCL711_MODE(0)
60#define PCL711_MODE_SOFTTRIG PCL711_MODE(1)
61#define PCL711_MODE_EXT PCL711_MODE(2)
62#define PCL711_MODE_EXT_IRQ PCL711_MODE(3)
63#define PCL711_MODE_PACER PCL711_MODE(4)
64#define PCL711_MODE_PACER_IRQ PCL711_MODE(6)
65#define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4)
66#define PCL711_SOFTTRIG_REG 0x0c
67#define PCL711_SOFTTRIG (0 << 0)
68#define PCL711_DO_LSB_REG 0x0d
69#define PCL711_DO_MSB_REG 0x0e
70
71static const struct comedi_lrange range_pcl711b_ai = {
72 5, {
73 BIP_RANGE(5),
74 BIP_RANGE(2.5),
75 BIP_RANGE(1.25),
76 BIP_RANGE(0.625),
77 BIP_RANGE(0.3125)
78 }
79};
80
81static const struct comedi_lrange range_acl8112hg_ai = {
82 12, {
83 BIP_RANGE(5),
84 BIP_RANGE(0.5),
85 BIP_RANGE(0.05),
86 BIP_RANGE(0.005),
87 UNI_RANGE(10),
88 UNI_RANGE(1),
89 UNI_RANGE(0.1),
90 UNI_RANGE(0.01),
91 BIP_RANGE(10),
92 BIP_RANGE(1),
93 BIP_RANGE(0.1),
94 BIP_RANGE(0.01)
95 }
96};
97
98static const struct comedi_lrange range_acl8112dg_ai = {
99 9, {
100 BIP_RANGE(5),
101 BIP_RANGE(2.5),
102 BIP_RANGE(1.25),
103 BIP_RANGE(0.625),
104 UNI_RANGE(10),
105 UNI_RANGE(5),
106 UNI_RANGE(2.5),
107 UNI_RANGE(1.25),
108 BIP_RANGE(10)
109 }
110};
111
112struct pcl711_board {
113 const char *name;
114 int n_aichan;
115 int n_aochan;
116 int maxirq;
117 const struct comedi_lrange *ai_range_type;
118};
119
120static const struct pcl711_board boardtypes[] = {
121 {
122 .name = "pcl711",
123 .n_aichan = 8,
124 .n_aochan = 1,
125 .ai_range_type = &range_bipolar5,
126 }, {
127 .name = "pcl711b",
128 .n_aichan = 8,
129 .n_aochan = 1,
130 .maxirq = 7,
131 .ai_range_type = &range_pcl711b_ai,
132 }, {
133 .name = "acl8112hg",
134 .n_aichan = 16,
135 .n_aochan = 2,
136 .maxirq = 15,
137 .ai_range_type = &range_acl8112hg_ai,
138 }, {
139 .name = "acl8112dg",
140 .n_aichan = 16,
141 .n_aochan = 2,
142 .maxirq = 15,
143 .ai_range_type = &range_acl8112dg_ai,
144 },
145};
146
147static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
148{
149
150
151
152
153
154
155
156
157 if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
158 mode |= PCL711_MODE_IRQ(dev->irq);
159
160 outb(mode, dev->iobase + PCL711_MODE_REG);
161}
162
163static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
164 struct comedi_subdevice *s)
165{
166 unsigned int val;
167
168 val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
169 val |= inb(dev->iobase + PCL711_AI_LSB_REG);
170
171 return val & s->maxdata;
172}
173
174static int pcl711_ai_cancel(struct comedi_device *dev,
175 struct comedi_subdevice *s)
176{
177 outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
178 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
179 return 0;
180}
181
182static irqreturn_t pcl711_interrupt(int irq, void *d)
183{
184 struct comedi_device *dev = d;
185 struct comedi_subdevice *s = dev->read_subdev;
186 struct comedi_cmd *cmd = &s->async->cmd;
187 unsigned short data;
188
189 if (!dev->attached) {
190 dev_err(dev->class_dev, "spurious interrupt\n");
191 return IRQ_HANDLED;
192 }
193
194 data = pcl711_ai_get_sample(dev, s);
195
196 outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
197
198 comedi_buf_write_samples(s, &data, 1);
199
200 if (cmd->stop_src == TRIG_COUNT &&
201 s->async->scans_done >= cmd->stop_arg)
202 s->async->events |= COMEDI_CB_EOA;
203
204 comedi_handle_events(dev, s);
205
206 return IRQ_HANDLED;
207}
208
209static void pcl711_set_changain(struct comedi_device *dev,
210 struct comedi_subdevice *s,
211 unsigned int chanspec)
212{
213 unsigned int chan = CR_CHAN(chanspec);
214 unsigned int range = CR_RANGE(chanspec);
215 unsigned int aref = CR_AREF(chanspec);
216 unsigned int mux = 0;
217
218 outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
219
220 if (s->n_chan > 8) {
221
222 if (aref == AREF_DIFF) {
223 chan &= 0x7;
224 mux |= PCL711_MUX_DIFF;
225 } else {
226 if (chan < 8)
227 mux |= PCL711_MUX_CS0;
228 else
229 mux |= PCL711_MUX_CS1;
230 }
231 }
232 outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
233}
234
235static int pcl711_ai_eoc(struct comedi_device *dev,
236 struct comedi_subdevice *s,
237 struct comedi_insn *insn,
238 unsigned long context)
239{
240 unsigned int status;
241
242 status = inb(dev->iobase + PCL711_AI_MSB_REG);
243 if ((status & PCL711_AI_MSB_DRDY) == 0)
244 return 0;
245 return -EBUSY;
246}
247
248static int pcl711_ai_insn_read(struct comedi_device *dev,
249 struct comedi_subdevice *s,
250 struct comedi_insn *insn,
251 unsigned int *data)
252{
253 int ret;
254 int i;
255
256 pcl711_set_changain(dev, s, insn->chanspec);
257
258 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
259
260 for (i = 0; i < insn->n; i++) {
261 outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
262
263 ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0);
264 if (ret)
265 return ret;
266
267 data[i] = pcl711_ai_get_sample(dev, s);
268 }
269
270 return insn->n;
271}
272
273static int pcl711_ai_cmdtest(struct comedi_device *dev,
274 struct comedi_subdevice *s, struct comedi_cmd *cmd)
275{
276 int err = 0;
277
278
279
280 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
281 err |= comedi_check_trigger_src(&cmd->scan_begin_src,
282 TRIG_TIMER | TRIG_EXT);
283 err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
284 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
285 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
286
287 if (err)
288 return 1;
289
290
291
292 err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
293 err |= comedi_check_trigger_is_unique(cmd->stop_src);
294
295
296
297 if (err)
298 return 2;
299
300
301
302 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
303
304 if (cmd->scan_begin_src == TRIG_EXT) {
305 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
306 } else {
307#define MAX_SPEED 1000
308 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
309 MAX_SPEED);
310 }
311
312 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
313 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
314 cmd->chanlist_len);
315
316 if (cmd->stop_src == TRIG_COUNT)
317 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
318 else
319 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
320
321 if (err)
322 return 3;
323
324
325
326 if (cmd->scan_begin_src == TRIG_TIMER) {
327 unsigned int arg = cmd->scan_begin_arg;
328
329 comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
330 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
331 }
332
333 if (err)
334 return 4;
335
336 return 0;
337}
338
339static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
340{
341 struct comedi_cmd *cmd = &s->async->cmd;
342
343 pcl711_set_changain(dev, s, cmd->chanlist[0]);
344
345 if (cmd->scan_begin_src == TRIG_TIMER) {
346 comedi_8254_update_divisors(dev->pacer);
347 comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
348 outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
349 pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
350 } else {
351 pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
352 }
353
354 return 0;
355}
356
357static void pcl711_ao_write(struct comedi_device *dev,
358 unsigned int chan, unsigned int val)
359{
360 outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
361 outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
362}
363
364static int pcl711_ao_insn_write(struct comedi_device *dev,
365 struct comedi_subdevice *s,
366 struct comedi_insn *insn,
367 unsigned int *data)
368{
369 unsigned int chan = CR_CHAN(insn->chanspec);
370 unsigned int val = s->readback[chan];
371 int i;
372
373 for (i = 0; i < insn->n; i++) {
374 val = data[i];
375 pcl711_ao_write(dev, chan, val);
376 }
377 s->readback[chan] = val;
378
379 return insn->n;
380}
381
382static int pcl711_di_insn_bits(struct comedi_device *dev,
383 struct comedi_subdevice *s,
384 struct comedi_insn *insn,
385 unsigned int *data)
386{
387 unsigned int val;
388
389 val = inb(dev->iobase + PCL711_DI_LSB_REG);
390 val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
391
392 data[1] = val;
393
394 return insn->n;
395}
396
397static int pcl711_do_insn_bits(struct comedi_device *dev,
398 struct comedi_subdevice *s,
399 struct comedi_insn *insn,
400 unsigned int *data)
401{
402 unsigned int mask;
403
404 mask = comedi_dio_update_state(s, data);
405 if (mask) {
406 if (mask & 0x00ff)
407 outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
408 if (mask & 0xff00)
409 outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
410 }
411
412 data[1] = s->state;
413
414 return insn->n;
415}
416
417static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
418{
419 const struct pcl711_board *board = dev->board_ptr;
420 struct comedi_subdevice *s;
421 int ret;
422
423 ret = comedi_request_region(dev, it->options[0], 0x10);
424 if (ret)
425 return ret;
426
427 if (it->options[1] && it->options[1] <= board->maxirq) {
428 ret = request_irq(it->options[1], pcl711_interrupt, 0,
429 dev->board_name, dev);
430 if (ret == 0)
431 dev->irq = it->options[1];
432 }
433
434 dev->pacer = comedi_8254_init(dev->iobase + PCL711_TIMER_BASE,
435 I8254_OSC_BASE_2MHZ, I8254_IO8, 0);
436 if (!dev->pacer)
437 return -ENOMEM;
438
439 ret = comedi_alloc_subdevices(dev, 4);
440 if (ret)
441 return ret;
442
443
444 s = &dev->subdevices[0];
445 s->type = COMEDI_SUBD_AI;
446 s->subdev_flags = SDF_READABLE | SDF_GROUND;
447 if (board->n_aichan > 8)
448 s->subdev_flags |= SDF_DIFF;
449 s->n_chan = board->n_aichan;
450 s->maxdata = 0xfff;
451 s->range_table = board->ai_range_type;
452 s->insn_read = pcl711_ai_insn_read;
453 if (dev->irq) {
454 dev->read_subdev = s;
455 s->subdev_flags |= SDF_CMD_READ;
456 s->len_chanlist = 1;
457 s->do_cmdtest = pcl711_ai_cmdtest;
458 s->do_cmd = pcl711_ai_cmd;
459 s->cancel = pcl711_ai_cancel;
460 }
461
462
463 s = &dev->subdevices[1];
464 s->type = COMEDI_SUBD_AO;
465 s->subdev_flags = SDF_WRITABLE;
466 s->n_chan = board->n_aochan;
467 s->maxdata = 0xfff;
468 s->range_table = &range_bipolar5;
469 s->insn_write = pcl711_ao_insn_write;
470
471 ret = comedi_alloc_subdev_readback(s);
472 if (ret)
473 return ret;
474
475
476 s = &dev->subdevices[2];
477 s->type = COMEDI_SUBD_DI;
478 s->subdev_flags = SDF_READABLE;
479 s->n_chan = 16;
480 s->maxdata = 1;
481 s->range_table = &range_digital;
482 s->insn_bits = pcl711_di_insn_bits;
483
484
485 s = &dev->subdevices[3];
486 s->type = COMEDI_SUBD_DO;
487 s->subdev_flags = SDF_WRITABLE;
488 s->n_chan = 16;
489 s->maxdata = 1;
490 s->range_table = &range_digital;
491 s->insn_bits = pcl711_do_insn_bits;
492
493
494 pcl711_ao_write(dev, 0, 0x0);
495 pcl711_ao_write(dev, 1, 0x0);
496
497 return 0;
498}
499
500static struct comedi_driver pcl711_driver = {
501 .driver_name = "pcl711",
502 .module = THIS_MODULE,
503 .attach = pcl711_attach,
504 .detach = comedi_legacy_detach,
505 .board_name = &boardtypes[0].name,
506 .num_names = ARRAY_SIZE(boardtypes),
507 .offset = sizeof(struct pcl711_board),
508};
509module_comedi_driver(pcl711_driver);
510
511MODULE_AUTHOR("Comedi https://www.comedi.org");
512MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
513MODULE_LICENSE("GPL");
514