Merge tag 'thermal-6.8-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael...
[linux-2.6-block.git] / drivers / comedi / drivers / adv_pci1760.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * COMEDI driver for the Advantech PCI-1760
4  * Copyright (C) 2015 H Hartley Sweeten <hsweeten@visionengravers.com>
5  *
6  * Based on the pci1760 support in the adv_pci_dio driver written by:
7  *      Michal Dobes <dobes@tesnet.cz>
8  *
9  * COMEDI - Linux Control and Measurement Device Interface
10  * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
11  */
12
13 /*
14  * Driver: adv_pci1760
15  * Description: Advantech PCI-1760 Relay & Isolated Digital Input Card
16  * Devices: [Advantech] PCI-1760 (adv_pci1760)
17  * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
18  * Updated: Fri, 13 Nov 2015 12:34:00 -0700
19  * Status: untested
20  *
21  * Configuration Options: not applicable, uses PCI auto config
22  */
23
24 #include <linux/module.h>
25 #include <linux/comedi/comedi_pci.h>
26
27 /*
28  * PCI-1760 Register Map
29  *
30  * Outgoing Mailbox Bytes
31  * OMB3: Not used (must be 0)
32  * OMB2: The command code to the PCI-1760
33  * OMB1: The hi byte of the parameter for the command in OMB2
34  * OMB0: The lo byte of the parameter for the command in OMB2
35  *
36  * Incoming Mailbox Bytes
37  * IMB3: The Isolated Digital Input status (updated every 100us)
38  * IMB2: The current command (matches OMB2 when command is successful)
39  * IMB1: The hi byte of the feedback data for the command in OMB2
40  * IMB0: The lo byte of the feedback data for the command in OMB2
41  *
42  * Interrupt Control/Status
43  * INTCSR3: Not used (must be 0)
44  * INTCSR2: The interrupt status (read only)
45  * INTCSR1: Interrupt enable/disable
46  * INTCSR0: Not used (must be 0)
47  */
48 #define PCI1760_OMB_REG(x)              (0x0c + (x))
49 #define PCI1760_IMB_REG(x)              (0x1c + (x))
50 #define PCI1760_INTCSR_REG(x)           (0x38 + (x))
51 #define PCI1760_INTCSR1_IRQ_ENA         BIT(5)
52 #define PCI1760_INTCSR2_OMB_IRQ         BIT(0)
53 #define PCI1760_INTCSR2_IMB_IRQ         BIT(1)
54 #define PCI1760_INTCSR2_IRQ_STATUS      BIT(6)
55 #define PCI1760_INTCSR2_IRQ_ASSERTED    BIT(7)
56
57 /* PCI-1760 command codes */
58 #define PCI1760_CMD_CLR_IMB2            0x00    /* Clears IMB2 */
59 #define PCI1760_CMD_SET_DO              0x01    /* Set output state */
60 #define PCI1760_CMD_GET_DO              0x02    /* Read output status */
61 #define PCI1760_CMD_GET_STATUS          0x07    /* Read current status */
62 #define PCI1760_CMD_GET_FW_VER          0x0e    /* Read firmware version */
63 #define PCI1760_CMD_GET_HW_VER          0x0f    /* Read hardware version */
64 #define PCI1760_CMD_SET_PWM_HI(x)       (0x10 + (x) * 2) /* Set "hi" period */
65 #define PCI1760_CMD_SET_PWM_LO(x)       (0x11 + (x) * 2) /* Set "lo" period */
66 #define PCI1760_CMD_SET_PWM_CNT(x)      (0x14 + (x)) /* Set burst count */
67 #define PCI1760_CMD_ENA_PWM             0x1f    /* Enable PWM outputs */
68 #define PCI1760_CMD_ENA_FILT            0x20    /* Enable input filter */
69 #define PCI1760_CMD_ENA_PAT_MATCH       0x21    /* Enable input pattern match */
70 #define PCI1760_CMD_SET_PAT_MATCH       0x22    /* Set input pattern match */
71 #define PCI1760_CMD_ENA_RISE_EDGE       0x23    /* Enable input rising edge */
72 #define PCI1760_CMD_ENA_FALL_EDGE       0x24    /* Enable input falling edge */
73 #define PCI1760_CMD_ENA_CNT             0x28    /* Enable counter */
74 #define PCI1760_CMD_RST_CNT             0x29    /* Reset counter */
75 #define PCI1760_CMD_ENA_CNT_OFLOW       0x2a    /* Enable counter overflow */
76 #define PCI1760_CMD_ENA_CNT_MATCH       0x2b    /* Enable counter match */
77 #define PCI1760_CMD_SET_CNT_EDGE        0x2c    /* Set counter edge */
78 #define PCI1760_CMD_GET_CNT             0x2f    /* Reads counter value */
79 #define PCI1760_CMD_SET_HI_SAMP(x)      (0x30 + (x)) /* Set "hi" sample time */
80 #define PCI1760_CMD_SET_LO_SAMP(x)      (0x38 + (x)) /* Set "lo" sample time */
81 #define PCI1760_CMD_SET_CNT(x)          (0x40 + (x)) /* Set counter reset val */
82 #define PCI1760_CMD_SET_CNT_MATCH(x)    (0x48 + (x)) /* Set counter match val */
83 #define PCI1760_CMD_GET_INT_FLAGS       0x60    /* Read interrupt flags */
84 #define PCI1760_CMD_GET_INT_FLAGS_MATCH BIT(0)
85 #define PCI1760_CMD_GET_INT_FLAGS_COS   BIT(1)
86 #define PCI1760_CMD_GET_INT_FLAGS_OFLOW BIT(2)
87 #define PCI1760_CMD_GET_OS              0x61    /* Read edge change flags */
88 #define PCI1760_CMD_GET_CNT_STATUS      0x62    /* Read counter oflow/match */
89
90 #define PCI1760_CMD_TIMEOUT             250     /* 250 usec timeout */
91 #define PCI1760_CMD_RETRIES             3       /* limit number of retries */
92
93 #define PCI1760_PWM_TIMEBASE            100000  /* 1 unit = 100 usec */
94
95 static int pci1760_send_cmd(struct comedi_device *dev,
96                             unsigned char cmd, unsigned short val)
97 {
98         unsigned long timeout;
99
100         /* send the command and parameter */
101         outb(val & 0xff, dev->iobase + PCI1760_OMB_REG(0));
102         outb((val >> 8) & 0xff, dev->iobase + PCI1760_OMB_REG(1));
103         outb(cmd, dev->iobase + PCI1760_OMB_REG(2));
104         outb(0, dev->iobase + PCI1760_OMB_REG(3));
105
106         /* datasheet says to allow up to 250 usec for the command to complete */
107         timeout = jiffies + usecs_to_jiffies(PCI1760_CMD_TIMEOUT);
108         do {
109                 if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) {
110                         /* command success; return the feedback data */
111                         return inb(dev->iobase + PCI1760_IMB_REG(0)) |
112                                (inb(dev->iobase + PCI1760_IMB_REG(1)) << 8);
113                 }
114                 cpu_relax();
115         } while (time_before(jiffies, timeout));
116
117         return -EBUSY;
118 }
119
120 static int pci1760_cmd(struct comedi_device *dev,
121                        unsigned char cmd, unsigned short val)
122 {
123         int repeats;
124         int ret;
125
126         /* send PCI1760_CMD_CLR_IMB2 between identical commands */
127         if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) {
128                 ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0);
129                 if (ret < 0) {
130                         /* timeout? try it once more */
131                         ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0);
132                         if (ret < 0)
133                                 return -ETIMEDOUT;
134                 }
135         }
136
137         /* datasheet says to keep retrying the command */
138         for (repeats = 0; repeats < PCI1760_CMD_RETRIES; repeats++) {
139                 ret = pci1760_send_cmd(dev, cmd, val);
140                 if (ret >= 0)
141                         return ret;
142         }
143
144         /* command failed! */
145         return -ETIMEDOUT;
146 }
147
148 static int pci1760_di_insn_bits(struct comedi_device *dev,
149                                 struct comedi_subdevice *s,
150                                 struct comedi_insn *insn,
151                                 unsigned int *data)
152 {
153         data[1] = inb(dev->iobase + PCI1760_IMB_REG(3));
154
155         return insn->n;
156 }
157
158 static int pci1760_do_insn_bits(struct comedi_device *dev,
159                                 struct comedi_subdevice *s,
160                                 struct comedi_insn *insn,
161                                 unsigned int *data)
162 {
163         int ret;
164
165         if (comedi_dio_update_state(s, data)) {
166                 ret = pci1760_cmd(dev, PCI1760_CMD_SET_DO, s->state);
167                 if (ret < 0)
168                         return ret;
169         }
170
171         data[1] = s->state;
172
173         return insn->n;
174 }
175
176 static int pci1760_pwm_ns_to_div(unsigned int flags, unsigned int ns)
177 {
178         unsigned int divisor;
179
180         switch (flags) {
181         case CMDF_ROUND_NEAREST:
182                 divisor = DIV_ROUND_CLOSEST(ns, PCI1760_PWM_TIMEBASE);
183                 break;
184         case CMDF_ROUND_UP:
185                 divisor = DIV_ROUND_UP(ns, PCI1760_PWM_TIMEBASE);
186                 break;
187         case CMDF_ROUND_DOWN:
188                 divisor = ns / PCI1760_PWM_TIMEBASE;
189                 break;
190         default:
191                 return -EINVAL;
192         }
193
194         if (divisor < 1)
195                 divisor = 1;
196         if (divisor > 0xffff)
197                 divisor = 0xffff;
198
199         return divisor;
200 }
201
202 static int pci1760_pwm_enable(struct comedi_device *dev,
203                               unsigned int chan, bool enable)
204 {
205         int ret;
206
207         ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, PCI1760_CMD_ENA_PWM);
208         if (ret < 0)
209                 return ret;
210
211         if (enable)
212                 ret |= BIT(chan);
213         else
214                 ret &= ~BIT(chan);
215
216         return pci1760_cmd(dev, PCI1760_CMD_ENA_PWM, ret);
217 }
218
219 static int pci1760_pwm_insn_config(struct comedi_device *dev,
220                                    struct comedi_subdevice *s,
221                                    struct comedi_insn *insn,
222                                    unsigned int *data)
223 {
224         unsigned int chan = CR_CHAN(insn->chanspec);
225         int hi_div;
226         int lo_div;
227         int ret;
228
229         switch (data[0]) {
230         case INSN_CONFIG_ARM:
231                 ret = pci1760_pwm_enable(dev, chan, false);
232                 if (ret < 0)
233                         return ret;
234
235                 if (data[1] > 0xffff)
236                         return -EINVAL;
237                 ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_CNT(chan), data[1]);
238                 if (ret < 0)
239                         return ret;
240
241                 ret = pci1760_pwm_enable(dev, chan, true);
242                 if (ret < 0)
243                         return ret;
244                 break;
245         case INSN_CONFIG_DISARM:
246                 ret = pci1760_pwm_enable(dev, chan, false);
247                 if (ret < 0)
248                         return ret;
249                 break;
250         case INSN_CONFIG_PWM_OUTPUT:
251                 ret = pci1760_pwm_enable(dev, chan, false);
252                 if (ret < 0)
253                         return ret;
254
255                 hi_div = pci1760_pwm_ns_to_div(data[1], data[2]);
256                 lo_div = pci1760_pwm_ns_to_div(data[3], data[4]);
257                 if (hi_div < 0 || lo_div < 0)
258                         return -EINVAL;
259                 if ((hi_div * PCI1760_PWM_TIMEBASE) != data[2] ||
260                     (lo_div * PCI1760_PWM_TIMEBASE) != data[4]) {
261                         data[2] = hi_div * PCI1760_PWM_TIMEBASE;
262                         data[4] = lo_div * PCI1760_PWM_TIMEBASE;
263                         return -EAGAIN;
264                 }
265                 ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_HI(chan), hi_div);
266                 if (ret < 0)
267                         return ret;
268                 ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_LO(chan), lo_div);
269                 if (ret < 0)
270                         return ret;
271                 break;
272         case INSN_CONFIG_GET_PWM_OUTPUT:
273                 hi_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS,
274                                      PCI1760_CMD_SET_PWM_HI(chan));
275                 lo_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS,
276                                      PCI1760_CMD_SET_PWM_LO(chan));
277                 if (hi_div < 0 || lo_div < 0)
278                         return -ETIMEDOUT;
279
280                 data[1] = hi_div * PCI1760_PWM_TIMEBASE;
281                 data[2] = lo_div * PCI1760_PWM_TIMEBASE;
282                 break;
283         case INSN_CONFIG_GET_PWM_STATUS:
284                 ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS,
285                                   PCI1760_CMD_ENA_PWM);
286                 if (ret < 0)
287                         return ret;
288
289                 data[1] = (ret & BIT(chan)) ? 1 : 0;
290                 break;
291         default:
292                 return -EINVAL;
293         }
294
295         return insn->n;
296 }
297
298 static void pci1760_reset(struct comedi_device *dev)
299 {
300         int i;
301
302         /* disable interrupts (intcsr2 is read-only) */
303         outb(0, dev->iobase + PCI1760_INTCSR_REG(0));
304         outb(0, dev->iobase + PCI1760_INTCSR_REG(1));
305         outb(0, dev->iobase + PCI1760_INTCSR_REG(3));
306
307         /* disable counters */
308         pci1760_cmd(dev, PCI1760_CMD_ENA_CNT, 0);
309
310         /* disable overflow interrupts */
311         pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_OFLOW, 0);
312
313         /* disable match */
314         pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_MATCH, 0);
315
316         /* set match and counter reset values */
317         for (i = 0; i < 8; i++) {
318                 pci1760_cmd(dev, PCI1760_CMD_SET_CNT_MATCH(i), 0x8000);
319                 pci1760_cmd(dev, PCI1760_CMD_SET_CNT(i), 0x0000);
320         }
321
322         /* reset counters to reset values */
323         pci1760_cmd(dev, PCI1760_CMD_RST_CNT, 0xff);
324
325         /* set counter count edges */
326         pci1760_cmd(dev, PCI1760_CMD_SET_CNT_EDGE, 0);
327
328         /* disable input filters */
329         pci1760_cmd(dev, PCI1760_CMD_ENA_FILT, 0);
330
331         /* disable pattern matching */
332         pci1760_cmd(dev, PCI1760_CMD_ENA_PAT_MATCH, 0);
333
334         /* set pattern match value */
335         pci1760_cmd(dev, PCI1760_CMD_SET_PAT_MATCH, 0);
336 }
337
338 static int pci1760_auto_attach(struct comedi_device *dev,
339                                unsigned long context)
340 {
341         struct pci_dev *pcidev = comedi_to_pci_dev(dev);
342         struct comedi_subdevice *s;
343         int ret;
344
345         ret = comedi_pci_enable(dev);
346         if (ret)
347                 return ret;
348         dev->iobase = pci_resource_start(pcidev, 0);
349
350         pci1760_reset(dev);
351
352         ret = comedi_alloc_subdevices(dev, 4);
353         if (ret)
354                 return ret;
355
356         /* Digital Input subdevice */
357         s = &dev->subdevices[0];
358         s->type         = COMEDI_SUBD_DI;
359         s->subdev_flags = SDF_READABLE;
360         s->n_chan       = 8;
361         s->maxdata      = 1;
362         s->range_table  = &range_digital;
363         s->insn_bits    = pci1760_di_insn_bits;
364
365         /* Digital Output subdevice */
366         s = &dev->subdevices[1];
367         s->type         = COMEDI_SUBD_DO;
368         s->subdev_flags = SDF_WRITABLE;
369         s->n_chan       = 8;
370         s->maxdata      = 1;
371         s->range_table  = &range_digital;
372         s->insn_bits    = pci1760_do_insn_bits;
373
374         /* get the current state of the outputs */
375         ret = pci1760_cmd(dev, PCI1760_CMD_GET_DO, 0);
376         if (ret < 0)
377                 return ret;
378         s->state        = ret;
379
380         /* PWM subdevice */
381         s = &dev->subdevices[2];
382         s->type         = COMEDI_SUBD_PWM;
383         s->subdev_flags = SDF_PWM_COUNTER;
384         s->n_chan       = 2;
385         s->insn_config  = pci1760_pwm_insn_config;
386
387         /* Counter subdevice */
388         s = &dev->subdevices[3];
389         s->type         = COMEDI_SUBD_UNUSED;
390
391         return 0;
392 }
393
394 static struct comedi_driver pci1760_driver = {
395         .driver_name    = "adv_pci1760",
396         .module         = THIS_MODULE,
397         .auto_attach    = pci1760_auto_attach,
398         .detach         = comedi_pci_detach,
399 };
400
401 static int pci1760_pci_probe(struct pci_dev *dev,
402                              const struct pci_device_id *id)
403 {
404         return comedi_pci_auto_config(dev, &pci1760_driver, id->driver_data);
405 }
406
407 static const struct pci_device_id pci1760_pci_table[] = {
408         { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1760) },
409         { 0 }
410 };
411 MODULE_DEVICE_TABLE(pci, pci1760_pci_table);
412
413 static struct pci_driver pci1760_pci_driver = {
414         .name           = "adv_pci1760",
415         .id_table       = pci1760_pci_table,
416         .probe          = pci1760_pci_probe,
417         .remove         = comedi_pci_auto_unconfig,
418 };
419 module_comedi_pci_driver(pci1760_driver, pci1760_pci_driver);
420
421 MODULE_AUTHOR("Comedi https://www.comedi.org");
422 MODULE_DESCRIPTION("Comedi driver for Advantech PCI-1760");
423 MODULE_LICENSE("GPL");