1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * s526.c
4 * Sensoray s526 Comedi driver
5 *
6 * COMEDI - Linux Control and Measurement Device Interface
7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
8 */
9
10/*
11 * Driver: s526
12 * Description: Sensoray 526 driver
13 * Devices: [Sensoray] 526 (s526)
14 * Author: Richie
15 * Everett Wang <everett.wang@everteq.com>
16 * Updated: Thu, 14 Sep. 2006
17 * Status: experimental
18 *
19 * Encoder works
20 * Analog input works
21 * Analog output works
22 * PWM output works
23 * Commands are not supported yet.
24 *
25 * Configuration Options:
26 * [0] - I/O port base address
27 */
28
29#include <linux/module.h>
30#include <linux/comedi/comedidev.h>
31
32/*
33 * Register I/O map
34 */
35#define S526_TIMER_REG 0x00
36#define S526_TIMER_LOAD(x) (((x) & 0xff) << 8)
37#define S526_TIMER_MODE ((x) << 1)
38#define S526_TIMER_MANUAL S526_TIMER_MODE(0)
39#define S526_TIMER_AUTO S526_TIMER_MODE(1)
40#define S526_TIMER_RESTART BIT(0)
41#define S526_WDOG_REG 0x02
42#define S526_WDOG_INVERTED BIT(4)
43#define S526_WDOG_ENA BIT(3)
44#define S526_WDOG_INTERVAL(x) (((x) & 0x7) << 0)
45#define S526_AO_CTRL_REG 0x04
46#define S526_AO_CTRL_RESET BIT(3)
47#define S526_AO_CTRL_CHAN(x) (((x) & 0x3) << 1)
48#define S526_AO_CTRL_START BIT(0)
49#define S526_AI_CTRL_REG 0x06
50#define S526_AI_CTRL_DELAY BIT(15)
51#define S526_AI_CTRL_CONV(x) (1 << (5 + ((x) & 0x9)))
52#define S526_AI_CTRL_READ(x) (((x) & 0xf) << 1)
53#define S526_AI_CTRL_START BIT(0)
54#define S526_AO_REG 0x08
55#define S526_AI_REG 0x08
56#define S526_DIO_CTRL_REG 0x0a
57#define S526_DIO_CTRL_DIO3_NEG BIT(15) /* irq on DIO3 neg/pos edge */
58#define S526_DIO_CTRL_DIO2_NEG BIT(14) /* irq on DIO2 neg/pos edge */
59#define S526_DIO_CTRL_DIO1_NEG BIT(13) /* irq on DIO1 neg/pos edge */
60#define S526_DIO_CTRL_DIO0_NEG BIT(12) /* irq on DIO0 neg/pos edge */
61#define S526_DIO_CTRL_GRP2_OUT BIT(11)
62#define S526_DIO_CTRL_GRP1_OUT BIT(10)
63#define S526_DIO_CTRL_GRP2_NEG BIT(8) /* irq on DIO[4-7] neg/pos edge */
64#define S526_INT_ENA_REG 0x0c
65#define S526_INT_STATUS_REG 0x0e
66#define S526_INT_DIO(x) BIT(8 + ((x) & 0x7))
67#define S526_INT_EEPROM BIT(7) /* status only */
68#define S526_INT_CNTR(x) BIT(3 + (3 - ((x) & 0x3)))
69#define S526_INT_AI BIT(2)
70#define S526_INT_AO BIT(1)
71#define S526_INT_TIMER BIT(0)
72#define S526_MISC_REG 0x10
73#define S526_MISC_LED_OFF BIT(0)
74#define S526_GPCT_LSB_REG(x) (0x12 + ((x) * 8))
75#define S526_GPCT_MSB_REG(x) (0x14 + ((x) * 8))
76#define S526_GPCT_MODE_REG(x) (0x16 + ((x) * 8))
77#define S526_GPCT_MODE_COUT_SRC(x) ((x) << 0)
78#define S526_GPCT_MODE_COUT_SRC_MASK S526_GPCT_MODE_COUT_SRC(0x1)
79#define S526_GPCT_MODE_COUT_SRC_RCAP S526_GPCT_MODE_COUT_SRC(0)
80#define S526_GPCT_MODE_COUT_SRC_RTGL S526_GPCT_MODE_COUT_SRC(1)
81#define S526_GPCT_MODE_COUT_POL(x) ((x) << 1)
82#define S526_GPCT_MODE_COUT_POL_MASK S526_GPCT_MODE_COUT_POL(0x1)
83#define S526_GPCT_MODE_COUT_POL_NORM S526_GPCT_MODE_COUT_POL(0)
84#define S526_GPCT_MODE_COUT_POL_INV S526_GPCT_MODE_COUT_POL(1)
85#define S526_GPCT_MODE_AUTOLOAD(x) ((x) << 2)
86#define S526_GPCT_MODE_AUTOLOAD_MASK S526_GPCT_MODE_AUTOLOAD(0x7)
87#define S526_GPCT_MODE_AUTOLOAD_NONE S526_GPCT_MODE_AUTOLOAD(0)
88/* these 3 bits can be OR'ed */
89#define S526_GPCT_MODE_AUTOLOAD_RO S526_GPCT_MODE_AUTOLOAD(0x1)
90#define S526_GPCT_MODE_AUTOLOAD_IXFALL S526_GPCT_MODE_AUTOLOAD(0x2)
91#define S526_GPCT_MODE_AUTOLOAD_IXRISE S526_GPCT_MODE_AUTOLOAD(0x4)
92#define S526_GPCT_MODE_HWCTEN_SRC(x) ((x) << 5)
93#define S526_GPCT_MODE_HWCTEN_SRC_MASK S526_GPCT_MODE_HWCTEN_SRC(0x3)
94#define S526_GPCT_MODE_HWCTEN_SRC_CEN S526_GPCT_MODE_HWCTEN_SRC(0)
95#define S526_GPCT_MODE_HWCTEN_SRC_IX S526_GPCT_MODE_HWCTEN_SRC(1)
96#define S526_GPCT_MODE_HWCTEN_SRC_IXRF S526_GPCT_MODE_HWCTEN_SRC(2)
97#define S526_GPCT_MODE_HWCTEN_SRC_NRCAP S526_GPCT_MODE_HWCTEN_SRC(3)
98#define S526_GPCT_MODE_CTEN_CTRL(x) ((x) << 7)
99#define S526_GPCT_MODE_CTEN_CTRL_MASK S526_GPCT_MODE_CTEN_CTRL(0x3)
100#define S526_GPCT_MODE_CTEN_CTRL_DIS S526_GPCT_MODE_CTEN_CTRL(0)
101#define S526_GPCT_MODE_CTEN_CTRL_ENA S526_GPCT_MODE_CTEN_CTRL(1)
102#define S526_GPCT_MODE_CTEN_CTRL_HW S526_GPCT_MODE_CTEN_CTRL(2)
103#define S526_GPCT_MODE_CTEN_CTRL_INVHW S526_GPCT_MODE_CTEN_CTRL(3)
104#define S526_GPCT_MODE_CLK_SRC(x) ((x) << 9)
105#define S526_GPCT_MODE_CLK_SRC_MASK S526_GPCT_MODE_CLK_SRC(0x3)
106/* if count direction control set to quadrature */
107#define S526_GPCT_MODE_CLK_SRC_QUADX1 S526_GPCT_MODE_CLK_SRC(0)
108#define S526_GPCT_MODE_CLK_SRC_QUADX2 S526_GPCT_MODE_CLK_SRC(1)
109#define S526_GPCT_MODE_CLK_SRC_QUADX4 S526_GPCT_MODE_CLK_SRC(2)
110#define S526_GPCT_MODE_CLK_SRC_QUADX4_ S526_GPCT_MODE_CLK_SRC(3)
111/* if count direction control set to software control */
112#define S526_GPCT_MODE_CLK_SRC_ARISE S526_GPCT_MODE_CLK_SRC(0)
113#define S526_GPCT_MODE_CLK_SRC_AFALL S526_GPCT_MODE_CLK_SRC(1)
114#define S526_GPCT_MODE_CLK_SRC_INT S526_GPCT_MODE_CLK_SRC(2)
115#define S526_GPCT_MODE_CLK_SRC_INTHALF S526_GPCT_MODE_CLK_SRC(3)
116#define S526_GPCT_MODE_CT_DIR(x) ((x) << 11)
117#define S526_GPCT_MODE_CT_DIR_MASK S526_GPCT_MODE_CT_DIR(0x1)
118/* if count direction control set to software control */
119#define S526_GPCT_MODE_CT_DIR_UP S526_GPCT_MODE_CT_DIR(0)
120#define S526_GPCT_MODE_CT_DIR_DOWN S526_GPCT_MODE_CT_DIR(1)
121#define S526_GPCT_MODE_CTDIR_CTRL(x) ((x) << 12)
122#define S526_GPCT_MODE_CTDIR_CTRL_MASK S526_GPCT_MODE_CTDIR_CTRL(0x1)
123#define S526_GPCT_MODE_CTDIR_CTRL_QUAD S526_GPCT_MODE_CTDIR_CTRL(0)
124#define S526_GPCT_MODE_CTDIR_CTRL_SOFT S526_GPCT_MODE_CTDIR_CTRL(1)
125#define S526_GPCT_MODE_LATCH_CTRL(x) ((x) << 13)
126#define S526_GPCT_MODE_LATCH_CTRL_MASK S526_GPCT_MODE_LATCH_CTRL(0x1)
127#define S526_GPCT_MODE_LATCH_CTRL_READ S526_GPCT_MODE_LATCH_CTRL(0)
128#define S526_GPCT_MODE_LATCH_CTRL_EVENT S526_GPCT_MODE_LATCH_CTRL(1)
129#define S526_GPCT_MODE_PR_SELECT(x) ((x) << 14)
130#define S526_GPCT_MODE_PR_SELECT_MASK S526_GPCT_MODE_PR_SELECT(0x1)
131#define S526_GPCT_MODE_PR_SELECT_PR0 S526_GPCT_MODE_PR_SELECT(0)
132#define S526_GPCT_MODE_PR_SELECT_PR1 S526_GPCT_MODE_PR_SELECT(1)
133/* Control/Status - R = readable, W = writeable, C = write 1 to clear */
134#define S526_GPCT_CTRL_REG(x) (0x18 + ((x) * 8))
135#define S526_GPCT_CTRL_EV_STATUS(x) ((x) << 0) /* RC */
136#define S526_GPCT_CTRL_EV_STATUS_MASK S526_GPCT_EV_STATUS(0xf)
137#define S526_GPCT_CTRL_EV_STATUS_NONE S526_GPCT_EV_STATUS(0)
138/* these 4 bits can be OR'ed */
139#define S526_GPCT_CTRL_EV_STATUS_ECAP S526_GPCT_EV_STATUS(0x1)
140#define S526_GPCT_CTRL_EV_STATUS_ICAPN S526_GPCT_EV_STATUS(0x2)
141#define S526_GPCT_CTRL_EV_STATUS_ICAPP S526_GPCT_EV_STATUS(0x4)
142#define S526_GPCT_CTRL_EV_STATUS_RCAP S526_GPCT_EV_STATUS(0x8)
143#define S526_GPCT_CTRL_COUT_STATUS BIT(4) /* R */
144#define S526_GPCT_CTRL_INDEX_STATUS BIT(5) /* R */
145#define S525_GPCT_CTRL_INTEN(x) ((x) << 6) /* W */
146#define S525_GPCT_CTRL_INTEN_MASK S526_GPCT_CTRL_INTEN(0xf)
147#define S525_GPCT_CTRL_INTEN_NONE S526_GPCT_CTRL_INTEN(0)
148/* these 4 bits can be OR'ed */
149#define S525_GPCT_CTRL_INTEN_ERROR S526_GPCT_CTRL_INTEN(0x1)
150#define S525_GPCT_CTRL_INTEN_IXFALL S526_GPCT_CTRL_INTEN(0x2)
151#define S525_GPCT_CTRL_INTEN_IXRISE S526_GPCT_CTRL_INTEN(0x4)
152#define S525_GPCT_CTRL_INTEN_RO S526_GPCT_CTRL_INTEN(0x8)
153#define S525_GPCT_CTRL_LATCH_SEL(x) ((x) << 10) /* W */
154#define S525_GPCT_CTRL_LATCH_SEL_MASK S526_GPCT_CTRL_LATCH_SEL(0x7)
155#define S525_GPCT_CTRL_LATCH_SEL_NONE S526_GPCT_CTRL_LATCH_SEL(0)
156/* these 3 bits can be OR'ed */
157#define S525_GPCT_CTRL_LATCH_SEL_IXFALL S526_GPCT_CTRL_LATCH_SEL(0x1)
158#define S525_GPCT_CTRL_LATCH_SEL_IXRISE S526_GPCT_CTRL_LATCH_SEL(0x2)
159#define S525_GPCT_CTRL_LATCH_SEL_ITIMER S526_GPCT_CTRL_LATCH_SEL(0x4)
160#define S525_GPCT_CTRL_CT_ARM BIT(13) /* W */
161#define S525_GPCT_CTRL_CT_LOAD BIT(14) /* W */
162#define S526_GPCT_CTRL_CT_RESET BIT(15) /* W */
163#define S526_EEPROM_DATA_REG 0x32
164#define S526_EEPROM_CTRL_REG 0x34
165#define S526_EEPROM_CTRL_ADDR(x) (((x) & 0x3f) << 3)
166#define S526_EEPROM_CTRL(x) (((x) & 0x3) << 1)
167#define S526_EEPROM_CTRL_READ S526_EEPROM_CTRL(2)
168#define S526_EEPROM_CTRL_START BIT(0)
169
170struct s526_private {
171 unsigned int gpct_config[4];
172 unsigned short ai_ctrl;
173};
174
175static void s526_gpct_write(struct comedi_device *dev,
176 unsigned int chan, unsigned int val)
177{
178 /* write high word then low word */
179 outw(value: (val >> 16) & 0xffff, port: dev->iobase + S526_GPCT_MSB_REG(chan));
180 outw(value: val & 0xffff, port: dev->iobase + S526_GPCT_LSB_REG(chan));
181}
182
183static unsigned int s526_gpct_read(struct comedi_device *dev,
184 unsigned int chan)
185{
186 unsigned int val;
187
188 /* read the low word then high word */
189 val = inw(port: dev->iobase + S526_GPCT_LSB_REG(chan)) & 0xffff;
190 val |= (inw(port: dev->iobase + S526_GPCT_MSB_REG(chan)) & 0xff) << 16;
191
192 return val;
193}
194
195static int s526_gpct_rinsn(struct comedi_device *dev,
196 struct comedi_subdevice *s,
197 struct comedi_insn *insn,
198 unsigned int *data)
199{
200 unsigned int chan = CR_CHAN(insn->chanspec);
201 int i;
202
203 for (i = 0; i < insn->n; i++)
204 data[i] = s526_gpct_read(dev, chan);
205
206 return insn->n;
207}
208
209static int s526_gpct_insn_config(struct comedi_device *dev,
210 struct comedi_subdevice *s,
211 struct comedi_insn *insn,
212 unsigned int *data)
213{
214 struct s526_private *devpriv = dev->private;
215 unsigned int chan = CR_CHAN(insn->chanspec);
216 unsigned int val;
217
218 /*
219 * Check what type of Counter the user requested
220 * data[0] contains the Application type
221 */
222 switch (data[0]) {
223 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
224 /*
225 * data[0]: Application Type
226 * data[1]: Counter Mode Register Value
227 * data[2]: Pre-load Register Value
228 * data[3]: Conter Control Register
229 */
230 devpriv->gpct_config[chan] = data[0];
231
232#if 1
233 /* Set Counter Mode Register */
234 val = data[1] & 0xffff;
235 outw(value: val, port: dev->iobase + S526_GPCT_MODE_REG(chan));
236
237 /* Reset the counter if it is software preload */
238 if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) ==
239 S526_GPCT_MODE_AUTOLOAD_NONE) {
240 /* Reset the counter */
241 outw(S526_GPCT_CTRL_CT_RESET,
242 port: dev->iobase + S526_GPCT_CTRL_REG(chan));
243 /*
244 * Load the counter from PR0
245 * outw(S526_GPCT_CTRL_CT_LOAD,
246 * dev->iobase + S526_GPCT_CTRL_REG(chan));
247 */
248 }
249#else
250 val = S526_GPCT_MODE_CTDIR_CTRL_QUAD;
251
252 /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
253 if (data[1] == GPCT_X2)
254 val |= S526_GPCT_MODE_CLK_SRC_QUADX2;
255 else if (data[1] == GPCT_X4)
256 val |= S526_GPCT_MODE_CLK_SRC_QUADX4;
257 else
258 val |= S526_GPCT_MODE_CLK_SRC_QUADX1;
259
260 /* When to take into account the indexpulse: */
261 /*
262 * if (data[2] == GPCT_IndexPhaseLowLow) {
263 * } else if (data[2] == GPCT_IndexPhaseLowHigh) {
264 * } else if (data[2] == GPCT_IndexPhaseHighLow) {
265 * } else if (data[2] == GPCT_IndexPhaseHighHigh) {
266 * }
267 */
268 /* Take into account the index pulse? */
269 if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) {
270 /* Auto load with INDEX^ */
271 val |= S526_GPCT_MODE_AUTOLOAD_IXRISE;
272 }
273
274 /* Set Counter Mode Register */
275 val = data[1] & 0xffff;
276 outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));
277
278 /* Load the pre-load register */
279 s526_gpct_write(dev, chan, data[2]);
280
281 /* Write the Counter Control Register */
282 if (data[3])
283 outw(data[3] & 0xffff,
284 dev->iobase + S526_GPCT_CTRL_REG(chan));
285
286 /* Reset the counter if it is software preload */
287 if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) ==
288 S526_GPCT_MODE_AUTOLOAD_NONE) {
289 /* Reset the counter */
290 outw(S526_GPCT_CTRL_CT_RESET,
291 dev->iobase + S526_GPCT_CTRL_REG(chan));
292 /* Load the counter from PR0 */
293 outw(S526_GPCT_CTRL_CT_LOAD,
294 dev->iobase + S526_GPCT_CTRL_REG(chan));
295 }
296#endif
297 break;
298
299 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
300 /*
301 * data[0]: Application Type
302 * data[1]: Counter Mode Register Value
303 * data[2]: Pre-load Register 0 Value
304 * data[3]: Pre-load Register 1 Value
305 * data[4]: Conter Control Register
306 */
307 devpriv->gpct_config[chan] = data[0];
308
309 /* Set Counter Mode Register */
310 val = data[1] & 0xffff;
311 /* Select PR0 */
312 val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
313 val |= S526_GPCT_MODE_PR_SELECT_PR0;
314 outw(value: val, port: dev->iobase + S526_GPCT_MODE_REG(chan));
315
316 /* Load the pre-load register 0 */
317 s526_gpct_write(dev, chan, val: data[2]);
318
319 /* Set Counter Mode Register */
320 val = data[1] & 0xffff;
321 /* Select PR1 */
322 val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
323 val |= S526_GPCT_MODE_PR_SELECT_PR1;
324 outw(value: val, port: dev->iobase + S526_GPCT_MODE_REG(chan));
325
326 /* Load the pre-load register 1 */
327 s526_gpct_write(dev, chan, val: data[3]);
328
329 /* Write the Counter Control Register */
330 if (data[4]) {
331 val = data[4] & 0xffff;
332 outw(value: val, port: dev->iobase + S526_GPCT_CTRL_REG(chan));
333 }
334 break;
335
336 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
337 /*
338 * data[0]: Application Type
339 * data[1]: Counter Mode Register Value
340 * data[2]: Pre-load Register 0 Value
341 * data[3]: Pre-load Register 1 Value
342 * data[4]: Conter Control Register
343 */
344 devpriv->gpct_config[chan] = data[0];
345
346 /* Set Counter Mode Register */
347 val = data[1] & 0xffff;
348 /* Select PR0 */
349 val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
350 val |= S526_GPCT_MODE_PR_SELECT_PR0;
351 outw(value: val, port: dev->iobase + S526_GPCT_MODE_REG(chan));
352
353 /* Load the pre-load register 0 */
354 s526_gpct_write(dev, chan, val: data[2]);
355
356 /* Set Counter Mode Register */
357 val = data[1] & 0xffff;
358 /* Select PR1 */
359 val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
360 val |= S526_GPCT_MODE_PR_SELECT_PR1;
361 outw(value: val, port: dev->iobase + S526_GPCT_MODE_REG(chan));
362
363 /* Load the pre-load register 1 */
364 s526_gpct_write(dev, chan, val: data[3]);
365
366 /* Write the Counter Control Register */
367 if (data[4]) {
368 val = data[4] & 0xffff;
369 outw(value: val, port: dev->iobase + S526_GPCT_CTRL_REG(chan));
370 }
371 break;
372
373 default:
374 return -EINVAL;
375 }
376
377 return insn->n;
378}
379
380static int s526_gpct_winsn(struct comedi_device *dev,
381 struct comedi_subdevice *s,
382 struct comedi_insn *insn,
383 unsigned int *data)
384{
385 struct s526_private *devpriv = dev->private;
386 unsigned int chan = CR_CHAN(insn->chanspec);
387
388 inw(port: dev->iobase + S526_GPCT_MODE_REG(chan)); /* Is this required? */
389
390 /* Check what Application of Counter this channel is configured for */
391 switch (devpriv->gpct_config[chan]) {
392 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
393 /*
394 * data[0] contains the PULSE_WIDTH
395 * data[1] contains the PULSE_PERIOD
396 * @pre PULSE_PERIOD > PULSE_WIDTH > 0
397 * The above periods must be expressed as a multiple of the
398 * pulse frequency on the selected source
399 */
400 if ((data[1] <= data[0]) || !data[0])
401 return -EINVAL;
402 /* to write the PULSE_WIDTH */
403 fallthrough;
404 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
405 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
406 s526_gpct_write(dev, chan, val: data[0]);
407 break;
408
409 default:
410 return -EINVAL;
411 }
412
413 return insn->n;
414}
415
416static int s526_eoc(struct comedi_device *dev,
417 struct comedi_subdevice *s,
418 struct comedi_insn *insn,
419 unsigned long context)
420{
421 unsigned int status;
422
423 status = inw(port: dev->iobase + S526_INT_STATUS_REG);
424 if (status & context) {
425 /* we got our eoc event, clear it */
426 outw(value: context, port: dev->iobase + S526_INT_STATUS_REG);
427 return 0;
428 }
429 return -EBUSY;
430}
431
432static int s526_ai_insn_read(struct comedi_device *dev,
433 struct comedi_subdevice *s,
434 struct comedi_insn *insn,
435 unsigned int *data)
436{
437 struct s526_private *devpriv = dev->private;
438 unsigned int chan = CR_CHAN(insn->chanspec);
439 unsigned int ctrl;
440 unsigned int val;
441 int ret;
442 int i;
443
444 ctrl = S526_AI_CTRL_CONV(chan) | S526_AI_CTRL_READ(chan) |
445 S526_AI_CTRL_START;
446 if (ctrl != devpriv->ai_ctrl) {
447 /*
448 * The multiplexor needs to change, enable the 15us
449 * delay for the first sample.
450 */
451 devpriv->ai_ctrl = ctrl;
452 ctrl |= S526_AI_CTRL_DELAY;
453 }
454
455 for (i = 0; i < insn->n; i++) {
456 /* trigger conversion */
457 outw(value: ctrl, port: dev->iobase + S526_AI_CTRL_REG);
458 ctrl &= ~S526_AI_CTRL_DELAY;
459
460 /* wait for conversion to end */
461 ret = comedi_timeout(dev, s, insn, cb: s526_eoc, S526_INT_AI);
462 if (ret)
463 return ret;
464
465 val = inw(port: dev->iobase + S526_AI_REG);
466 data[i] = comedi_offset_munge(s, val);
467 }
468
469 return insn->n;
470}
471
472static int s526_ao_insn_write(struct comedi_device *dev,
473 struct comedi_subdevice *s,
474 struct comedi_insn *insn,
475 unsigned int *data)
476{
477 unsigned int chan = CR_CHAN(insn->chanspec);
478 unsigned int ctrl = S526_AO_CTRL_CHAN(chan);
479 unsigned int val = s->readback[chan];
480 int ret;
481 int i;
482
483 outw(value: ctrl, port: dev->iobase + S526_AO_CTRL_REG);
484 ctrl |= S526_AO_CTRL_START;
485
486 for (i = 0; i < insn->n; i++) {
487 val = data[i];
488 outw(value: val, port: dev->iobase + S526_AO_REG);
489 outw(value: ctrl, port: dev->iobase + S526_AO_CTRL_REG);
490
491 /* wait for conversion to end */
492 ret = comedi_timeout(dev, s, insn, cb: s526_eoc, S526_INT_AO);
493 if (ret)
494 return ret;
495 }
496 s->readback[chan] = val;
497
498 return insn->n;
499}
500
501static int s526_dio_insn_bits(struct comedi_device *dev,
502 struct comedi_subdevice *s,
503 struct comedi_insn *insn,
504 unsigned int *data)
505{
506 if (comedi_dio_update_state(s, data))
507 outw(value: s->state, port: dev->iobase + S526_DIO_CTRL_REG);
508
509 data[1] = inw(port: dev->iobase + S526_DIO_CTRL_REG) & 0xff;
510
511 return insn->n;
512}
513
514static int s526_dio_insn_config(struct comedi_device *dev,
515 struct comedi_subdevice *s,
516 struct comedi_insn *insn,
517 unsigned int *data)
518{
519 unsigned int chan = CR_CHAN(insn->chanspec);
520 unsigned int mask;
521 int ret;
522
523 /*
524 * Digital I/O can be configured as inputs or outputs in
525 * groups of 4; DIO group 1 (DIO0-3) and DIO group 2 (DIO4-7).
526 */
527 if (chan < 4)
528 mask = 0x0f;
529 else
530 mask = 0xf0;
531
532 ret = comedi_dio_insn_config(dev, s, insn, data, mask);
533 if (ret)
534 return ret;
535
536 if (s->io_bits & 0x0f)
537 s->state |= S526_DIO_CTRL_GRP1_OUT;
538 else
539 s->state &= ~S526_DIO_CTRL_GRP1_OUT;
540 if (s->io_bits & 0xf0)
541 s->state |= S526_DIO_CTRL_GRP2_OUT;
542 else
543 s->state &= ~S526_DIO_CTRL_GRP2_OUT;
544
545 outw(value: s->state, port: dev->iobase + S526_DIO_CTRL_REG);
546
547 return insn->n;
548}
549
550static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
551{
552 struct s526_private *devpriv;
553 struct comedi_subdevice *s;
554 int ret;
555
556 ret = comedi_request_region(dev, start: it->options[0], len: 0x40);
557 if (ret)
558 return ret;
559
560 devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv));
561 if (!devpriv)
562 return -ENOMEM;
563
564 ret = comedi_alloc_subdevices(dev, num_subdevices: 4);
565 if (ret)
566 return ret;
567
568 /* General-Purpose Counter/Timer (GPCT) */
569 s = &dev->subdevices[0];
570 s->type = COMEDI_SUBD_COUNTER;
571 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
572 s->n_chan = 4;
573 s->maxdata = 0x00ffffff;
574 s->insn_read = s526_gpct_rinsn;
575 s->insn_config = s526_gpct_insn_config;
576 s->insn_write = s526_gpct_winsn;
577
578 /*
579 * Analog Input subdevice
580 * channels 0 to 7 are the regular differential inputs
581 * channel 8 is "reference 0" (+10V)
582 * channel 9 is "reference 1" (0V)
583 */
584 s = &dev->subdevices[1];
585 s->type = COMEDI_SUBD_AI;
586 s->subdev_flags = SDF_READABLE | SDF_DIFF;
587 s->n_chan = 10;
588 s->maxdata = 0xffff;
589 s->range_table = &range_bipolar10;
590 s->len_chanlist = 16;
591 s->insn_read = s526_ai_insn_read;
592
593 /* Analog Output subdevice */
594 s = &dev->subdevices[2];
595 s->type = COMEDI_SUBD_AO;
596 s->subdev_flags = SDF_WRITABLE;
597 s->n_chan = 4;
598 s->maxdata = 0xffff;
599 s->range_table = &range_bipolar10;
600 s->insn_write = s526_ao_insn_write;
601
602 ret = comedi_alloc_subdev_readback(s);
603 if (ret)
604 return ret;
605
606 /* Digital I/O subdevice */
607 s = &dev->subdevices[3];
608 s->type = COMEDI_SUBD_DIO;
609 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
610 s->n_chan = 8;
611 s->maxdata = 1;
612 s->range_table = &range_digital;
613 s->insn_bits = s526_dio_insn_bits;
614 s->insn_config = s526_dio_insn_config;
615
616 return 0;
617}
618
619static struct comedi_driver s526_driver = {
620 .driver_name = "s526",
621 .module = THIS_MODULE,
622 .attach = s526_attach,
623 .detach = comedi_legacy_detach,
624};
625module_comedi_driver(s526_driver);
626
627MODULE_AUTHOR("Comedi https://www.comedi.org");
628MODULE_DESCRIPTION("Comedi low-level driver");
629MODULE_LICENSE("GPL");
630

source code of linux/drivers/comedi/drivers/s526.c