Skip to content

Commit 1e15687

Browse files
csouth3gregkh
authored andcommitted
staging: comedi: addi_apci_1564: add Change-of-State interrupt subdevice and required functions
This board supports an interrupt that can be generated by an AND/OR combination of 16 of the input channels. Create a separate subdevice to handle this interrupt. The apci1564_di_config() function is used to configure which inputs are used to generate the interrupt. Currently this function is broken since it does not follow the comedi API for insn_config functions. Fix this function by implementing the config instruction INSN_CONFIG_DIGITAL_TRIG. Add the remaining subdevice operations necessary for the interrupt subdevice to support async commands. Signed-off-by: Chase Southwood <[email protected]> Reviewed-by: Ian Abbott <[email protected]> Cc: H Hartley Sweeten <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 4c95a2b commit 1e15687

File tree

2 files changed

+232
-62
lines changed

2 files changed

+232
-62
lines changed

drivers/staging/comedi/drivers/addi-data/hwdrv_apci1564.c

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
#define APCI1564_ADDRESS_RANGE 128
2727

2828
/* Digital Input IRQ Function Selection */
29-
#define ADDIDATA_OR 0
30-
#define ADDIDATA_AND 1
29+
#define APCI1564_DI_INT_OR (0 << 1)
30+
#define APCI1564_DI_INT_AND (1 << 1)
3131

3232
/* Digital Input Interrupt Enable Disable. */
33-
#define APCI1564_DIGITAL_IP_INTERRUPT_ENABLE 0x4
34-
#define APCI1564_DIGITAL_IP_INTERRUPT_DISABLE 0xfffffffb
33+
#define APCI1564_DI_INT_ENABLE 0x4
34+
#define APCI1564_DI_INT_DISABLE 0xfffffffb
3535

3636
/* Digital Output Interrupt Enable Disable. */
3737
#define APCI1564_DIGITAL_OP_VCC_INTERRUPT_ENABLE 0x1
@@ -89,42 +89,6 @@
8989
#define APCI1564_TCW_WARN_TIMEVAL_REG(x) (0x18 + ((x) * 0x20))
9090
#define APCI1564_TCW_WARN_TIMEBASE_REG(x) (0x1c + ((x) * 0x20))
9191

