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 | |
170 | struct s526_private { |
171 | unsigned int gpct_config[4]; |
172 | unsigned short ai_ctrl; |
173 | }; |
174 | |
175 | static 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 | |
183 | static 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 | |
195 | static 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 | |
209 | static 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 | |
380 | static 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 | |
416 | static 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 | |
432 | static 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 | |
472 | static 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 | |
501 | static 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 | |
514 | static 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 | |
550 | static 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 | |
619 | static struct comedi_driver s526_driver = { |
620 | .driver_name = "s526" , |
621 | .module = THIS_MODULE, |
622 | .attach = s526_attach, |
623 | .detach = comedi_legacy_detach, |
624 | }; |
625 | module_comedi_driver(s526_driver); |
626 | |
627 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
628 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
629 | MODULE_LICENSE("GPL" ); |
630 | |