]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blame - drivers/staging/comedi/drivers/addi_apci_2032.c
staging: comedi: introduce addi_watchdog driver
[mirror_ubuntu-bionic-kernel.git] / drivers / staging / comedi / drivers / addi_apci_2032.c
CommitLineData
d02178b7
HS
1/*
2 * addi_apci_2032.c
3 * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module.
4 * Project manager: Eric Stolz
5 *
6 * ADDI-DATA GmbH
7 * Dieselstrasse 3
8 * D-77833 Ottersweier
9 * Tel: +19(0)7223/9493-0
10 * Fax: +49(0)7223/9493-92
11 * http://www.addi-data.com
12 * info@addi-data.com
13 *
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU General Public License as published by the
16 * Free Software Foundation; either version 2 of the License, or (at your
17 * option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * more details.
23 *
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 *
28 * You should also find the complete GPL in the COPYING file accompanying
29 * this source code.
30 */
31
3d41c443
HS
32#include "../comedidev.h"
33#include "comedi_fc.h"
34
d02178b7
HS
35/*
36 * PCI bar 1 I/O Register map
37 */
7180eb30
HS
38#define APCI2032_DO_REG 0x00
39#define APCI2032_INT_CTRL_REG 0x04
40#define APCI2032_INT_CTRL_VCC_ENA (1 << 0)
41#define APCI2032_INT_CTRL_CC_ENA (1 << 1)
42#define APCI2032_INT_STATUS_REG 0x08
43#define APCI2032_INT_STATUS_VCC (1 << 0)
44#define APCI2032_INT_STATUS_CC (1 << 1)
45#define APCI2032_STATUS_REG 0x0c
46#define APCI2032_STATUS_IRQ (1 << 0)
47#define APCI2032_WDOG_REG 0x10
48#define APCI2032_WDOG_RELOAD_REG 0x14
49#define APCI2032_WDOG_TIMEBASE 0x18
50#define APCI2032_WDOG_CTRL_REG 0x1c
51#define APCI2032_WDOG_CTRL_ENABLE (1 << 0)
52#define APCI2032_WDOG_CTRL_SW_TRIG (1 << 9)
53#define APCI2032_WDOG_STATUS_REG 0x20
54#define APCI2032_WDOG_STATUS_ENABLED (1 << 0)
55#define APCI2032_WDOG_STATUS_SW_TRIG (1 << 1)
d02178b7 56
23fb1747
HS
57struct apci2032_private {
58 unsigned int wdog_ctrl;
59};
60
5c2d4cba
IA
61struct apci2032_int_private {
62 spinlock_t spinlock;
215caceb 63 unsigned int stop_count;
5c2d4cba
IA
64 bool active;
65 unsigned char enabled_isns;
66};
67
d02178b7
HS
68static int apci2032_do_insn_bits(struct comedi_device *dev,
69 struct comedi_subdevice *s,
70 struct comedi_insn *insn,
71 unsigned int *data)
72{
73 unsigned int mask = data[0];
74 unsigned int bits = data[1];
75
7180eb30 76 s->state = inl(dev->iobase + APCI2032_DO_REG);
d02178b7
HS
77 if (mask) {
78 s->state &= ~mask;
79 s->state |= (bits & mask);
80
7180eb30 81 outl(s->state, dev->iobase + APCI2032_DO_REG);
d02178b7
HS
82 }
83
84 data[1] = s->state;
85
86 return insn->n;
87}
88
23fb1747
HS
89/*
90 * The watchdog subdevice is configured with two INSN_CONFIG instructions:
91 *
92 * Enable the watchdog and set the reload timeout:
93 * data[0] = INSN_CONFIG_ARM
94 * data[1] = timeout reload value
95 *
96 * Disable the watchdog:
97 * data[0] = INSN_CONFIG_DISARM
98 */
99static int apci2032_wdog_insn_config(struct comedi_device *dev,
d02178b7
HS
100 struct comedi_subdevice *s,
101 struct comedi_insn *insn,
102 unsigned int *data)
103{
23fb1747
HS
104 struct apci2032_private *devpriv = dev->private;
105 unsigned int reload;
106
107 switch (data[0]) {
108 case INSN_CONFIG_ARM:
109 devpriv->wdog_ctrl = APCI2032_WDOG_CTRL_ENABLE;
110 reload = data[1] & s->maxdata;
111 outw(reload, dev->iobase + APCI2032_WDOG_RELOAD_REG);
112
113 /* Time base is 20ms, let the user know the timeout */
114 dev_info(dev->class_dev, "watchdog enabled, timeout:%dms\n",
115 20 * reload + 20);
116 break;
117 case INSN_CONFIG_DISARM:
118 devpriv->wdog_ctrl = 0;
119 break;
120 default:
d02178b7
HS
121 return -EINVAL;
122 }
123
23fb1747
HS
124 outw(devpriv->wdog_ctrl, dev->iobase + APCI2032_WDOG_CTRL_REG);
125
d02178b7
HS
126 return insn->n;
127}
128
23fb1747
HS
129static int apci2032_wdog_insn_write(struct comedi_device *dev,
130 struct comedi_subdevice *s,
131 struct comedi_insn *insn,
132 unsigned int *data)
d02178b7 133{
23fb1747
HS
134 struct apci2032_private *devpriv = dev->private;
135 int i;
136
137 if (devpriv->wdog_ctrl == 0) {
138 dev_warn(dev->class_dev, "watchdog is disabled\n");
d02178b7
HS
139 return -EINVAL;
140 }
23fb1747
HS
141
142 /* "ping" the watchdog */
143 for (i = 0; i < insn->n; i++) {
144 outw(devpriv->wdog_ctrl | APCI2032_WDOG_CTRL_SW_TRIG,
145 dev->iobase + APCI2032_WDOG_CTRL_REG);
146 }
147
d02178b7
HS
148 return insn->n;
149}
150
23fb1747 151static int apci2032_wdog_insn_read(struct comedi_device *dev,
d02178b7
HS
152 struct comedi_subdevice *s,
153 struct comedi_insn *insn,
154 unsigned int *data)
155{
7b5dd1cc
HS
156 int i;
157
158 for (i = 0; i < insn->n; i++)
159 data[i] = inl(dev->iobase + APCI2032_WDOG_STATUS_REG);
160
d02178b7
HS
161 return insn->n;
162}
163
05fcdced
HS
164static int apci2032_int_insn_bits(struct comedi_device *dev,
165 struct comedi_subdevice *s,
166 struct comedi_insn *insn,
167 unsigned int *data)
b3b7dab7 168{
b652bd83 169 data[1] = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
05fcdced
HS
170 return insn->n;
171}
b3b7dab7 172
5c2d4cba
IA
173static void apci2032_int_stop(struct comedi_device *dev,
174 struct comedi_subdevice *s)
175{
176 struct apci2032_int_private *subpriv = s->private;
177
178 subpriv->active = false;
179 subpriv->enabled_isns = 0;
180 outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
181}
182
215caceb
IA
183static bool apci2032_int_start(struct comedi_device *dev,
184 struct comedi_subdevice *s,
185 unsigned char enabled_isns)
186{
187 struct apci2032_int_private *subpriv = s->private;
188 struct comedi_cmd *cmd = &s->async->cmd;
189 bool do_event;
190
191 subpriv->enabled_isns = enabled_isns;
192 subpriv->stop_count = cmd->stop_arg;
193 if (cmd->stop_src == TRIG_COUNT && subpriv->stop_count == 0) {
194 /* An empty acquisition! */
195 s->async->events |= COMEDI_CB_EOA;
196 subpriv->active = false;
197 do_event = true;
198 } else {
199 subpriv->active = true;
200 outl(enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG);
201 do_event = false;
202 }
203
204 return do_event;
205}
206
05fcdced
HS
207static int apci2032_int_cmdtest(struct comedi_device *dev,
208 struct comedi_subdevice *s,
209 struct comedi_cmd *cmd)
210{
211 int err = 0;
b3b7dab7 212
05fcdced 213 /* Step 1 : check if triggers are trivially valid */
b3b7dab7 214
05fcdced 215 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
5c2d4cba
IA
216 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
217 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
05fcdced 218 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
215caceb 219 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
b3b7dab7 220
05fcdced
HS
221 if (err)
222 return 1;
b3b7dab7 223
05fcdced 224 /* Step 2a : make sure trigger sources are unique */
215caceb
IA
225 err |= cfc_check_trigger_is_unique(cmd->stop_src);
226
05fcdced
HS
227 /* Step 2b : and mutually compatible */
228
229 if (err)
230 return 2;
231
232 /* Step 3: check if arguments are trivially valid */
233
234 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
5c2d4cba 235 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
05fcdced 236 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
5c2d4cba 237 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
215caceb
IA
238 if (cmd->stop_src == TRIG_NONE)
239 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
05fcdced
HS
240
241 if (err)
242 return 3;
243
244 /* step 4: ignored */
245
246 if (err)
247 return 4;
248
249 return 0;
b3b7dab7
HS
250}
251
05fcdced
HS
252static int apci2032_int_cmd(struct comedi_device *dev,
253 struct comedi_subdevice *s)
d02178b7 254{
05fcdced 255 struct comedi_cmd *cmd = &s->async->cmd;
5c2d4cba
IA
256 struct apci2032_int_private *subpriv = s->private;
257 unsigned char enabled_isns;
258 unsigned int n;
259 unsigned long flags;
215caceb 260 bool do_event;
5c2d4cba
IA
261
262 enabled_isns = 0;
263 for (n = 0; n < cmd->chanlist_len; n++)
264 enabled_isns |= 1 << CR_CHAN(cmd->chanlist[n]);
05fcdced 265
5c2d4cba 266 spin_lock_irqsave(&subpriv->spinlock, flags);
215caceb 267 do_event = apci2032_int_start(dev, s, enabled_isns);
5c2d4cba 268 spin_unlock_irqrestore(&subpriv->spinlock, flags);
05fcdced 269
215caceb
IA
270 if (do_event)
271 comedi_event(dev, s);
272
05fcdced 273 return 0;
d02178b7 274}
7180eb30 275
05fcdced
HS
276static int apci2032_int_cancel(struct comedi_device *dev,
277 struct comedi_subdevice *s)
d02178b7 278{
5c2d4cba
IA
279 struct apci2032_int_private *subpriv = s->private;
280 unsigned long flags;
281
282 spin_lock_irqsave(&subpriv->spinlock, flags);
283 if (subpriv->active)
284 apci2032_int_stop(dev, s);
285 spin_unlock_irqrestore(&subpriv->spinlock, flags);
d02178b7 286
05fcdced 287 return 0;
d02178b7 288}
317285d7 289
05fcdced 290static irqreturn_t apci2032_interrupt(int irq, void *d)
25adf2cc 291{
05fcdced
HS
292 struct comedi_device *dev = d;
293 struct comedi_subdevice *s = dev->read_subdev;
5c2d4cba 294 struct apci2032_int_private *subpriv;
05fcdced 295 unsigned int val;
5c2d4cba 296 bool do_event = false;
05fcdced 297
0774c2b5
IA
298 if (!dev->attached)
299 return IRQ_NONE;
300
05fcdced
HS
301 /* Check if VCC OR CC interrupt has occurred */
302 val = inl(dev->iobase + APCI2032_STATUS_REG) & APCI2032_STATUS_IRQ;
303 if (!val)
304 return IRQ_NONE;
305
5c2d4cba
IA
306 subpriv = s->private;
307 spin_lock(&subpriv->spinlock);
308
b652bd83 309 val = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
ef6543db
IA
310 /* Disable triggered interrupt sources. */
311 outl(~val & 3, dev->iobase + APCI2032_INT_CTRL_REG);
312 /*
313 * Note: We don't reenable the triggered interrupt sources because they
314 * are level-sensitive, hardware error status interrupt sources and
315 * they'd keep triggering interrupts repeatedly.
316 */
05fcdced 317
5c2d4cba
IA
318 if (subpriv->active && (val & subpriv->enabled_isns) != 0) {
319 unsigned short bits;
320 unsigned int n, len;
321 unsigned int *chanlist;
322
323 /* Bits in scan data correspond to indices in channel list. */
324 bits = 0;
325 len = s->async->cmd.chanlist_len;
326 chanlist = &s->async->cmd.chanlist[0];
327 for (n = 0; n < len; n++)
328 if ((val & (1U << CR_CHAN(chanlist[n]))) != 0)
329 bits |= 1U << n;
330
331 if (comedi_buf_put(s->async, bits)) {
332 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
215caceb
IA
333 if (s->async->cmd.stop_src == TRIG_COUNT &&
334 subpriv->stop_count > 0) {
335 subpriv->stop_count--;
336 if (subpriv->stop_count == 0) {
337 /* end of acquisition */
338 s->async->events |= COMEDI_CB_EOA;
339 apci2032_int_stop(dev, s);
340 }
341 }
5c2d4cba
IA
342 } else {
343 apci2032_int_stop(dev, s);
344 s->async->events |= COMEDI_CB_OVERFLOW;
345 }
346 do_event = true;
347 }
348
349 spin_unlock(&subpriv->spinlock);
350 if (do_event)
351 comedi_event(dev, s);
05fcdced
HS
352
353 return IRQ_HANDLED;
25adf2cc
HS
354}
355
791c9792 356static int apci2032_reset(struct comedi_device *dev)
25adf2cc 357{
7180eb30
HS
358 outl(0x0, dev->iobase + APCI2032_DO_REG);
359 outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
360 outl(0x0, dev->iobase + APCI2032_WDOG_CTRL_REG);
361 outl(0x0, dev->iobase + APCI2032_WDOG_RELOAD_REG);
25adf2cc 362
25adf2cc
HS
363 return 0;
364}
365
25adf2cc
HS
366static int apci2032_auto_attach(struct comedi_device *dev,
367 unsigned long context_unused)
368{
369 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
23fb1747 370 struct apci2032_private *devpriv;
25adf2cc 371 struct comedi_subdevice *s;
0c33bdd0 372 int ret;
25adf2cc 373
c0c3c7df 374 dev->board_name = dev->driver->driver_name;
25adf2cc 375
23fb1747
HS
376 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
377 if (!devpriv)
378 return -ENOMEM;
379 dev->private = devpriv;
380
25adf2cc
HS
381 ret = comedi_pci_enable(pcidev, dev->board_name);
382 if (ret)
383 return ret;
70ff4065 384 dev->iobase = pci_resource_start(pcidev, 1);
0774c2b5 385 apci2032_reset(dev);
25adf2cc 386
25adf2cc 387 if (pcidev->irq > 0) {
05fcdced
HS
388 ret = request_irq(pcidev->irq, apci2032_interrupt,
389 IRQF_SHARED, dev->board_name, dev);
25adf2cc
HS
390 if (ret == 0)
391 dev->irq = pcidev->irq;
392 }
393
05fcdced 394 ret = comedi_alloc_subdevices(dev, 3);
25adf2cc
HS
395 if (ret)
396 return ret;
397
0c33bdd0 398 /* Initialize the digital output subdevice */
25adf2cc 399 s = &dev->subdevices[0];
cf110882
HS
400 s->type = COMEDI_SUBD_DO;
401 s->subdev_flags = SDF_WRITEABLE;
402 s->n_chan = 32;
403 s->maxdata = 1;
404 s->range_table = &range_digital;
cf110882 405 s->insn_bits = apci2032_do_insn_bits;
25adf2cc 406
0c33bdd0
HS
407 /* Initialize the watchdog subdevice */
408 s = &dev->subdevices[1];
cf110882
HS
409 s->type = COMEDI_SUBD_TIMER;
410 s->subdev_flags = SDF_WRITEABLE;
411 s->n_chan = 1;
412 s->maxdata = 0xff;
cf110882
HS
413 s->insn_write = apci2032_wdog_insn_write;
414 s->insn_read = apci2032_wdog_insn_read;
415 s->insn_config = apci2032_wdog_insn_config;
25adf2cc 416
05fcdced
HS
417 /* Initialize the interrupt subdevice */
418 s = &dev->subdevices[2];
5f6c2a95
IA
419 s->type = COMEDI_SUBD_DI;
420 s->subdev_flags = SDF_READABLE;
421 s->n_chan = 2;
422 s->maxdata = 1;
423 s->range_table = &range_digital;
424 s->insn_bits = apci2032_int_insn_bits;
05fcdced 425 if (dev->irq) {
5c2d4cba
IA
426 struct apci2032_int_private *subpriv;
427
05fcdced 428 dev->read_subdev = s;
5c2d4cba
IA
429 subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
430 if (!subpriv)
431 return -ENOMEM;
432 spin_lock_init(&subpriv->spinlock);
433 s->private = subpriv;
b82fe57c 434 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
5c2d4cba 435 s->len_chanlist = 2;
05fcdced
HS
436 s->do_cmdtest = apci2032_int_cmdtest;
437 s->do_cmd = apci2032_int_cmd;
438 s->cancel = apci2032_int_cancel;
05fcdced
HS
439 }
440
25adf2cc
HS
441 return 0;
442}
443
444static void apci2032_detach(struct comedi_device *dev)
445{
446 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
25adf2cc 447
dce10abc
HS
448 if (dev->iobase)
449 apci2032_reset(dev);
450 if (dev->irq)
451 free_irq(dev->irq, dev);
5c2d4cba
IA
452 if (dev->read_subdev)
453 kfree(dev->read_subdev->private);
25adf2cc
HS
454 if (pcidev) {
455 if (dev->iobase)
456 comedi_pci_disable(pcidev);
457 }
458}
459
20a22b70
HS
460static struct comedi_driver apci2032_driver = {
461 .driver_name = "addi_apci_2032",
462 .module = THIS_MODULE,
25adf2cc
HS
463 .auto_attach = apci2032_auto_attach,
464 .detach = apci2032_detach,
20a22b70
HS
465};
466
a690b7e5 467static int apci2032_pci_probe(struct pci_dev *dev,
20a22b70
HS
468 const struct pci_device_id *ent)
469{
470 return comedi_pci_auto_config(dev, &apci2032_driver);
471}
472
53b80019 473static void apci2032_pci_remove(struct pci_dev *dev)
20a22b70
HS
474{
475 comedi_pci_auto_unconfig(dev);
476}
477
478static DEFINE_PCI_DEVICE_TABLE(apci2032_pci_table) = {
317285d7
HS
479 { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1004) },
480 { 0 }
481};
20a22b70 482MODULE_DEVICE_TABLE(pci, apci2032_pci_table);
317285d7 483
20a22b70
HS
484static struct pci_driver apci2032_pci_driver = {
485 .name = "addi_apci_2032",
486 .id_table = apci2032_pci_table,
487 .probe = apci2032_pci_probe,
a471eace 488 .remove = apci2032_pci_remove,
20a22b70
HS
489};
490module_comedi_pci_driver(apci2032_driver, apci2032_pci_driver);
90f703d3
AT
491
492MODULE_AUTHOR("Comedi http://www.comedi.org");
493MODULE_DESCRIPTION("Comedi low-level driver");
494MODULE_LICENSE("GPL");