Commit | Line | Data |
---|---|---|
e184e2be | 1 | // SPDX-License-Identifier: GPL-2.0 |
dd2996b3 | 2 | /* |
fdb3be16 HS |
3 | * pcl816.c |
4 | * Comedi driver for Advantech PCL-816 cards | |
5 | * | |
6 | * Author: Juan Grigera <juan@grigera.com.ar> | |
7 | * based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812 | |
8 | */ | |
dd2996b3 | 9 | |
dd2996b3 | 10 | /* |
fdb3be16 HS |
11 | * Driver: pcl816 |
12 | * Description: Advantech PCL-816 cards, PCL-814 | |
13 | * Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b) | |
14 | * Author: Juan Grigera <juan@grigera.com.ar> | |
15 | * Status: works | |
16 | * Updated: Tue, 2 Apr 2002 23:15:21 -0800 | |
17 | * | |
18 | * PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO. | |
19 | * Differences are at resolution (16 vs 12 bits). | |
20 | * | |
21 | * The driver support AI command mode, other subdevices not written. | |
22 | * | |
23 | * Analog output and digital input and output are not supported. | |
24 | * | |
25 | * Configuration Options: | |
26 | * [0] - IO Base | |
27 | * [1] - IRQ (0=disable, 2, 3, 4, 5, 6, 7) | |
28 | * [2] - DMA (0=disable, 1, 3) | |
29 | * [3] - 0, 10=10MHz clock for 8254 | |
30 | * 1= 1MHz clock for 8254 | |
31 | */ | |
dd2996b3 | 32 | |
ce157f80 | 33 | #include <linux/module.h> |
5a0e3ad6 | 34 | #include <linux/gfp.h> |
dd2996b3 | 35 | #include <linux/delay.h> |
845d131e | 36 | #include <linux/io.h> |
a4b47eea | 37 | #include <linux/interrupt.h> |
df0e68c1 | 38 | #include <linux/comedi/comedidev.h> |
44fb7aff | 39 | #include <linux/comedi/comedi_8254.h> |
fe7a4f5b | 40 | #include <linux/comedi/comedi_isadma.h> |
dd2996b3 | 41 | |
e8e7709f HS |
42 | /* |
43 | * Register I/O map | |
44 | */ | |
c1cd5623 HS |
45 | #define PCL816_DO_DI_LSB_REG 0x00 |
46 | #define PCL816_DO_DI_MSB_REG 0x01 | |
15e222c1 | 47 | #define PCL816_TIMER_BASE 0x04 |
c99e0e19 HS |
48 | #define PCL816_AI_LSB_REG 0x08 |
49 | #define PCL816_AI_MSB_REG 0x09 | |
19720c07 | 50 | #define PCL816_RANGE_REG 0x09 |
e8e7709f | 51 | #define PCL816_CLRINT_REG 0x0a |
19720c07 HS |
52 | #define PCL816_MUX_REG 0x0b |
53 | #define PCL816_MUX_SCAN(_first, _last) (((_last) << 4) | (_first)) | |
6cf215d7 | 54 | #define PCL816_CTRL_REG 0x0c |
2d8ea891 HS |
55 | #define PCL816_CTRL_SOFT_TRIG BIT(0) |
56 | #define PCL816_CTRL_PACER_TRIG BIT(1) | |
57 | #define PCL816_CTRL_EXT_TRIG BIT(2) | |
58 | #define PCL816_CTRL_POE BIT(3) | |
59 | #define PCL816_CTRL_DMAEN BIT(4) | |
60 | #define PCL816_CTRL_INTEN BIT(5) | |
61 | #define PCL816_CTRL_DMASRC_SLOT(x) (((x) & 0x3) << 6) | |
f72196ec HS |
62 | #define PCL816_STATUS_REG 0x0d |
63 | #define PCL816_STATUS_NEXT_CHAN_MASK (0xf << 0) | |
2d8ea891 HS |
64 | #define PCL816_STATUS_INTSRC_SLOT(x) (((x) & 0x3) << 4) |
65 | #define PCL816_STATUS_INTSRC_DMA PCL816_STATUS_INTSRC_SLOT(3) | |
66 | #define PCL816_STATUS_INTSRC_MASK PCL816_STATUS_INTSRC_SLOT(3) | |
67 | #define PCL816_STATUS_INTACT BIT(6) | |
68 | #define PCL816_STATUS_DRDY BIT(7) | |
dd2996b3 | 69 | |
dd2996b3 JG |
70 | #define MAGIC_DMA_WORD 0x5a5a |
71 | ||
f5a9097e HS |
72 | static const struct comedi_lrange range_pcl816 = { |
73 | 8, { | |
74 | BIP_RANGE(10), | |
75 | BIP_RANGE(5), | |
76 | BIP_RANGE(2.5), | |
77 | BIP_RANGE(1.25), | |
78 | UNI_RANGE(10), | |
79 | UNI_RANGE(5), | |
80 | UNI_RANGE(2.5), | |
81 | UNI_RANGE(1.25) | |
82 | } | |
dd2996b3 | 83 | }; |
0a85b6f0 | 84 | |
1c7f40d9 | 85 | struct pcl816_board { |
a094dbdd | 86 | const char *name; |
a094dbdd | 87 | int ai_maxdata; |
a094dbdd | 88 | int ai_chanlist; |
a094dbdd | 89 | }; |
1c7f40d9 | 90 | |
a094dbdd HS |
91 | static const struct pcl816_board boardtypes[] = { |
92 | { | |
93 | .name = "pcl816", | |
a094dbdd | 94 | .ai_maxdata = 0xffff, |
a094dbdd | 95 | .ai_chanlist = 1024, |
a094dbdd HS |
96 | }, { |
97 | .name = "pcl814b", | |
a094dbdd | 98 | .ai_maxdata = 0x3fff, |
a094dbdd | 99 | .ai_chanlist = 1024, |
a094dbdd | 100 | }, |
1c7f40d9 BP |
101 | }; |
102 | ||
fe0ff175 | 103 | struct pcl816_private { |
bd7ea421 | 104 | struct comedi_isadma *dma; |
58c0576e | 105 | unsigned int ai_poll_ptr; /* how many sampes transfer poll */ |
94349cb9 | 106 | unsigned int ai_cmd_running:1; |
edf0f32b | 107 | unsigned int ai_cmd_canceled:1; |
fe0ff175 BP |
108 | }; |
109 | ||
82ac3e7a | 110 | static void pcl816_ai_setup_dma(struct comedi_device *dev, |
93e60452 HS |
111 | struct comedi_subdevice *s, |
112 | unsigned int unread_samples) | |
82ac3e7a HS |
113 | { |
114 | struct pcl816_private *devpriv = dev->private; | |
bd7ea421 | 115 | struct comedi_isadma *dma = devpriv->dma; |
93e60452 HS |
116 | struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; |
117 | unsigned int max_samples = comedi_bytes_to_samples(s, desc->maxsize); | |
fe4a22a0 | 118 | unsigned int nsamples; |
bd7ea421 HS |
119 | |
120 | comedi_isadma_disable(dma->chan); | |
d77bf973 | 121 | |
fe4a22a0 HS |
122 | /* |
123 | * Determine dma size based on the buffer maxsize plus the number of | |
124 | * unread samples and the number of samples remaining in the command. | |
125 | */ | |
93e60452 | 126 | nsamples = comedi_nsamples_left(s, max_samples + unread_samples); |
fe4a22a0 HS |
127 | if (nsamples > unread_samples) { |
128 | nsamples -= unread_samples; | |
129 | desc->size = comedi_samples_to_bytes(s, nsamples); | |
bd7ea421 | 130 | comedi_isadma_program(desc); |
82ac3e7a | 131 | } |
82ac3e7a HS |
132 | } |
133 | ||
19720c07 HS |
134 | static void pcl816_ai_set_chan_range(struct comedi_device *dev, |
135 | unsigned int chan, | |
136 | unsigned int range) | |
137 | { | |
138 | outb(chan, dev->iobase + PCL816_MUX_REG); | |
139 | outb(range, dev->iobase + PCL816_RANGE_REG); | |
140 | } | |
141 | ||
142 | static void pcl816_ai_set_chan_scan(struct comedi_device *dev, | |
143 | unsigned int first_chan, | |
144 | unsigned int last_chan) | |
145 | { | |
146 | outb(PCL816_MUX_SCAN(first_chan, last_chan), | |
147 | dev->iobase + PCL816_MUX_REG); | |
148 | } | |
149 | ||
150 | static void pcl816_ai_setup_chanlist(struct comedi_device *dev, | |
151 | unsigned int *chanlist, | |
152 | unsigned int seglen) | |
153 | { | |
154 | unsigned int first_chan = CR_CHAN(chanlist[0]); | |
155 | unsigned int last_chan; | |
156 | unsigned int range; | |
157 | unsigned int i; | |
158 | ||
159 | /* store range list to card */ | |
160 | for (i = 0; i < seglen; i++) { | |
161 | last_chan = CR_CHAN(chanlist[i]); | |
162 | range = CR_RANGE(chanlist[i]); | |
163 | ||
164 | pcl816_ai_set_chan_range(dev, last_chan, range); | |
165 | } | |
166 | ||
167 | udelay(1); | |
168 | ||
169 | pcl816_ai_set_chan_scan(dev, first_chan, last_chan); | |
170 | } | |
171 | ||
97880eaf HS |
172 | static void pcl816_ai_clear_eoc(struct comedi_device *dev) |
173 | { | |
174 | /* writing any value clears the interrupt request */ | |
e8e7709f | 175 | outb(0, dev->iobase + PCL816_CLRINT_REG); |
97880eaf HS |
176 | } |
177 | ||
939d33d6 HS |
178 | static void pcl816_ai_soft_trig(struct comedi_device *dev) |
179 | { | |
180 | /* writing any value triggers a software conversion */ | |
c99e0e19 | 181 | outb(0, dev->iobase + PCL816_AI_LSB_REG); |
939d33d6 HS |
182 | } |
183 | ||
66581ca5 HS |
184 | static unsigned int pcl816_ai_get_sample(struct comedi_device *dev, |
185 | struct comedi_subdevice *s) | |
186 | { | |
187 | unsigned int val; | |
188 | ||
c99e0e19 HS |
189 | val = inb(dev->iobase + PCL816_AI_MSB_REG) << 8; |
190 | val |= inb(dev->iobase + PCL816_AI_LSB_REG); | |
66581ca5 HS |
191 | |
192 | return val & s->maxdata; | |
193 | } | |
194 | ||
c0c3531b HS |
195 | static int pcl816_ai_eoc(struct comedi_device *dev, |
196 | struct comedi_subdevice *s, | |
197 | struct comedi_insn *insn, | |
198 | unsigned long context) | |
199 | { | |
200 | unsigned int status; | |
201 | ||
f72196ec HS |
202 | status = inb(dev->iobase + PCL816_STATUS_REG); |
203 | if ((status & PCL816_STATUS_DRDY) == 0) | |
c0c3531b HS |
204 | return 0; |
205 | return -EBUSY; | |
206 | } | |
207 | ||
0f8837bc HS |
208 | static bool pcl816_ai_next_chan(struct comedi_device *dev, |
209 | struct comedi_subdevice *s) | |
210 | { | |
0f8837bc HS |
211 | struct comedi_cmd *cmd = &s->async->cmd; |
212 | ||
0f8837bc | 213 | if (cmd->stop_src == TRIG_COUNT && |
64886498 | 214 | s->async->scans_done >= cmd->stop_arg) { |
0f8837bc | 215 | s->async->events |= COMEDI_CB_EOA; |
0f8837bc HS |
216 | return false; |
217 | } | |
218 | ||
219 | return true; | |
220 | } | |
221 | ||
0a85b6f0 | 222 | static void transfer_from_dma_buf(struct comedi_device *dev, |
54d2dd84 IA |
223 | struct comedi_subdevice *s, |
224 | unsigned short *ptr, | |
0a85b6f0 | 225 | unsigned int bufptr, unsigned int len) |
dd2996b3 | 226 | { |
c0491d4e | 227 | unsigned short val; |
dd2996b3 JG |
228 | int i; |
229 | ||
dd2996b3 | 230 | for (i = 0; i < len; i++) { |
c0491d4e HS |
231 | val = ptr[bufptr++]; |
232 | comedi_buf_write_samples(s, &val, 1); | |
dd2996b3 | 233 | |
0f8837bc HS |
234 | if (!pcl816_ai_next_chan(dev, s)) |
235 | return; | |
dd2996b3 | 236 | } |
dd2996b3 JG |
237 | } |
238 | ||
fb98ec17 | 239 | static irqreturn_t pcl816_interrupt(int irq, void *d) |
dd2996b3 | 240 | { |
71b5f4f1 | 241 | struct comedi_device *dev = d; |
5451274a | 242 | struct comedi_subdevice *s = dev->read_subdev; |
9a1a6cf8 | 243 | struct pcl816_private *devpriv = dev->private; |
bd7ea421 HS |
244 | struct comedi_isadma *dma = devpriv->dma; |
245 | struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; | |
ceb41be7 | 246 | unsigned int nsamples; |
fb98ec17 | 247 | unsigned int bufptr; |
9a1a6cf8 | 248 | |
b619f5aa | 249 | if (!dev->attached || !devpriv->ai_cmd_running) { |
97880eaf | 250 | pcl816_ai_clear_eoc(dev); |
edf0f32b HS |
251 | return IRQ_HANDLED; |
252 | } | |
253 | ||
254 | if (devpriv->ai_cmd_canceled) { | |
255 | devpriv->ai_cmd_canceled = 0; | |
97880eaf | 256 | pcl816_ai_clear_eoc(dev); |
dd2996b3 JG |
257 | return IRQ_HANDLED; |
258 | } | |
259 | ||
bd7ea421 HS |
260 | nsamples = comedi_bytes_to_samples(s, desc->size) - |
261 | devpriv->ai_poll_ptr; | |
fb98ec17 HS |
262 | bufptr = devpriv->ai_poll_ptr; |
263 | devpriv->ai_poll_ptr = 0; | |
264 | ||
93e60452 HS |
265 | /* restart dma with the next buffer */ |
266 | dma->cur_dma = 1 - dma->cur_dma; | |
267 | pcl816_ai_setup_dma(dev, s, nsamples); | |
fe4a22a0 | 268 | |
bd7ea421 | 269 | transfer_from_dma_buf(dev, s, desc->virt_addr, bufptr, nsamples); |
fb98ec17 | 270 | |
97880eaf | 271 | pcl816_ai_clear_eoc(dev); |
fb98ec17 | 272 | |
a2ca8c86 | 273 | comedi_handle_events(dev, s); |
fb98ec17 | 274 | return IRQ_HANDLED; |
dd2996b3 JG |
275 | } |
276 | ||
bb3bd743 HS |
277 | static int check_channel_list(struct comedi_device *dev, |
278 | struct comedi_subdevice *s, | |
279 | unsigned int *chanlist, | |
280 | unsigned int chanlen) | |
281 | { | |
282 | unsigned int chansegment[16]; | |
c8332236 | 283 | unsigned int i, nowmustbechan, seglen; |
bb3bd743 HS |
284 | |
285 | /* correct channel and range number check itself comedi/range.c */ | |
286 | if (chanlen < 1) { | |
287 | dev_err(dev->class_dev, "range/channel list is empty!\n"); | |
288 | return 0; | |
289 | } | |
290 | ||
291 | if (chanlen > 1) { | |
292 | /* first channel is every time ok */ | |
293 | chansegment[0] = chanlist[0]; | |
294 | for (i = 1, seglen = 1; i < chanlen; i++, seglen++) { | |
295 | /* we detect loop, this must by finish */ | |
d258bf05 | 296 | if (chanlist[0] == chanlist[i]) |
bb3bd743 HS |
297 | break; |
298 | nowmustbechan = | |
299 | (CR_CHAN(chansegment[i - 1]) + 1) % chanlen; | |
300 | if (nowmustbechan != CR_CHAN(chanlist[i])) { | |
301 | /* channel list isn't continuous :-( */ | |
302 | dev_dbg(dev->class_dev, | |
303 | "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n", | |
304 | i, CR_CHAN(chanlist[i]), nowmustbechan, | |
305 | CR_CHAN(chanlist[0])); | |
306 | return 0; | |
307 | } | |
308 | /* well, this is next correct channel in list */ | |
309 | chansegment[i] = chanlist[i]; | |
310 | } | |
311 | ||
312 | /* check whole chanlist */ | |
c8332236 | 313 | for (i = 0; i < chanlen; i++) { |
8b4e7da5 | 314 | if (chanlist[i] != chansegment[i % seglen]) { |
bb3bd743 HS |
315 | dev_dbg(dev->class_dev, |
316 | "bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n", | |
317 | i, CR_CHAN(chansegment[i]), | |
318 | CR_RANGE(chansegment[i]), | |
319 | CR_AREF(chansegment[i]), | |
320 | CR_CHAN(chanlist[i % seglen]), | |
321 | CR_RANGE(chanlist[i % seglen]), | |
322 | CR_AREF(chansegment[i % seglen])); | |
323 | return 0; /* chan/gain list is strange */ | |
324 | } | |
325 | } | |
326 | } else { | |
327 | seglen = 1; | |
328 | } | |
329 | ||
330 | return seglen; /* we can serve this with MUX logic */ | |
331 | } | |
332 | ||
0a85b6f0 MT |
333 | static int pcl816_ai_cmdtest(struct comedi_device *dev, |
334 | struct comedi_subdevice *s, struct comedi_cmd *cmd) | |
dd2996b3 JG |
335 | { |
336 | int err = 0; | |
dd2996b3 | 337 | |
27020ffe | 338 | /* Step 1 : check if triggers are trivially valid */ |
dd2996b3 | 339 | |
5cf36bd7 IA |
340 | err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); |
341 | err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW); | |
342 | err |= comedi_check_trigger_src(&cmd->convert_src, | |
343 | TRIG_EXT | TRIG_TIMER); | |
344 | err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
345 | err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
dd2996b3 | 346 | |
4c68fb42 | 347 | if (err) |
dd2996b3 | 348 | return 1; |
dd2996b3 | 349 | |
27020ffe | 350 | /* Step 2a : make sure trigger sources are unique */ |
4c68fb42 | 351 | |
5cf36bd7 IA |
352 | err |= comedi_check_trigger_is_unique(cmd->convert_src); |
353 | err |= comedi_check_trigger_is_unique(cmd->stop_src); | |
dd2996b3 | 354 | |
27020ffe | 355 | /* Step 2b : and mutually compatible */ |
dd2996b3 | 356 | |
4c68fb42 | 357 | if (err) |
dd2996b3 | 358 | return 2; |
4c68fb42 | 359 | |
b93e56ad | 360 | /* Step 3: check if arguments are trivially valid */ |
dd2996b3 | 361 | |
5cf36bd7 IA |
362 | err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); |
363 | err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
dd2996b3 | 364 | |
b93e56ad | 365 | if (cmd->convert_src == TRIG_TIMER) |
5cf36bd7 | 366 | err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000); |
b93e56ad | 367 | else /* TRIG_EXT */ |
5cf36bd7 | 368 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); |
b93e56ad | 369 | |
5cf36bd7 IA |
370 | err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, |
371 | cmd->chanlist_len); | |
b93e56ad HS |
372 | |
373 | if (cmd->stop_src == TRIG_COUNT) | |
5cf36bd7 | 374 | err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1); |
b93e56ad | 375 | else /* TRIG_NONE */ |
5cf36bd7 | 376 | err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); |
dd2996b3 | 377 | |
4c68fb42 | 378 | if (err) |
dd2996b3 | 379 | return 3; |
4c68fb42 | 380 | |
dd2996b3 JG |
381 | /* step 4: fix up any arguments */ |
382 | if (cmd->convert_src == TRIG_TIMER) { | |
f48c21fc HS |
383 | unsigned int arg = cmd->convert_arg; |
384 | ||
385 | comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags); | |
5cf36bd7 | 386 | err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg); |
dd2996b3 JG |
387 | } |
388 | ||
4c68fb42 | 389 | if (err) |
dd2996b3 | 390 | return 4; |
4c68fb42 | 391 | |
64a1f7bd IA |
392 | /* step 5: complain about special chanlist considerations */ |
393 | ||
394 | if (cmd->chanlist) { | |
395 | if (!check_channel_list(dev, s, cmd->chanlist, | |
396 | cmd->chanlist_len)) | |
397 | return 5; /* incorrect channels list */ | |
398 | } | |
399 | ||
dd2996b3 JG |
400 | return 0; |
401 | } | |
402 | ||
da91b269 | 403 | static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
dd2996b3 | 404 | { |
9a1a6cf8 | 405 | struct pcl816_private *devpriv = dev->private; |
bd7ea421 | 406 | struct comedi_isadma *dma = devpriv->dma; |
ea6d0d4c | 407 | struct comedi_cmd *cmd = &s->async->cmd; |
6cf215d7 | 408 | unsigned int ctrl; |
64a1f7bd | 409 | unsigned int seglen; |
dd2996b3 | 410 | |
94349cb9 | 411 | if (devpriv->ai_cmd_running) |
dd2996b3 JG |
412 | return -EBUSY; |
413 | ||
64a1f7bd IA |
414 | seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len); |
415 | if (seglen < 1) | |
dd2996b3 | 416 | return -EINVAL; |
19720c07 | 417 | pcl816_ai_setup_chanlist(dev, cmd->chanlist, seglen); |
5f74ea14 | 418 | udelay(1); |
dd2996b3 | 419 | |
94349cb9 | 420 | devpriv->ai_cmd_running = 1; |
dd2996b3 | 421 | devpriv->ai_poll_ptr = 0; |
edf0f32b | 422 | devpriv->ai_cmd_canceled = 0; |
dd2996b3 | 423 | |
93e60452 HS |
424 | /* setup and enable dma for the first buffer */ |
425 | dma->cur_dma = 0; | |
426 | pcl816_ai_setup_dma(dev, s, 0); | |
dd2996b3 | 427 | |
f48c21fc HS |
428 | comedi_8254_set_mode(dev->pacer, 0, I8254_MODE1 | I8254_BINARY); |
429 | comedi_8254_write(dev->pacer, 0, 0x0ff); | |
430 | udelay(1); | |
431 | comedi_8254_update_divisors(dev->pacer); | |
432 | comedi_8254_pacer_enable(dev->pacer, 1, 2, true); | |
4c68fb42 | 433 | |
2d8ea891 HS |
434 | ctrl = PCL816_CTRL_INTEN | PCL816_CTRL_DMAEN | |
435 | PCL816_CTRL_DMASRC_SLOT(0); | |
6cf215d7 HS |
436 | if (cmd->convert_src == TRIG_TIMER) |
437 | ctrl |= PCL816_CTRL_PACER_TRIG; | |
438 | else /* TRIG_EXT */ | |
439 | ctrl |= PCL816_CTRL_EXT_TRIG; | |
4c68fb42 | 440 | |
6cf215d7 | 441 | outb(ctrl, dev->iobase + PCL816_CTRL_REG); |
bd7ea421 HS |
442 | outb((dma->chan << 4) | dev->irq, |
443 | dev->iobase + PCL816_STATUS_REG); | |
dd2996b3 | 444 | |
dd2996b3 JG |
445 | return 0; |
446 | } | |
447 | ||
da91b269 | 448 | static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s) |
dd2996b3 | 449 | { |
9a1a6cf8 | 450 | struct pcl816_private *devpriv = dev->private; |
bd7ea421 HS |
451 | struct comedi_isadma *dma = devpriv->dma; |
452 | struct comedi_isadma_desc *desc; | |
dd2996b3 | 453 | unsigned long flags; |
bd7ea421 HS |
454 | unsigned int poll; |
455 | int ret; | |
dd2996b3 | 456 | |
5f74ea14 | 457 | spin_lock_irqsave(&dev->spinlock, flags); |
dd2996b3 | 458 | |
bd7ea421 HS |
459 | poll = comedi_isadma_poll(dma); |
460 | poll = comedi_bytes_to_samples(s, poll); | |
461 | if (poll > devpriv->ai_poll_ptr) { | |
462 | desc = &dma->desc[dma->cur_dma]; | |
463 | transfer_from_dma_buf(dev, s, desc->virt_addr, | |
464 | devpriv->ai_poll_ptr, | |
465 | poll - devpriv->ai_poll_ptr); | |
466 | /* new buffer position */ | |
467 | devpriv->ai_poll_ptr = poll; | |
dd2996b3 | 468 | |
bd7ea421 | 469 | comedi_handle_events(dev, s); |
dd2996b3 | 470 | |
bd7ea421 HS |
471 | ret = comedi_buf_n_bytes_ready(s); |
472 | } else { | |
473 | /* no new samples */ | |
474 | ret = 0; | |
475 | } | |
5f74ea14 | 476 | spin_unlock_irqrestore(&dev->spinlock, flags); |
dd2996b3 | 477 | |
bd7ea421 | 478 | return ret; |
dd2996b3 JG |
479 | } |
480 | ||
0a85b6f0 MT |
481 | static int pcl816_ai_cancel(struct comedi_device *dev, |
482 | struct comedi_subdevice *s) | |
dd2996b3 | 483 | { |
9a1a6cf8 HS |
484 | struct pcl816_private *devpriv = dev->private; |
485 | ||
61b3980f HS |
486 | if (!devpriv->ai_cmd_running) |
487 | return 0; | |
4c68fb42 | 488 | |
2d8ea891 | 489 | outb(0, dev->iobase + PCL816_CTRL_REG); |
6cf215d7 | 490 | pcl816_ai_clear_eoc(dev); |
61b3980f | 491 | |
f48c21fc | 492 | comedi_8254_pacer_enable(dev->pacer, 1, 2, false); |
b619f5aa | 493 | |
b619f5aa HS |
494 | devpriv->ai_cmd_running = 0; |
495 | devpriv->ai_cmd_canceled = 1; | |
61b3980f | 496 | |
76b9e33e | 497 | return 0; |
dd2996b3 JG |
498 | } |
499 | ||
937a3706 HS |
500 | static int pcl816_ai_insn_read(struct comedi_device *dev, |
501 | struct comedi_subdevice *s, | |
502 | struct comedi_insn *insn, | |
503 | unsigned int *data) | |
504 | { | |
505 | unsigned int chan = CR_CHAN(insn->chanspec); | |
506 | unsigned int range = CR_RANGE(insn->chanspec); | |
507 | int ret = 0; | |
508 | int i; | |
509 | ||
6cf215d7 | 510 | outb(PCL816_CTRL_SOFT_TRIG, dev->iobase + PCL816_CTRL_REG); |
937a3706 | 511 | |
19720c07 HS |
512 | pcl816_ai_set_chan_range(dev, chan, range); |
513 | pcl816_ai_set_chan_scan(dev, chan, chan); | |
937a3706 HS |
514 | |
515 | for (i = 0; i < insn->n; i++) { | |
97880eaf | 516 | pcl816_ai_clear_eoc(dev); |
939d33d6 | 517 | pcl816_ai_soft_trig(dev); |
937a3706 HS |
518 | |
519 | ret = comedi_timeout(dev, s, insn, pcl816_ai_eoc, 0); | |
520 | if (ret) | |
521 | break; | |
522 | ||
523 | data[i] = pcl816_ai_get_sample(dev, s); | |
524 | } | |
2d8ea891 | 525 | outb(0, dev->iobase + PCL816_CTRL_REG); |
97880eaf | 526 | pcl816_ai_clear_eoc(dev); |
937a3706 HS |
527 | |
528 | return ret ? ret : insn->n; | |
529 | } | |
530 | ||
c1cd5623 HS |
531 | static int pcl816_di_insn_bits(struct comedi_device *dev, |
532 | struct comedi_subdevice *s, | |
533 | struct comedi_insn *insn, | |
534 | unsigned int *data) | |
535 | { | |
536 | data[1] = inb(dev->iobase + PCL816_DO_DI_LSB_REG) | | |
537 | (inb(dev->iobase + PCL816_DO_DI_MSB_REG) << 8); | |
538 | ||
539 | return insn->n; | |
540 | } | |
541 | ||
542 | static int pcl816_do_insn_bits(struct comedi_device *dev, | |
543 | struct comedi_subdevice *s, | |
544 | struct comedi_insn *insn, | |
545 | unsigned int *data) | |
546 | { | |
547 | if (comedi_dio_update_state(s, data)) { | |
548 | outb(s->state & 0xff, dev->iobase + PCL816_DO_DI_LSB_REG); | |
549 | outb((s->state >> 8), dev->iobase + PCL816_DO_DI_MSB_REG); | |
550 | } | |
551 | ||
552 | data[1] = s->state; | |
553 | ||
554 | return insn->n; | |
555 | } | |
556 | ||
dc917419 HS |
557 | static void pcl816_reset(struct comedi_device *dev) |
558 | { | |
2d8ea891 | 559 | outb(0, dev->iobase + PCL816_CTRL_REG); |
19720c07 | 560 | pcl816_ai_set_chan_range(dev, 0, 0); |
97880eaf | 561 | pcl816_ai_clear_eoc(dev); |
dc917419 | 562 | |
dc917419 HS |
563 | /* set all digital outputs low */ |
564 | outb(0, dev->iobase + PCL816_DO_DI_LSB_REG); | |
565 | outb(0, dev->iobase + PCL816_DO_DI_MSB_REG); | |
566 | } | |
567 | ||
bd7ea421 HS |
568 | static void pcl816_alloc_irq_and_dma(struct comedi_device *dev, |
569 | struct comedi_devconfig *it) | |
1fa51639 HS |
570 | { |
571 | struct pcl816_private *devpriv = dev->private; | |
bd7ea421 HS |
572 | unsigned int irq_num = it->options[1]; |
573 | unsigned int dma_chan = it->options[2]; | |
1fa51639 | 574 | |
bd7ea421 | 575 | /* only IRQs 2-7 and DMA channels 3 and 1 are valid */ |
1fa51639 HS |
576 | if (!(irq_num >= 2 && irq_num <= 7) || |
577 | !(dma_chan == 3 || dma_chan == 1)) | |
bd7ea421 | 578 | return; |
1fa51639 | 579 | |
1fa51639 | 580 | if (request_irq(irq_num, pcl816_interrupt, 0, dev->board_name, dev)) |
bd7ea421 | 581 | return; |
1fa51639 | 582 | |
bd7ea421 HS |
583 | /* DMA uses two 16K buffers */ |
584 | devpriv->dma = comedi_isadma_alloc(dev, 2, dma_chan, dma_chan, | |
585 | PAGE_SIZE * 4, COMEDI_ISADMA_READ); | |
586 | if (!devpriv->dma) | |
587 | free_irq(irq_num, dev); | |
588 | else | |
589 | dev->irq = irq_num; | |
1fa51639 HS |
590 | } |
591 | ||
d7b8bbb6 HS |
592 | static void pcl816_free_dma(struct comedi_device *dev) |
593 | { | |
594 | struct pcl816_private *devpriv = dev->private; | |
d7b8bbb6 | 595 | |
bd7ea421 HS |
596 | if (devpriv) |
597 | comedi_isadma_free(devpriv->dma); | |
d7b8bbb6 HS |
598 | } |
599 | ||
da91b269 | 600 | static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
dd2996b3 | 601 | { |
79c9f68f | 602 | const struct pcl816_board *board = dev->board_ptr; |
9a1a6cf8 | 603 | struct pcl816_private *devpriv; |
34c43922 | 604 | struct comedi_subdevice *s; |
55b95f14 | 605 | int ret; |
dd2996b3 | 606 | |
c36d44ac HS |
607 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
608 | if (!devpriv) | |
609 | return -ENOMEM; | |
610 | ||
2599048a | 611 | ret = comedi_request_region(dev, it->options[0], 0x10); |
22b580f0 HS |
612 | if (ret) |
613 | return ret; | |
dd2996b3 | 614 | |
bd7ea421 HS |
615 | /* an IRQ and DMA are required to support async commands */ |
616 | pcl816_alloc_irq_and_dma(dev, it); | |
dd2996b3 | 617 | |
fade5e5b IA |
618 | dev->pacer = comedi_8254_io_alloc(dev->iobase + PCL816_TIMER_BASE, |
619 | I8254_OSC_BASE_10MHZ, I8254_IO8, 0); | |
620 | if (IS_ERR(dev->pacer)) | |
621 | return PTR_ERR(dev->pacer); | |
f48c21fc | 622 | |
c1cd5623 | 623 | ret = comedi_alloc_subdevices(dev, 4); |
8b6c5694 | 624 | if (ret) |
dd2996b3 JG |
625 | return ret; |
626 | ||
9417de06 | 627 | s = &dev->subdevices[0]; |
1ddd22c0 HS |
628 | s->type = COMEDI_SUBD_AI; |
629 | s->subdev_flags = SDF_CMD_READ | SDF_DIFF; | |
630 | s->n_chan = 16; | |
631 | s->maxdata = board->ai_maxdata; | |
8cfda3f8 | 632 | s->range_table = &range_pcl816; |
1ddd22c0 | 633 | s->insn_read = pcl816_ai_insn_read; |
1fa51639 | 634 | if (dev->irq) { |
1ddd22c0 HS |
635 | dev->read_subdev = s; |
636 | s->subdev_flags |= SDF_CMD_READ; | |
637 | s->len_chanlist = board->ai_chanlist; | |
638 | s->do_cmdtest = pcl816_ai_cmdtest; | |
639 | s->do_cmd = pcl816_ai_cmd; | |
640 | s->poll = pcl816_ai_poll; | |
641 | s->cancel = pcl816_ai_cancel; | |
dd2996b3 JG |
642 | } |
643 | ||
026d7877 HS |
644 | /* Piggyback Slot1 subdevice */ |
645 | s = &dev->subdevices[1]; | |
c1cd5623 | 646 | s->type = COMEDI_SUBD_UNUSED; |
dd2996b3 | 647 | |
c1cd5623 HS |
648 | /* Digital Input subdevice */ |
649 | s = &dev->subdevices[2]; | |
650 | s->type = COMEDI_SUBD_DI; | |
651 | s->subdev_flags = SDF_READABLE; | |
652 | s->n_chan = 16; | |
653 | s->maxdata = 1; | |
654 | s->range_table = &range_digital; | |
655 | s->insn_bits = pcl816_di_insn_bits; | |
656 | ||
657 | /* Digital Output subdevice */ | |
658 | s = &dev->subdevices[3]; | |
659 | s->type = COMEDI_SUBD_DO; | |
660 | s->subdev_flags = SDF_WRITABLE; | |
661 | s->n_chan = 16; | |
662 | s->maxdata = 1; | |
663 | s->range_table = &range_digital; | |
664 | s->insn_bits = pcl816_do_insn_bits; | |
665 | ||
dd2996b3 JG |
666 | pcl816_reset(dev); |
667 | ||
dd2996b3 JG |
668 | return 0; |
669 | } | |
670 | ||
484ecc95 | 671 | static void pcl816_detach(struct comedi_device *dev) |
dd2996b3 | 672 | { |
484ecc95 | 673 | if (dev->private) { |
5d6f1a2e | 674 | pcl816_ai_cancel(dev, dev->read_subdev); |
484ecc95 | 675 | pcl816_reset(dev); |
484ecc95 | 676 | } |
d7b8bbb6 | 677 | pcl816_free_dma(dev); |
a32c6d00 | 678 | comedi_legacy_detach(dev); |
dd2996b3 | 679 | } |
90f703d3 | 680 | |
294f930d | 681 | static struct comedi_driver pcl816_driver = { |
34023cd1 HS |
682 | .driver_name = "pcl816", |
683 | .module = THIS_MODULE, | |
684 | .attach = pcl816_attach, | |
685 | .detach = pcl816_detach, | |
686 | .board_name = &boardtypes[0].name, | |
687 | .num_names = ARRAY_SIZE(boardtypes), | |
688 | .offset = sizeof(struct pcl816_board), | |
689 | }; | |
294f930d | 690 | module_comedi_driver(pcl816_driver); |
34023cd1 | 691 | |
2c183944 | 692 | MODULE_AUTHOR("Comedi https://www.comedi.org"); |
90f703d3 AT |
693 | MODULE_DESCRIPTION("Comedi low-level driver"); |
694 | MODULE_LICENSE("GPL"); |