Linux-libre 5.4.47-gnu
[librecmc/linux-libre.git] / drivers / staging / comedi / drivers / multiq3.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * multiq3.c
4  * Hardware driver for Quanser Consulting MultiQ-3 board
5  *
6  * COMEDI - Linux Control and Measurement Device Interface
7  * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
8  */
9
10 /*
11  * Driver: multiq3
12  * Description: Quanser Consulting MultiQ-3
13  * Devices: [Quanser Consulting] MultiQ-3 (multiq3)
14  * Author: Anders Blomdell <anders.blomdell@control.lth.se>
15  * Status: works
16  *
17  * Configuration Options:
18  *  [0] - I/O port base address
19  *  [1] - IRQ (not used)
20  *  [2] - Number of optional encoder chips installed on board
21  *        0 = none
22  *        1 = 2 inputs (Model -2E)
23  *        2 = 4 inputs (Model -4E)
24  *        3 = 6 inputs (Model -6E)
25  *        4 = 8 inputs (Model -8E)
26  */
27
28 #include <linux/module.h>
29
30 #include "../comedidev.h"
31
32 /*
33  * Register map
34  */
35 #define MULTIQ3_DI_REG                  0x00
36 #define MULTIQ3_DO_REG                  0x00
37 #define MULTIQ3_AO_REG                  0x02
38 #define MULTIQ3_AI_REG                  0x04
39 #define MULTIQ3_AI_CONV_REG             0x04
40 #define MULTIQ3_STATUS_REG              0x06
41 #define MULTIQ3_STATUS_EOC              BIT(3)
42 #define MULTIQ3_STATUS_EOC_I            BIT(4)
43 #define MULTIQ3_CTRL_REG                0x06
44 #define MULTIQ3_CTRL_AO_CHAN(x)         (((x) & 0x7) << 0)
45 #define MULTIQ3_CTRL_RC(x)              (((x) & 0x3) << 0)
46 #define MULTIQ3_CTRL_AI_CHAN(x)         (((x) & 0x7) << 3)
47 #define MULTIQ3_CTRL_E_CHAN(x)          (((x) & 0x7) << 3)
48 #define MULTIQ3_CTRL_EN                 BIT(6)
49 #define MULTIQ3_CTRL_AZ                 BIT(7)
50 #define MULTIQ3_CTRL_CAL                BIT(8)
51 #define MULTIQ3_CTRL_SH                 BIT(9)
52 #define MULTIQ3_CTRL_CLK                BIT(10)
53 #define MULTIQ3_CTRL_LD                 (3 << 11)
54 #define MULTIQ3_CLK_REG                 0x08
55 #define MULTIQ3_ENC_DATA_REG            0x0c
56 #define MULTIQ3_ENC_CTRL_REG            0x0e
57
58 /*
59  * Encoder chip commands (from the programming manual)
60  */
61 #define MULTIQ3_CLOCK_DATA              0x00    /* FCK frequency divider */
62 #define MULTIQ3_CLOCK_SETUP             0x18    /* xfer PR0 to PSC */
63 #define MULTIQ3_INPUT_SETUP             0x41    /* enable inputs A and B */
64 #define MULTIQ3_QUAD_X4                 0x38    /* quadrature */
65 #define MULTIQ3_BP_RESET                0x01    /* reset byte pointer */
66 #define MULTIQ3_CNTR_RESET              0x02    /* reset counter */
67 #define MULTIQ3_TRSFRPR_CTR             0x08    /* xfre preset reg to counter */
68 #define MULTIQ3_TRSFRCNTR_OL            0x10    /* xfer CNTR to OL (x and y) */
69 #define MULTIQ3_EFLAG_RESET             0x06    /* reset E bit of flag reg */
70
71 static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits)
72 {
73         /*
74          * According to the programming manual, the SH and CLK bits should
75          * be kept high at all times.
76          */
77         outw(MULTIQ3_CTRL_SH | MULTIQ3_CTRL_CLK | bits,
78              dev->iobase + MULTIQ3_CTRL_REG);
79 }
80
81 static int multiq3_ai_status(struct comedi_device *dev,
82                              struct comedi_subdevice *s,
83                              struct comedi_insn *insn,
84                              unsigned long context)
85 {
86         unsigned int status;
87
88         status = inw(dev->iobase + MULTIQ3_STATUS_REG);
89         if (status & context)
90                 return 0;
91         return -EBUSY;
92 }
93
94 static int multiq3_ai_insn_read(struct comedi_device *dev,
95                                 struct comedi_subdevice *s,
96                                 struct comedi_insn *insn,
97                                 unsigned int *data)
98 {
99         unsigned int chan = CR_CHAN(insn->chanspec);
100         unsigned int val;
101         int ret;
102         int i;
103
104         multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_AI_CHAN(chan));
105
106         ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
107                              MULTIQ3_STATUS_EOC);
108         if (ret)
109                 return ret;
110
111         for (i = 0; i < insn->n; i++) {
112                 outw(0, dev->iobase + MULTIQ3_AI_CONV_REG);
113
114                 ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
115                                      MULTIQ3_STATUS_EOC_I);
116                 if (ret)
117                         return ret;
118
119                 /* get a 16-bit sample; mask it to the subdevice resolution */
120                 val = inb(dev->iobase + MULTIQ3_AI_REG) << 8;
121                 val |= inb(dev->iobase + MULTIQ3_AI_REG);
122                 val &= s->maxdata;
123
124                 /* munge the 2's complement value to offset binary */
125                 data[i] = comedi_offset_munge(s, val);
126         }
127
128         return insn->n;
129 }
130
131 static int multiq3_ao_insn_write(struct comedi_device *dev,
132                                  struct comedi_subdevice *s,
133                                  struct comedi_insn *insn,
134                                  unsigned int *data)
135 {
136         unsigned int chan = CR_CHAN(insn->chanspec);
137         unsigned int val = s->readback[chan];
138         int i;
139
140         for (i = 0; i < insn->n; i++) {
141                 val = data[i];
142                 multiq3_set_ctrl(dev, MULTIQ3_CTRL_LD |
143                                       MULTIQ3_CTRL_AO_CHAN(chan));
144                 outw(val, dev->iobase + MULTIQ3_AO_REG);
145                 multiq3_set_ctrl(dev, 0);
146         }
147         s->readback[chan] = val;
148
149         return insn->n;
150 }
151
152 static int multiq3_di_insn_bits(struct comedi_device *dev,
153                                 struct comedi_subdevice *s,
154                                 struct comedi_insn *insn, unsigned int *data)
155 {
156         data[1] = inw(dev->iobase + MULTIQ3_DI_REG);
157
158         return insn->n;
159 }
160
161 static int multiq3_do_insn_bits(struct comedi_device *dev,
162                                 struct comedi_subdevice *s,
163                                 struct comedi_insn *insn,
164                                 unsigned int *data)
165 {
166         if (comedi_dio_update_state(s, data))
167                 outw(s->state, dev->iobase + MULTIQ3_DO_REG);
168
169         data[1] = s->state;
170
171         return insn->n;
172 }
173
174 static int multiq3_encoder_insn_read(struct comedi_device *dev,
175                                      struct comedi_subdevice *s,
176                                      struct comedi_insn *insn,
177                                      unsigned int *data)
178 {
179         unsigned int chan = CR_CHAN(insn->chanspec);
180         unsigned int val;
181         int i;
182
183         for (i = 0; i < insn->n; i++) {
184                 /* select encoder channel */
185                 multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN |
186                                       MULTIQ3_CTRL_E_CHAN(chan));
187
188                 /* reset the byte pointer */
189                 outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
190
191                 /* latch the data */
192                 outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CTRL_REG);
193
194                 /* read the 24-bit encoder data (lsb/mid/msb) */
195                 val = inb(dev->iobase + MULTIQ3_ENC_DATA_REG);
196                 val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 8);
197                 val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 16);
198
199                 /*
200                  * Munge the data so that the reset value is in the middle
201                  * of the maxdata range, i.e.:
202                  *
203                  * real value   comedi value
204                  * 0xffffff     0x7fffff        1 negative count
205                  * 0x000000     0x800000        reset value
206                  * 0x000001     0x800001        1 positive count
207                  *
208                  * It's possible for the 24-bit counter to overflow but it
209                  * would normally take _quite_ a few turns. A 2000 line
210                  * encoder in quadrature results in 8000 counts/rev. So about
211                  * 1048 turns in either direction can be measured without
212                  * an overflow.
213                  */
214                 data[i] = (val + ((s->maxdata + 1) >> 1)) & s->maxdata;
215         }
216
217         return insn->n;
218 }
219
220 static void multiq3_encoder_reset(struct comedi_device *dev,
221                                   unsigned int chan)
222 {
223         multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_E_CHAN(chan));
224         outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
225         outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
226         outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA_REG);
227         outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
228         outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
229         outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CTRL_REG);
230         outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
231 }
232
233 static int multiq3_encoder_insn_config(struct comedi_device *dev,
234                                        struct comedi_subdevice *s,
235                                        struct comedi_insn *insn,
236                                        unsigned int *data)
237 {
238         unsigned int chan = CR_CHAN(insn->chanspec);
239
240         switch (data[0]) {
241         case INSN_CONFIG_RESET:
242                 multiq3_encoder_reset(dev, chan);
243                 break;
244         default:
245                 return -EINVAL;
246         }
247
248         return insn->n;
249 }
250
251 static int multiq3_attach(struct comedi_device *dev,
252                           struct comedi_devconfig *it)
253 {
254         struct comedi_subdevice *s;
255         int ret;
256         int i;
257
258         ret = comedi_request_region(dev, it->options[0], 0x10);
259         if (ret)
260                 return ret;
261
262         ret = comedi_alloc_subdevices(dev, 5);
263         if (ret)
264                 return ret;
265
266         /* Analog Input subdevice */
267         s = &dev->subdevices[0];
268         s->type         = COMEDI_SUBD_AI;
269         s->subdev_flags = SDF_READABLE | SDF_GROUND;
270         s->n_chan       = 8;
271         s->maxdata      = 0x1fff;
272         s->range_table  = &range_bipolar5;
273         s->insn_read    = multiq3_ai_insn_read;
274
275         /* Analog Output subdevice */
276         s = &dev->subdevices[1];
277         s->type         = COMEDI_SUBD_AO;
278         s->subdev_flags = SDF_WRITABLE;
279         s->n_chan       = 8;
280         s->maxdata      = 0x0fff;
281         s->range_table  = &range_bipolar5;
282         s->insn_write   = multiq3_ao_insn_write;
283
284         ret = comedi_alloc_subdev_readback(s);
285         if (ret)
286                 return ret;
287
288         /* Digital Input subdevice */
289         s = &dev->subdevices[2];
290         s->type         = COMEDI_SUBD_DI;
291         s->subdev_flags = SDF_READABLE;
292         s->n_chan       = 16;
293         s->maxdata      = 1;
294         s->range_table  = &range_digital;
295         s->insn_bits    = multiq3_di_insn_bits;
296
297         /* Digital Output subdevice */
298         s = &dev->subdevices[3];
299         s->type         = COMEDI_SUBD_DO;
300         s->subdev_flags = SDF_WRITABLE;
301         s->n_chan       = 16;
302         s->maxdata      = 1;
303         s->range_table  = &range_digital;
304         s->insn_bits    = multiq3_do_insn_bits;
305
306         /* Encoder (Counter) subdevice */
307         s = &dev->subdevices[4];
308         s->type         = COMEDI_SUBD_COUNTER;
309         s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
310         s->n_chan       = it->options[2] * 2;
311         s->maxdata      = 0x00ffffff;
312         s->range_table  = &range_unknown;
313         s->insn_read    = multiq3_encoder_insn_read;
314         s->insn_config  = multiq3_encoder_insn_config;
315
316         for (i = 0; i < s->n_chan; i++)
317                 multiq3_encoder_reset(dev, i);
318
319         return 0;
320 }
321
322 static struct comedi_driver multiq3_driver = {
323         .driver_name    = "multiq3",
324         .module         = THIS_MODULE,
325         .attach         = multiq3_attach,
326         .detach         = comedi_legacy_detach,
327 };
328 module_comedi_driver(multiq3_driver);
329
330 MODULE_AUTHOR("Comedi http://www.comedi.org");
331 MODULE_DESCRIPTION("Comedi driver for Quanser Consulting MultiQ-3 board");
332 MODULE_LICENSE("GPL");