1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * cb_das16_cs.c |
4 | * Driver for Computer Boards PC-CARD DAS16/16. |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 2000, 2001, 2002 David A. Schleef <ds@schleef.org> |
8 | * |
9 | * PCMCIA support code for this driver is adapted from the dummy_cs.c |
10 | * driver of the Linux PCMCIA Card Services package. |
11 | * |
12 | * The initial developer of the original code is David A. Hinds |
13 | * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds |
14 | * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. |
15 | */ |
16 | |
17 | /* |
18 | * Driver: cb_das16_cs |
19 | * Description: Computer Boards PC-CARD DAS16/16 |
20 | * Devices: [ComputerBoards] PC-CARD DAS16/16 (cb_das16_cs), |
21 | * PC-CARD DAS16/16-AO |
22 | * Author: ds |
23 | * Updated: Mon, 04 Nov 2002 20:04:21 -0800 |
24 | * Status: experimental |
25 | */ |
26 | |
27 | #include <linux/module.h> |
28 | #include <linux/interrupt.h> |
29 | #include <linux/delay.h> |
30 | #include <linux/comedi/comedi_pcmcia.h> |
31 | #include <linux/comedi/comedi_8254.h> |
32 | |
33 | /* |
34 | * Register I/O map |
35 | */ |
36 | #define DAS16CS_AI_DATA_REG 0x00 |
37 | #define DAS16CS_AI_MUX_REG 0x02 |
38 | #define DAS16CS_AI_MUX_HI_CHAN(x) (((x) & 0xf) << 4) |
39 | #define DAS16CS_AI_MUX_LO_CHAN(x) (((x) & 0xf) << 0) |
40 | #define DAS16CS_AI_MUX_SINGLE_CHAN(x) (DAS16CS_AI_MUX_HI_CHAN(x) | \ |
41 | DAS16CS_AI_MUX_LO_CHAN(x)) |
42 | #define DAS16CS_MISC1_REG 0x04 |
43 | #define DAS16CS_MISC1_INTE BIT(15) /* 1=enable; 0=disable */ |
44 | #define DAS16CS_MISC1_INT_SRC(x) (((x) & 0x7) << 12) /* interrupt src */ |
45 | #define DAS16CS_MISC1_INT_SRC_NONE DAS16CS_MISC1_INT_SRC(0) |
46 | #define DAS16CS_MISC1_INT_SRC_PACER DAS16CS_MISC1_INT_SRC(1) |
47 | #define DAS16CS_MISC1_INT_SRC_EXT DAS16CS_MISC1_INT_SRC(2) |
48 | #define DAS16CS_MISC1_INT_SRC_FNE DAS16CS_MISC1_INT_SRC(3) |
49 | #define DAS16CS_MISC1_INT_SRC_FHF DAS16CS_MISC1_INT_SRC(4) |
50 | #define DAS16CS_MISC1_INT_SRC_EOS DAS16CS_MISC1_INT_SRC(5) |
51 | #define DAS16CS_MISC1_INT_SRC_MASK DAS16CS_MISC1_INT_SRC(7) |
52 | #define DAS16CS_MISC1_OVR BIT(10) /* ro - 1=FIFO overflow */ |
53 | #define DAS16CS_MISC1_AI_CONV(x) (((x) & 0x3) << 8) /* AI convert src */ |
54 | #define DAS16CS_MISC1_AI_CONV_SW DAS16CS_MISC1_AI_CONV(0) |
55 | #define DAS16CS_MISC1_AI_CONV_EXT_NEG DAS16CS_MISC1_AI_CONV(1) |
56 | #define DAS16CS_MISC1_AI_CONV_EXT_POS DAS16CS_MISC1_AI_CONV(2) |
57 | #define DAS16CS_MISC1_AI_CONV_PACER DAS16CS_MISC1_AI_CONV(3) |
58 | #define DAS16CS_MISC1_AI_CONV_MASK DAS16CS_MISC1_AI_CONV(3) |
59 | #define DAS16CS_MISC1_EOC BIT(7) /* ro - 0=busy; 1=ready */ |
60 | #define DAS16CS_MISC1_SEDIFF BIT(5) /* 0=diff; 1=se */ |
61 | #define DAS16CS_MISC1_INTB BIT(4) /* ro - 0=latched; 1=cleared */ |
62 | #define DAS16CS_MISC1_MA_MASK (0xf << 0) /* ro - current ai mux */ |
63 | #define DAS16CS_MISC1_DAC1CS BIT(3) /* wo - DAC1 chip select */ |
64 | #define DAS16CS_MISC1_DACCLK BIT(2) /* wo - Serial DAC clock */ |
65 | #define DAS16CS_MISC1_DACSD BIT(1) /* wo - Serial DAC data */ |
66 | #define DAS16CS_MISC1_DAC0CS BIT(0) /* wo - DAC0 chip select */ |
67 | #define DAS16CS_MISC1_DAC_MASK (0x0f << 0) |
68 | #define DAS16CS_MISC2_REG 0x06 |
69 | #define DAS16CS_MISC2_BME BIT(14) /* 1=burst enable; 0=disable */ |
70 | #define DAS16CS_MISC2_AI_GAIN(x) (((x) & 0xf) << 8) /* AI gain */ |
71 | #define DAS16CS_MISC2_AI_GAIN_1 DAS16CS_MISC2_AI_GAIN(4) /* +/-10V */ |
72 | #define DAS16CS_MISC2_AI_GAIN_2 DAS16CS_MISC2_AI_GAIN(0) /* +/-5V */ |
73 | #define DAS16CS_MISC2_AI_GAIN_4 DAS16CS_MISC2_AI_GAIN(1) /* +/-2.5V */ |
74 | #define DAS16CS_MISC2_AI_GAIN_8 DAS16CS_MISC2_AI_GAIN(2) /* +-1.25V */ |
75 | #define DAS16CS_MISC2_AI_GAIN_MASK DAS16CS_MISC2_AI_GAIN(0xf) |
76 | #define DAS16CS_MISC2_UDIR BIT(7) /* 1=dio7:4 output; 0=input */ |
77 | #define DAS16CS_MISC2_LDIR BIT(6) /* 1=dio3:0 output; 0=input */ |
78 | #define DAS16CS_MISC2_TRGPOL BIT(5) /* 1=active lo; 0=hi */ |
79 | #define DAS16CS_MISC2_TRGSEL BIT(4) /* 1=edge; 0=level */ |
80 | #define DAS16CS_MISC2_FFNE BIT(3) /* ro - 1=FIFO not empty */ |
81 | #define DAS16CS_MISC2_TRGCLR BIT(3) /* wo - 1=clr (monstable) */ |
82 | #define DAS16CS_MISC2_CLK2 BIT(2) /* 1=10 MHz; 0=1 MHz */ |
83 | #define DAS16CS_MISC2_CTR1 BIT(1) /* 1=int. 100 kHz; 0=ext. clk */ |
84 | #define DAS16CS_MISC2_TRG0 BIT(0) /* 1=enable; 0=disable */ |
85 | #define DAS16CS_TIMER_BASE 0x08 |
86 | #define DAS16CS_DIO_REG 0x10 |
87 | |
88 | struct das16cs_board { |
89 | const char *name; |
90 | int device_id; |
91 | unsigned int has_ao:1; |
92 | unsigned int has_4dio:1; |
93 | }; |
94 | |
95 | static const struct das16cs_board das16cs_boards[] = { |
96 | { |
97 | .name = "PC-CARD DAS16/16-AO" , |
98 | .device_id = 0x0039, |
99 | .has_ao = 1, |
100 | .has_4dio = 1, |
101 | }, { |
102 | .name = "PCM-DAS16s/16" , |
103 | .device_id = 0x4009, |
104 | }, { |
105 | .name = "PC-CARD DAS16/16" , |
106 | .device_id = 0x0000, /* unknown */ |
107 | }, |
108 | }; |
109 | |
110 | struct das16cs_private { |
111 | unsigned short misc1; |
112 | unsigned short misc2; |
113 | }; |
114 | |
115 | static const struct comedi_lrange das16cs_ai_range = { |
116 | 4, { |
117 | BIP_RANGE(10), |
118 | BIP_RANGE(5), |
119 | BIP_RANGE(2.5), |
120 | BIP_RANGE(1.25), |
121 | } |
122 | }; |
123 | |
124 | static int das16cs_ai_eoc(struct comedi_device *dev, |
125 | struct comedi_subdevice *s, |
126 | struct comedi_insn *insn, |
127 | unsigned long context) |
128 | { |
129 | unsigned int status; |
130 | |
131 | status = inw(port: dev->iobase + DAS16CS_MISC1_REG); |
132 | if (status & DAS16CS_MISC1_EOC) |
133 | return 0; |
134 | return -EBUSY; |
135 | } |
136 | |
137 | static int das16cs_ai_insn_read(struct comedi_device *dev, |
138 | struct comedi_subdevice *s, |
139 | struct comedi_insn *insn, |
140 | unsigned int *data) |
141 | { |
142 | struct das16cs_private *devpriv = dev->private; |
143 | int chan = CR_CHAN(insn->chanspec); |
144 | int range = CR_RANGE(insn->chanspec); |
145 | int aref = CR_AREF(insn->chanspec); |
146 | int ret; |
147 | int i; |
148 | |
149 | outw(DAS16CS_AI_MUX_SINGLE_CHAN(chan), |
150 | port: dev->iobase + DAS16CS_AI_MUX_REG); |
151 | |
152 | /* disable interrupts, software convert */ |
153 | devpriv->misc1 &= ~(DAS16CS_MISC1_INTE | DAS16CS_MISC1_INT_SRC_MASK | |
154 | DAS16CS_MISC1_AI_CONV_MASK); |
155 | if (aref == AREF_DIFF) |
156 | devpriv->misc1 &= ~DAS16CS_MISC1_SEDIFF; |
157 | else |
158 | devpriv->misc1 |= DAS16CS_MISC1_SEDIFF; |
159 | outw(value: devpriv->misc1, port: dev->iobase + DAS16CS_MISC1_REG); |
160 | |
161 | devpriv->misc2 &= ~(DAS16CS_MISC2_BME | DAS16CS_MISC2_AI_GAIN_MASK); |
162 | switch (range) { |
163 | case 0: |
164 | devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_1; |
165 | break; |
166 | case 1: |
167 | devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_2; |
168 | break; |
169 | case 2: |
170 | devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_4; |
171 | break; |
172 | case 3: |
173 | devpriv->misc2 |= DAS16CS_MISC2_AI_GAIN_8; |
174 | break; |
175 | } |
176 | outw(value: devpriv->misc2, port: dev->iobase + DAS16CS_MISC2_REG); |
177 | |
178 | for (i = 0; i < insn->n; i++) { |
179 | outw(value: 0, port: dev->iobase + DAS16CS_AI_DATA_REG); |
180 | |
181 | ret = comedi_timeout(dev, s, insn, cb: das16cs_ai_eoc, context: 0); |
182 | if (ret) |
183 | return ret; |
184 | |
185 | data[i] = inw(port: dev->iobase + DAS16CS_AI_DATA_REG); |
186 | } |
187 | |
188 | return i; |
189 | } |
190 | |
191 | static int das16cs_ao_insn_write(struct comedi_device *dev, |
192 | struct comedi_subdevice *s, |
193 | struct comedi_insn *insn, |
194 | unsigned int *data) |
195 | { |
196 | struct das16cs_private *devpriv = dev->private; |
197 | unsigned int chan = CR_CHAN(insn->chanspec); |
198 | unsigned int val = s->readback[chan]; |
199 | unsigned short misc1; |
200 | int bit; |
201 | int i; |
202 | |
203 | for (i = 0; i < insn->n; i++) { |
204 | val = data[i]; |
205 | |
206 | outw(value: devpriv->misc1, port: dev->iobase + DAS16CS_MISC1_REG); |
207 | udelay(1); |
208 | |
209 | /* raise the DACxCS line for the non-selected channel */ |
210 | misc1 = devpriv->misc1 & ~DAS16CS_MISC1_DAC_MASK; |
211 | if (chan) |
212 | misc1 |= DAS16CS_MISC1_DAC0CS; |
213 | else |
214 | misc1 |= DAS16CS_MISC1_DAC1CS; |
215 | |
216 | outw(value: misc1, port: dev->iobase + DAS16CS_MISC1_REG); |
217 | udelay(1); |
218 | |
219 | for (bit = 15; bit >= 0; bit--) { |
220 | if ((val >> bit) & 0x1) |
221 | misc1 |= DAS16CS_MISC1_DACSD; |
222 | else |
223 | misc1 &= ~DAS16CS_MISC1_DACSD; |
224 | outw(value: misc1, port: dev->iobase + DAS16CS_MISC1_REG); |
225 | udelay(1); |
226 | outw(value: misc1 | DAS16CS_MISC1_DACCLK, |
227 | port: dev->iobase + DAS16CS_MISC1_REG); |
228 | udelay(1); |
229 | } |
230 | /* |
231 | * Make both DAC0CS and DAC1CS high to load |
232 | * the new data and update analog the output |
233 | */ |
234 | outw(value: misc1 | DAS16CS_MISC1_DAC0CS | DAS16CS_MISC1_DAC1CS, |
235 | port: dev->iobase + DAS16CS_MISC1_REG); |
236 | } |
237 | s->readback[chan] = val; |
238 | |
239 | return insn->n; |
240 | } |
241 | |
242 | static int das16cs_dio_insn_bits(struct comedi_device *dev, |
243 | struct comedi_subdevice *s, |
244 | struct comedi_insn *insn, |
245 | unsigned int *data) |
246 | { |
247 | if (comedi_dio_update_state(s, data)) |
248 | outw(value: s->state, port: dev->iobase + DAS16CS_DIO_REG); |
249 | |
250 | data[1] = inw(port: dev->iobase + DAS16CS_DIO_REG); |
251 | |
252 | return insn->n; |
253 | } |
254 | |
255 | static int das16cs_dio_insn_config(struct comedi_device *dev, |
256 | struct comedi_subdevice *s, |
257 | struct comedi_insn *insn, |
258 | unsigned int *data) |
259 | { |
260 | struct das16cs_private *devpriv = dev->private; |
261 | unsigned int chan = CR_CHAN(insn->chanspec); |
262 | unsigned int mask; |
263 | int ret; |
264 | |
265 | if (chan < 4) |
266 | mask = 0x0f; |
267 | else |
268 | mask = 0xf0; |
269 | |
270 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
271 | if (ret) |
272 | return ret; |
273 | |
274 | if (s->io_bits & 0xf0) |
275 | devpriv->misc2 |= DAS16CS_MISC2_UDIR; |
276 | else |
277 | devpriv->misc2 &= ~DAS16CS_MISC2_UDIR; |
278 | if (s->io_bits & 0x0f) |
279 | devpriv->misc2 |= DAS16CS_MISC2_LDIR; |
280 | else |
281 | devpriv->misc2 &= ~DAS16CS_MISC2_LDIR; |
282 | outw(value: devpriv->misc2, port: dev->iobase + DAS16CS_MISC2_REG); |
283 | |
284 | return insn->n; |
285 | } |
286 | |
287 | static int das16cs_counter_insn_config(struct comedi_device *dev, |
288 | struct comedi_subdevice *s, |
289 | struct comedi_insn *insn, |
290 | unsigned int *data) |
291 | { |
292 | struct das16cs_private *devpriv = dev->private; |
293 | |
294 | switch (data[0]) { |
295 | case INSN_CONFIG_SET_CLOCK_SRC: |
296 | switch (data[1]) { |
297 | case 0: /* internal 100 kHz */ |
298 | devpriv->misc2 |= DAS16CS_MISC2_CTR1; |
299 | break; |
300 | case 1: /* external */ |
301 | devpriv->misc2 &= ~DAS16CS_MISC2_CTR1; |
302 | break; |
303 | default: |
304 | return -EINVAL; |
305 | } |
306 | outw(value: devpriv->misc2, port: dev->iobase + DAS16CS_MISC2_REG); |
307 | break; |
308 | case INSN_CONFIG_GET_CLOCK_SRC: |
309 | if (devpriv->misc2 & DAS16CS_MISC2_CTR1) { |
310 | data[1] = 0; |
311 | data[2] = I8254_OSC_BASE_100KHZ; |
312 | } else { |
313 | data[1] = 1; |
314 | data[2] = 0; /* unknown */ |
315 | } |
316 | break; |
317 | default: |
318 | return -EINVAL; |
319 | } |
320 | |
321 | return insn->n; |
322 | } |
323 | |
324 | static const void *das16cs_find_boardinfo(struct comedi_device *dev, |
325 | struct pcmcia_device *link) |
326 | { |
327 | const struct das16cs_board *board; |
328 | int i; |
329 | |
330 | for (i = 0; i < ARRAY_SIZE(das16cs_boards); i++) { |
331 | board = &das16cs_boards[i]; |
332 | if (board->device_id == link->card_id) |
333 | return board; |
334 | } |
335 | |
336 | return NULL; |
337 | } |
338 | |
339 | static int das16cs_auto_attach(struct comedi_device *dev, |
340 | unsigned long context) |
341 | { |
342 | struct pcmcia_device *link = comedi_to_pcmcia_dev(dev); |
343 | const struct das16cs_board *board; |
344 | struct das16cs_private *devpriv; |
345 | struct comedi_subdevice *s; |
346 | int ret; |
347 | |
348 | board = das16cs_find_boardinfo(dev, link); |
349 | if (!board) |
350 | return -ENODEV; |
351 | dev->board_ptr = board; |
352 | dev->board_name = board->name; |
353 | |
354 | link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ; |
355 | ret = comedi_pcmcia_enable(dev, NULL); |
356 | if (ret) |
357 | return ret; |
358 | dev->iobase = link->resource[0]->start; |
359 | |
360 | link->priv = dev; |
361 | |
362 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
363 | if (!devpriv) |
364 | return -ENOMEM; |
365 | |
366 | dev->pacer = comedi_8254_io_alloc(iobase: dev->iobase + DAS16CS_TIMER_BASE, |
367 | I8254_OSC_BASE_10MHZ, I8254_IO16, regshift: 0); |
368 | if (IS_ERR(ptr: dev->pacer)) |
369 | return PTR_ERR(ptr: dev->pacer); |
370 | |
371 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
372 | if (ret) |
373 | return ret; |
374 | |
375 | /* Analog Input subdevice */ |
376 | s = &dev->subdevices[0]; |
377 | s->type = COMEDI_SUBD_AI; |
378 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; |
379 | s->n_chan = 16; |
380 | s->maxdata = 0xffff; |
381 | s->range_table = &das16cs_ai_range; |
382 | s->insn_read = das16cs_ai_insn_read; |
383 | |
384 | /* Analog Output subdevice */ |
385 | s = &dev->subdevices[1]; |
386 | if (board->has_ao) { |
387 | s->type = COMEDI_SUBD_AO; |
388 | s->subdev_flags = SDF_WRITABLE; |
389 | s->n_chan = 2; |
390 | s->maxdata = 0xffff; |
391 | s->range_table = &range_bipolar10; |
392 | s->insn_write = &das16cs_ao_insn_write; |
393 | |
394 | ret = comedi_alloc_subdev_readback(s); |
395 | if (ret) |
396 | return ret; |
397 | } else { |
398 | s->type = COMEDI_SUBD_UNUSED; |
399 | } |
400 | |
401 | /* Digital I/O subdevice */ |
402 | s = &dev->subdevices[2]; |
403 | s->type = COMEDI_SUBD_DIO; |
404 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
405 | s->n_chan = board->has_4dio ? 4 : 8; |
406 | s->maxdata = 1; |
407 | s->range_table = &range_digital; |
408 | s->insn_bits = das16cs_dio_insn_bits; |
409 | s->insn_config = das16cs_dio_insn_config; |
410 | |
411 | /* Counter subdevice (8254) */ |
412 | s = &dev->subdevices[3]; |
413 | comedi_8254_subdevice_init(s, i8254: dev->pacer); |
414 | |
415 | dev->pacer->insn_config = das16cs_counter_insn_config; |
416 | |
417 | /* counters 1 and 2 are used internally for the pacer */ |
418 | comedi_8254_set_busy(i8254: dev->pacer, counter: 1, busy: true); |
419 | comedi_8254_set_busy(i8254: dev->pacer, counter: 2, busy: true); |
420 | |
421 | return 0; |
422 | } |
423 | |
424 | static struct comedi_driver driver_das16cs = { |
425 | .driver_name = "cb_das16_cs" , |
426 | .module = THIS_MODULE, |
427 | .auto_attach = das16cs_auto_attach, |
428 | .detach = comedi_pcmcia_disable, |
429 | }; |
430 | |
431 | static int das16cs_pcmcia_attach(struct pcmcia_device *link) |
432 | { |
433 | return comedi_pcmcia_auto_config(link, driver: &driver_das16cs); |
434 | } |
435 | |
436 | static const struct pcmcia_device_id das16cs_id_table[] = { |
437 | PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x0039), |
438 | PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4009), |
439 | PCMCIA_DEVICE_NULL |
440 | }; |
441 | MODULE_DEVICE_TABLE(pcmcia, das16cs_id_table); |
442 | |
443 | static struct pcmcia_driver das16cs_driver = { |
444 | .name = "cb_das16_cs" , |
445 | .owner = THIS_MODULE, |
446 | .id_table = das16cs_id_table, |
447 | .probe = das16cs_pcmcia_attach, |
448 | .remove = comedi_pcmcia_auto_unconfig, |
449 | }; |
450 | module_comedi_pcmcia_driver(driver_das16cs, das16cs_driver); |
451 | |
452 | MODULE_AUTHOR("David A. Schleef <ds@schleef.org>" ); |
453 | MODULE_DESCRIPTION("Comedi driver for Computer Boards PC-CARD DAS16/16" ); |
454 | MODULE_LICENSE("GPL" ); |
455 | |