Mention branches and keyring.
[releases.git] / drivers / cb_das16_cs.c
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(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              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(devpriv->misc1, 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(devpriv->misc2, dev->iobase + DAS16CS_MISC2_REG);
177
178         for (i = 0; i < insn->n; i++) {
179                 outw(0, dev->iobase + DAS16CS_AI_DATA_REG);
180
181                 ret = comedi_timeout(dev, s, insn, das16cs_ai_eoc, 0);
182                 if (ret)
183                         return ret;
184
185                 data[i] = inw(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(devpriv->misc1, 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(misc1, 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(misc1, dev->iobase + DAS16CS_MISC1_REG);
225                         udelay(1);
226                         outw(misc1 | DAS16CS_MISC1_DACCLK,
227                              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(misc1 | DAS16CS_MISC1_DAC0CS | DAS16CS_MISC1_DAC1CS,
235                      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(s->state, dev->iobase + DAS16CS_DIO_REG);
249
250         data[1] = inw(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(devpriv->misc2, 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(devpriv->misc2, 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, sizeof(*devpriv));
363         if (!devpriv)
364                 return -ENOMEM;
365
366         dev->pacer = comedi_8254_io_alloc(dev->iobase + DAS16CS_TIMER_BASE,
367                                           I8254_OSC_BASE_10MHZ, I8254_IO16, 0);
368         if (IS_ERR(dev->pacer))
369                 return PTR_ERR(dev->pacer);
370
371         ret = comedi_alloc_subdevices(dev, 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, 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(dev->pacer, 1, true);
419         comedi_8254_set_busy(dev->pacer, 2, 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_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");