92-
/*
93-
* Configures the digital input Subdevice
94-
*
95-
* data[0] 1 = Enable interrupt, 0 = Disable interrupt
96-
* data[1] 0 = ADDIDATA Interrupt OR LOGIC, 1 = ADDIDATA Interrupt AND LOGIC
97-
* data[2] Interrupt mask for the mode 1
98-
* data[3] Interrupt mask for the mode 2
99-
*/
100-
static int apci1564_di_config(struct comedi_device *dev,
101-
struct comedi_subdevice *s,
102-
struct comedi_insn *insn,
103-
unsigned int *data)
104-
{
105-
struct apci1564_private *devpriv = dev->private;
106-
107-
devpriv->tsk_current = current;
108-
109-
/* Set the digital input logic */
110-
if (data[0] == 1) {
111-
data[2] = data[2] << 4;
112-
data[3] = data[3] << 4;
113-
outl(data[2], devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
114-
outl(data[3], devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
115-
if (data[1] == ADDIDATA_OR)
116-
outl(0x4, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
117-
else
118-
outl(0x6, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
119-
} else {
120-
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
121-
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
122-
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
123-
}
124-
125-
return insn->n;
126-
}
127-
12892
/*
12993
* Configures The Digital Output Subdevice.
13094
*

drivers/staging/comedi/drivers/addi_apci_1564.c

Lines changed: 228 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
struct apci1564_private {
1010
unsigned int amcc_iobase; /* base of AMCC I/O registers */
11+
unsigned int mode1; /* riding-edge/high level channels */
12+
unsigned int mode2; /* falling-edge/low level channels */
13+
unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */
1114
unsigned int do_int_type;
1215
unsigned char timer_select_mode;
1316
unsigned char mode_select_register;
@@ -16,6 +19,38 @@ struct apci1564_private {
1619

1720
#include "addi-data/hwdrv_apci1564.c"
1821

22+
static int apci1564_reset(struct comedi_device *dev)
23+
{
24+
struct apci1564_private *devpriv = dev->private;
25+
26+
devpriv->do_int_type = 0;
27+
28+
/* Disable the input interrupts and reset status register */
29+
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
30+
inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
31+
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
32+
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
33+
34+
/* Reset the output channels and disable interrupts */
35+
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_REG);
36+
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_INT_CTRL_REG);
37+
38+
/* Reset the watchdog registers */
39+
addi_watchdog_reset(devpriv->amcc_iobase + APCI1564_WDOG_REG);
40+
41+
/* Reset the timer registers */
42+
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
43+
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_RELOAD_REG);
44+
45+
/* Reset the counter registers */
46+
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
47+
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
48+
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
49+
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
50+
51+
return 0;
52+
}
53+
1954
static irqreturn_t v_ADDI_Interrupt(int irq, void *d)
2055
{
2156
apci1564_interrupt(irq, d);
@@ -51,34 +86,187 @@ static int apci1564_do_insn_bits(struct comedi_device *dev,
5186
return insn->n;
5287
}
5388

54-
static int apci1564_reset(struct comedi_device *dev)
89+
/*
90+
* Change-Of-State (COS) interrupt configuration
91+
*
92+
* Channels 0 to 15 are interruptible. These channels can be configured
93+
* to generate interrupts based on AND/OR logic for the desired channels.
94+
*
95+
* OR logic
96+
* - reacts to rising or falling edges
97+
* - interrupt is generated when any enabled channel
98+
* meet the desired interrupt condition
99+
*
100+
* AND logic
101+
* - reacts to changes in level of the selected inputs
102+
* - interrupt is generated when all enabled channels
103+
* meet the desired interrupt condition
104+
* - after an interrupt, a change in level must occur on
105+
* the selected inputs to release the IRQ logic
106+
*
107+
* The COS interrupt must be configured before it can be enabled.
108+
*
109+
* data[0] : INSN_CONFIG_DIGITAL_TRIG
110+
* data[1] : trigger number (= 0)
111+
* data[2] : configuration operation:
112+
* COMEDI_DIGITAL_TRIG_DISABLE = no interrupts
113+
* COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts
114+
* COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts
115+
* data[3] : left-shift for data[4] and data[5]
116+
* data[4] : rising-edge/high level channels
117+
* data[5] : falling-edge/low level channels
118+
*/
119+
static int apci1564_cos_insn_config(struct comedi_device *dev,
120+
struct comedi_subdevice *s,
121+
struct comedi_insn *insn,
122+
unsigned int *data)
55123
{
56124
struct apci1564_private *devpriv = dev->private;
125+
unsigned int shift, oldmask;
126+
127+
switch (data[0]) {
128+
case INSN_CONFIG_DIGITAL_TRIG:
129+
if (data[1] != 0)
130+
return -EINVAL;
131+
shift = data[3];
132+
oldmask = (1U << shift) - 1;
133+
switch (data[2]) {
134+
case COMEDI_DIGITAL_TRIG_DISABLE:
135+
devpriv->ctrl = 0;
136+
devpriv->mode1 = 0;
137+
devpriv->mode2 = 0;
138+
apci1564_reset(dev);
139+
break;
140+
case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
141+
if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
142+
APCI1564_DI_INT_OR)) {
143+
/* switching to 'OR' mode */
144+
devpriv->ctrl = APCI1564_DI_INT_ENABLE |
145+
APCI1564_DI_INT_OR;
146+
/* wipe old channels */
147+
devpriv->mode1 = 0;
148+
devpriv->mode2 = 0;
149+
} else {
150+
/* preserve unspecified channels */
151+
devpriv->mode1 &= oldmask;
152+
devpriv->mode2 &= oldmask;
153+
}
154+
/* configure specified channels */
155+
devpriv->mode1 |= data[4] << shift;
156+
devpriv->mode2 |= data[5] << shift;
157+
break;
158+
case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
159+
if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
160+
APCI1564_DI_INT_AND)) {
161+
/* switching to 'AND' mode */
162+
devpriv->ctrl = APCI1564_DI_INT_ENABLE |
163+
APCI1564_DI_INT_AND;
164+
/* wipe old channels */
165+
devpriv->mode1 = 0;
166+
devpriv->mode2 = 0;
167+
} else {
168+
/* preserve unspecified channels */
169+
devpriv->mode1 &= oldmask;
170+
devpriv->mode2 &= oldmask;
171+
}
172+
/* configure specified channels */
173+
devpriv->mode1 |= data[4] << shift;
174+
devpriv->mode2 |= data[5] << shift;
175+
break;
176+
default:
177+
return -EINVAL;
178+
}
179+
break;
180+
default:
181+
return -EINVAL;
182+
}
183+
return insn->n;
184+
}
57185

58-
devpriv->do_int_type = 0;
186+
static int apci1564_cos_insn_bits(struct comedi_device *dev,
187+
struct comedi_subdevice *s,
188+
struct comedi_insn *insn,
189+
unsigned int *data)
190+
{
191+
data[1] = s->state;
59192

60-
/* Disable the input interrupts and reset status register */
61-
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
62-
inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
63-
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
64-
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
193+
return 0;
194+
}
65195

66-
/* Reset the output channels and disable interrupts */
67-
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_REG);
68-
outl(0x0, devpriv->amcc_iobase + APCI1564_DO_INT_CTRL_REG);
196+
static int apci1564_cos_cmdtest(struct comedi_device *dev,
197+
struct comedi_subdevice *s,
198+
struct comedi_cmd *cmd)
199+
{
200+
int err = 0;
69201

70-
/* Reset the watchdog registers */
71-
addi_watchdog_reset(devpriv->amcc_iobase + APCI1564_WDOG_REG);
202+
/* Step 1 : check if triggers are trivially valid */
72203

73-
/* Reset the timer registers */
74-
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
75-
outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_RELOAD_REG);
204+
err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
205+
err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
206+
err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
207+
err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
208+
err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
76209

77-
/* Reset the counter registers */
78-
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
79-
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
80-
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
81-
outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
210+
if (err)
211+
return 1;
212+
213+
/* Step 2a : make sure trigger sources are unique */
214+
/* Step 2b : and mutually compatible */
215+
216+
if (err)
217+
return 2;
218+
219+
/* Step 3: check if arguments are trivially valid */
220+
221+
err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
222+
err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
223+
err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
224+
err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
225+
err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
226+
227+
if (err)
228+
return 3;
229+
230+
/* step 4: ignored */
231+
232+
if (err)
233+
return 4;
234+
235+
return 0;
236+
}
237+
238+
/*
239+
* Change-Of-State (COS) 'do_cmd' operation
240+
*
241+
* Enable the COS interrupt as configured by apci1564_cos_insn_config().
242+
*/
243+
static int apci1564_cos_cmd(struct comedi_device *dev,
244+
struct comedi_subdevice *s)
245+
{
246+
struct apci1564_private *devpriv = dev->private;
247+
248+
if (!devpriv->ctrl) {
249+
dev_warn(dev->class_dev,
250+
"Interrupts disabled due to mode configuration!\n");
251+
return -EINVAL;
252+
}
253+
254+
outl(devpriv->mode1, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
255+
outl(devpriv->mode2, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
256+
outl(devpriv->ctrl, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
257+
258+
return 0;
259+
}
260+
261+
static int apci1564_cos_cancel(struct comedi_device *dev,
262+
struct comedi_subdevice *s)
263+
{
264+
struct apci1564_private *devpriv = dev->private;
265+
266+
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
267+
inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
268+
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
269+
outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
82270

83271
return 0;
84272
}
@@ -113,7 +301,7 @@ static int apci1564_auto_attach(struct comedi_device *dev,
113301
dev->irq = pcidev->irq;
114302
}
115303

116-
ret = comedi_alloc_subdevices(dev, 3);
304+
ret = comedi_alloc_subdevices(dev, 4);
117305
if (ret)
118306
return ret;
119307

@@ -125,7 +313,6 @@ static int apci1564_auto_attach(struct comedi_device *dev,
125313
s->maxdata = 1;
126314
s->len_chanlist = 32;
127315
s->range_table = &range_digital;
128-
s->insn_config = apci1564_di_config;
129316
s->insn_bits = apci1564_di_insn_bits;
130317

131318
/* Allocate and Initialise DO Subdevice Structures */
@@ -152,6 +339,25 @@ static int apci1564_auto_attach(struct comedi_device *dev,
152339
s->insn_read = apci1564_timer_read;
153340
s->insn_config = apci1564_timer_config;
154341

342+
/* Change-Of-State (COS) interrupt subdevice */
343+
s = &dev->subdevices[3];
344+
if (dev->irq) {
345+
dev->read_subdev = s;
346+
s->type = COMEDI_SUBD_DI;
347+
s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
348+
s->n_chan = 1;
349+
s->maxdata = 1;
350+
s->range_table = &range_digital;
351+
s->len_chanlist = 1;
352+
s->insn_config = apci1564_cos_insn_config;
353+
s->insn_bits = apci1564_cos_insn_bits;
354+
s->do_cmdtest = apci1564_cos_cmdtest;
355+
s->do_cmd = apci1564_cos_cmd;
356+
s->cancel = apci1564_cos_cancel;
357+
} else {
358+
s->type = COMEDI_SUBD_UNUSED;
359+
}
360+
155361
return 0;
156362
}
157363

0 commit comments

Comments
 (0)