Commit | Line | Data |
---|---|---|
f1d8a071 WBG |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Counter driver for the ACCES 104-QUAD-8 | |
4 | * Copyright (C) 2016 William Breathitt Gray | |
5 | * | |
6 | * This driver supports the ACCES 104-QUAD-8 and ACCES 104-QUAD-4. | |
7 | */ | |
8 | #include <linux/bitops.h> | |
9 | #include <linux/counter.h> | |
10 | #include <linux/device.h> | |
11 | #include <linux/errno.h> | |
12 | #include <linux/iio/iio.h> | |
13 | #include <linux/iio/types.h> | |
14 | #include <linux/io.h> | |
15 | #include <linux/ioport.h> | |
16 | #include <linux/isa.h> | |
17 | #include <linux/kernel.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/moduleparam.h> | |
20 | #include <linux/types.h> | |
21 | ||
22 | #define QUAD8_EXTENT 32 | |
23 | ||
24 | static unsigned int base[max_num_isa_dev(QUAD8_EXTENT)]; | |
25 | static unsigned int num_quad8; | |
26 | module_param_array(base, uint, &num_quad8, 0); | |
27 | MODULE_PARM_DESC(base, "ACCES 104-QUAD-8 base addresses"); | |
28 | ||
29 | #define QUAD8_NUM_COUNTERS 8 | |
30 | ||
31 | /** | |
32 | * struct quad8_iio - IIO device private data structure | |
33 | * @counter: instance of the counter_device | |
954ab5cc | 34 | * @fck_prescaler: array of filter clock prescaler configurations |
f1d8a071 WBG |
35 | * @preset: array of preset values |
36 | * @count_mode: array of count mode configurations | |
37 | * @quadrature_mode: array of quadrature mode configurations | |
38 | * @quadrature_scale: array of quadrature mode scale configurations | |
39 | * @ab_enable: array of A and B inputs enable configurations | |
40 | * @preset_enable: array of set_to_preset_on_index attribute configurations | |
41 | * @synchronous_mode: array of index function synchronous mode configurations | |
42 | * @index_polarity: array of index function polarity configurations | |
954ab5cc | 43 | * @cable_fault_enable: differential encoder cable status enable configurations |
f1d8a071 WBG |
44 | * @base: base port address of the IIO device |
45 | */ | |
46 | struct quad8_iio { | |
47 | struct counter_device counter; | |
de65d055 | 48 | unsigned int fck_prescaler[QUAD8_NUM_COUNTERS]; |
f1d8a071 WBG |
49 | unsigned int preset[QUAD8_NUM_COUNTERS]; |
50 | unsigned int count_mode[QUAD8_NUM_COUNTERS]; | |
51 | unsigned int quadrature_mode[QUAD8_NUM_COUNTERS]; | |
52 | unsigned int quadrature_scale[QUAD8_NUM_COUNTERS]; | |
53 | unsigned int ab_enable[QUAD8_NUM_COUNTERS]; | |
54 | unsigned int preset_enable[QUAD8_NUM_COUNTERS]; | |
55 | unsigned int synchronous_mode[QUAD8_NUM_COUNTERS]; | |
56 | unsigned int index_polarity[QUAD8_NUM_COUNTERS]; | |
954ab5cc | 57 | unsigned int cable_fault_enable; |
f1d8a071 WBG |
58 | unsigned int base; |
59 | }; | |
60 | ||
61 | #define QUAD8_REG_CHAN_OP 0x11 | |
62 | #define QUAD8_REG_INDEX_INPUT_LEVELS 0x16 | |
954ab5cc | 63 | #define QUAD8_DIFF_ENCODER_CABLE_STATUS 0x17 |
f1d8a071 WBG |
64 | /* Borrow Toggle flip-flop */ |
65 | #define QUAD8_FLAG_BT BIT(0) | |
66 | /* Carry Toggle flip-flop */ | |
67 | #define QUAD8_FLAG_CT BIT(1) | |
68 | /* Error flag */ | |
69 | #define QUAD8_FLAG_E BIT(4) | |
70 | /* Up/Down flag */ | |
71 | #define QUAD8_FLAG_UD BIT(5) | |
72 | /* Reset and Load Signal Decoders */ | |
73 | #define QUAD8_CTR_RLD 0x00 | |
74 | /* Counter Mode Register */ | |
75 | #define QUAD8_CTR_CMR 0x20 | |
76 | /* Input / Output Control Register */ | |
77 | #define QUAD8_CTR_IOR 0x40 | |
78 | /* Index Control Register */ | |
79 | #define QUAD8_CTR_IDR 0x60 | |
80 | /* Reset Byte Pointer (three byte data pointer) */ | |
81 | #define QUAD8_RLD_RESET_BP 0x01 | |
82 | /* Reset Counter */ | |
83 | #define QUAD8_RLD_RESET_CNTR 0x02 | |
84 | /* Reset Borrow Toggle, Carry Toggle, Compare Toggle, and Sign flags */ | |
85 | #define QUAD8_RLD_RESET_FLAGS 0x04 | |
86 | /* Reset Error flag */ | |
87 | #define QUAD8_RLD_RESET_E 0x06 | |
88 | /* Preset Register to Counter */ | |
89 | #define QUAD8_RLD_PRESET_CNTR 0x08 | |
90 | /* Transfer Counter to Output Latch */ | |
91 | #define QUAD8_RLD_CNTR_OUT 0x10 | |
de65d055 WBG |
92 | /* Transfer Preset Register LSB to FCK Prescaler */ |
93 | #define QUAD8_RLD_PRESET_PSC 0x18 | |
f1d8a071 WBG |
94 | #define QUAD8_CHAN_OP_ENABLE_COUNTERS 0x00 |
95 | #define QUAD8_CHAN_OP_RESET_COUNTERS 0x01 | |
96 | #define QUAD8_CMR_QUADRATURE_X1 0x08 | |
97 | #define QUAD8_CMR_QUADRATURE_X2 0x10 | |
98 | #define QUAD8_CMR_QUADRATURE_X4 0x18 | |
99 | ||
100 | ||
101 | static int quad8_read_raw(struct iio_dev *indio_dev, | |
102 | struct iio_chan_spec const *chan, int *val, int *val2, long mask) | |
103 | { | |
104 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
105 | const int base_offset = priv->base + 2 * chan->channel; | |
106 | unsigned int flags; | |
107 | unsigned int borrow; | |
108 | unsigned int carry; | |
109 | int i; | |
110 | ||
111 | switch (mask) { | |
112 | case IIO_CHAN_INFO_RAW: | |
113 | if (chan->type == IIO_INDEX) { | |
114 | *val = !!(inb(priv->base + QUAD8_REG_INDEX_INPUT_LEVELS) | |
115 | & BIT(chan->channel)); | |
116 | return IIO_VAL_INT; | |
117 | } | |
118 | ||
119 | flags = inb(base_offset + 1); | |
120 | borrow = flags & QUAD8_FLAG_BT; | |
121 | carry = !!(flags & QUAD8_FLAG_CT); | |
122 | ||
123 | /* Borrow XOR Carry effectively doubles count range */ | |
124 | *val = (borrow ^ carry) << 24; | |
125 | ||
126 | /* Reset Byte Pointer; transfer Counter to Output Latch */ | |
127 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_CNTR_OUT, | |
128 | base_offset + 1); | |
129 | ||
130 | for (i = 0; i < 3; i++) | |
131 | *val |= (unsigned int)inb(base_offset) << (8 * i); | |
132 | ||
133 | return IIO_VAL_INT; | |
134 | case IIO_CHAN_INFO_ENABLE: | |
135 | *val = priv->ab_enable[chan->channel]; | |
136 | return IIO_VAL_INT; | |
137 | case IIO_CHAN_INFO_SCALE: | |
138 | *val = 1; | |
139 | *val2 = priv->quadrature_scale[chan->channel]; | |
140 | return IIO_VAL_FRACTIONAL_LOG2; | |
141 | } | |
142 | ||
143 | return -EINVAL; | |
144 | } | |
145 | ||
146 | static int quad8_write_raw(struct iio_dev *indio_dev, | |
147 | struct iio_chan_spec const *chan, int val, int val2, long mask) | |
148 | { | |
149 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
150 | const int base_offset = priv->base + 2 * chan->channel; | |
151 | int i; | |
152 | unsigned int ior_cfg; | |
153 | ||
154 | switch (mask) { | |
155 | case IIO_CHAN_INFO_RAW: | |
156 | if (chan->type == IIO_INDEX) | |
157 | return -EINVAL; | |
158 | ||
159 | /* Only 24-bit values are supported */ | |
160 | if ((unsigned int)val > 0xFFFFFF) | |
161 | return -EINVAL; | |
162 | ||
163 | /* Reset Byte Pointer */ | |
164 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
165 | ||
166 | /* Counter can only be set via Preset Register */ | |
167 | for (i = 0; i < 3; i++) | |
168 | outb(val >> (8 * i), base_offset); | |
169 | ||
170 | /* Transfer Preset Register to Counter */ | |
171 | outb(QUAD8_CTR_RLD | QUAD8_RLD_PRESET_CNTR, base_offset + 1); | |
172 | ||
173 | /* Reset Byte Pointer */ | |
174 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
175 | ||
176 | /* Set Preset Register back to original value */ | |
177 | val = priv->preset[chan->channel]; | |
178 | for (i = 0; i < 3; i++) | |
179 | outb(val >> (8 * i), base_offset); | |
180 | ||
181 | /* Reset Borrow, Carry, Compare, and Sign flags */ | |
182 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); | |
183 | /* Reset Error flag */ | |
184 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); | |
185 | ||
186 | return 0; | |
187 | case IIO_CHAN_INFO_ENABLE: | |
188 | /* only boolean values accepted */ | |
189 | if (val < 0 || val > 1) | |
190 | return -EINVAL; | |
191 | ||
192 | priv->ab_enable[chan->channel] = val; | |
193 | ||
194 | ior_cfg = val | priv->preset_enable[chan->channel] << 1; | |
195 | ||
196 | /* Load I/O control configuration */ | |
197 | outb(QUAD8_CTR_IOR | ior_cfg, base_offset + 1); | |
198 | ||
199 | return 0; | |
200 | case IIO_CHAN_INFO_SCALE: | |
201 | /* Quadrature scaling only available in quadrature mode */ | |
202 | if (!priv->quadrature_mode[chan->channel] && (val2 || val != 1)) | |
203 | return -EINVAL; | |
204 | ||
205 | /* Only three gain states (1, 0.5, 0.25) */ | |
206 | if (val == 1 && !val2) | |
207 | priv->quadrature_scale[chan->channel] = 0; | |
208 | else if (!val) | |
209 | switch (val2) { | |
210 | case 500000: | |
211 | priv->quadrature_scale[chan->channel] = 1; | |
212 | break; | |
213 | case 250000: | |
214 | priv->quadrature_scale[chan->channel] = 2; | |
215 | break; | |
216 | default: | |
217 | return -EINVAL; | |
218 | } | |
219 | else | |
220 | return -EINVAL; | |
221 | ||
222 | return 0; | |
223 | } | |
224 | ||
225 | return -EINVAL; | |
226 | } | |
227 | ||
228 | static const struct iio_info quad8_info = { | |
229 | .read_raw = quad8_read_raw, | |
230 | .write_raw = quad8_write_raw | |
231 | }; | |
232 | ||
233 | static ssize_t quad8_read_preset(struct iio_dev *indio_dev, uintptr_t private, | |
234 | const struct iio_chan_spec *chan, char *buf) | |
235 | { | |
236 | const struct quad8_iio *const priv = iio_priv(indio_dev); | |
237 | ||
238 | return snprintf(buf, PAGE_SIZE, "%u\n", priv->preset[chan->channel]); | |
239 | } | |
240 | ||
241 | static ssize_t quad8_write_preset(struct iio_dev *indio_dev, uintptr_t private, | |
242 | const struct iio_chan_spec *chan, const char *buf, size_t len) | |
243 | { | |
244 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
245 | const int base_offset = priv->base + 2 * chan->channel; | |
246 | unsigned int preset; | |
247 | int ret; | |
248 | int i; | |
249 | ||
250 | ret = kstrtouint(buf, 0, &preset); | |
251 | if (ret) | |
252 | return ret; | |
253 | ||
254 | /* Only 24-bit values are supported */ | |
255 | if (preset > 0xFFFFFF) | |
256 | return -EINVAL; | |
257 | ||
258 | priv->preset[chan->channel] = preset; | |
259 | ||
260 | /* Reset Byte Pointer */ | |
261 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
262 | ||
263 | /* Set Preset Register */ | |
264 | for (i = 0; i < 3; i++) | |
265 | outb(preset >> (8 * i), base_offset); | |
266 | ||
267 | return len; | |
268 | } | |
269 | ||
270 | static ssize_t quad8_read_set_to_preset_on_index(struct iio_dev *indio_dev, | |
271 | uintptr_t private, const struct iio_chan_spec *chan, char *buf) | |
272 | { | |
273 | const struct quad8_iio *const priv = iio_priv(indio_dev); | |
274 | ||
275 | return snprintf(buf, PAGE_SIZE, "%u\n", | |
276 | !priv->preset_enable[chan->channel]); | |
277 | } | |
278 | ||
279 | static ssize_t quad8_write_set_to_preset_on_index(struct iio_dev *indio_dev, | |
280 | uintptr_t private, const struct iio_chan_spec *chan, const char *buf, | |
281 | size_t len) | |
282 | { | |
283 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
284 | const int base_offset = priv->base + 2 * chan->channel + 1; | |
285 | bool preset_enable; | |
286 | int ret; | |
287 | unsigned int ior_cfg; | |
288 | ||
289 | ret = kstrtobool(buf, &preset_enable); | |
290 | if (ret) | |
291 | return ret; | |
292 | ||
293 | /* Preset enable is active low in Input/Output Control register */ | |
294 | preset_enable = !preset_enable; | |
295 | ||
296 | priv->preset_enable[chan->channel] = preset_enable; | |
297 | ||
298 | ior_cfg = priv->ab_enable[chan->channel] | | |
299 | (unsigned int)preset_enable << 1; | |
300 | ||
301 | /* Load I/O control configuration to Input / Output Control Register */ | |
302 | outb(QUAD8_CTR_IOR | ior_cfg, base_offset); | |
303 | ||
304 | return len; | |
305 | } | |
306 | ||
307 | static const char *const quad8_noise_error_states[] = { | |
308 | "No excessive noise is present at the count inputs", | |
309 | "Excessive noise is present at the count inputs" | |
310 | }; | |
311 | ||
312 | static int quad8_get_noise_error(struct iio_dev *indio_dev, | |
313 | const struct iio_chan_spec *chan) | |
314 | { | |
315 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
316 | const int base_offset = priv->base + 2 * chan->channel + 1; | |
317 | ||
318 | return !!(inb(base_offset) & QUAD8_FLAG_E); | |
319 | } | |
320 | ||
321 | static const struct iio_enum quad8_noise_error_enum = { | |
322 | .items = quad8_noise_error_states, | |
323 | .num_items = ARRAY_SIZE(quad8_noise_error_states), | |
324 | .get = quad8_get_noise_error | |
325 | }; | |
326 | ||
327 | static const char *const quad8_count_direction_states[] = { | |
328 | "down", | |
329 | "up" | |
330 | }; | |
331 | ||
332 | static int quad8_get_count_direction(struct iio_dev *indio_dev, | |
333 | const struct iio_chan_spec *chan) | |
334 | { | |
335 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
336 | const int base_offset = priv->base + 2 * chan->channel + 1; | |
337 | ||
338 | return !!(inb(base_offset) & QUAD8_FLAG_UD); | |
339 | } | |
340 | ||
341 | static const struct iio_enum quad8_count_direction_enum = { | |
342 | .items = quad8_count_direction_states, | |
343 | .num_items = ARRAY_SIZE(quad8_count_direction_states), | |
344 | .get = quad8_get_count_direction | |
345 | }; | |
346 | ||
347 | static const char *const quad8_count_modes[] = { | |
348 | "normal", | |
349 | "range limit", | |
350 | "non-recycle", | |
351 | "modulo-n" | |
352 | }; | |
353 | ||
354 | static int quad8_set_count_mode(struct iio_dev *indio_dev, | |
355 | const struct iio_chan_spec *chan, unsigned int cnt_mode) | |
356 | { | |
357 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
358 | unsigned int mode_cfg = cnt_mode << 1; | |
359 | const int base_offset = priv->base + 2 * chan->channel + 1; | |
360 | ||
361 | priv->count_mode[chan->channel] = cnt_mode; | |
362 | ||
363 | /* Add quadrature mode configuration */ | |
364 | if (priv->quadrature_mode[chan->channel]) | |
365 | mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3; | |
366 | ||
367 | /* Load mode configuration to Counter Mode Register */ | |
368 | outb(QUAD8_CTR_CMR | mode_cfg, base_offset); | |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
373 | static int quad8_get_count_mode(struct iio_dev *indio_dev, | |
374 | const struct iio_chan_spec *chan) | |
375 | { | |
376 | const struct quad8_iio *const priv = iio_priv(indio_dev); | |
377 | ||
378 | return priv->count_mode[chan->channel]; | |
379 | } | |
380 | ||
381 | static const struct iio_enum quad8_count_mode_enum = { | |
382 | .items = quad8_count_modes, | |
383 | .num_items = ARRAY_SIZE(quad8_count_modes), | |
384 | .set = quad8_set_count_mode, | |
385 | .get = quad8_get_count_mode | |
386 | }; | |
387 | ||
388 | static const char *const quad8_synchronous_modes[] = { | |
389 | "non-synchronous", | |
390 | "synchronous" | |
391 | }; | |
392 | ||
393 | static int quad8_set_synchronous_mode(struct iio_dev *indio_dev, | |
394 | const struct iio_chan_spec *chan, unsigned int synchronous_mode) | |
395 | { | |
396 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
397 | const unsigned int idr_cfg = synchronous_mode | | |
398 | priv->index_polarity[chan->channel] << 1; | |
399 | const int base_offset = priv->base + 2 * chan->channel + 1; | |
400 | ||
401 | /* Index function must be non-synchronous in non-quadrature mode */ | |
402 | if (synchronous_mode && !priv->quadrature_mode[chan->channel]) | |
403 | return -EINVAL; | |
404 | ||
405 | priv->synchronous_mode[chan->channel] = synchronous_mode; | |
406 | ||
407 | /* Load Index Control configuration to Index Control Register */ | |
408 | outb(QUAD8_CTR_IDR | idr_cfg, base_offset); | |
409 | ||
410 | return 0; | |
411 | } | |
412 | ||
413 | static int quad8_get_synchronous_mode(struct iio_dev *indio_dev, | |
414 | const struct iio_chan_spec *chan) | |
415 | { | |
416 | const struct quad8_iio *const priv = iio_priv(indio_dev); | |
417 | ||
418 | return priv->synchronous_mode[chan->channel]; | |
419 | } | |
420 | ||
421 | static const struct iio_enum quad8_synchronous_mode_enum = { | |
422 | .items = quad8_synchronous_modes, | |
423 | .num_items = ARRAY_SIZE(quad8_synchronous_modes), | |
424 | .set = quad8_set_synchronous_mode, | |
425 | .get = quad8_get_synchronous_mode | |
426 | }; | |
427 | ||
428 | static const char *const quad8_quadrature_modes[] = { | |
429 | "non-quadrature", | |
430 | "quadrature" | |
431 | }; | |
432 | ||
433 | static int quad8_set_quadrature_mode(struct iio_dev *indio_dev, | |
434 | const struct iio_chan_spec *chan, unsigned int quadrature_mode) | |
435 | { | |
436 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
437 | unsigned int mode_cfg = priv->count_mode[chan->channel] << 1; | |
438 | const int base_offset = priv->base + 2 * chan->channel + 1; | |
439 | ||
440 | if (quadrature_mode) | |
441 | mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3; | |
442 | else { | |
443 | /* Quadrature scaling only available in quadrature mode */ | |
444 | priv->quadrature_scale[chan->channel] = 0; | |
445 | ||
446 | /* Synchronous function not supported in non-quadrature mode */ | |
447 | if (priv->synchronous_mode[chan->channel]) | |
448 | quad8_set_synchronous_mode(indio_dev, chan, 0); | |
449 | } | |
450 | ||
451 | priv->quadrature_mode[chan->channel] = quadrature_mode; | |
452 | ||
453 | /* Load mode configuration to Counter Mode Register */ | |
454 | outb(QUAD8_CTR_CMR | mode_cfg, base_offset); | |
455 | ||
456 | return 0; | |
457 | } | |
458 | ||
459 | static int quad8_get_quadrature_mode(struct iio_dev *indio_dev, | |
460 | const struct iio_chan_spec *chan) | |
461 | { | |
462 | const struct quad8_iio *const priv = iio_priv(indio_dev); | |
463 | ||
464 | return priv->quadrature_mode[chan->channel]; | |
465 | } | |
466 | ||
467 | static const struct iio_enum quad8_quadrature_mode_enum = { | |
468 | .items = quad8_quadrature_modes, | |
469 | .num_items = ARRAY_SIZE(quad8_quadrature_modes), | |
470 | .set = quad8_set_quadrature_mode, | |
471 | .get = quad8_get_quadrature_mode | |
472 | }; | |
473 | ||
474 | static const char *const quad8_index_polarity_modes[] = { | |
475 | "negative", | |
476 | "positive" | |
477 | }; | |
478 | ||
479 | static int quad8_set_index_polarity(struct iio_dev *indio_dev, | |
480 | const struct iio_chan_spec *chan, unsigned int index_polarity) | |
481 | { | |
482 | struct quad8_iio *const priv = iio_priv(indio_dev); | |
483 | const unsigned int idr_cfg = priv->synchronous_mode[chan->channel] | | |
484 | index_polarity << 1; | |
485 | const int base_offset = priv->base + 2 * chan->channel + 1; | |
486 | ||
487 | priv->index_polarity[chan->channel] = index_polarity; | |
488 | ||
489 | /* Load Index Control configuration to Index Control Register */ | |
490 | outb(QUAD8_CTR_IDR | idr_cfg, base_offset); | |
491 | ||
492 | return 0; | |
493 | } | |
494 | ||
495 | static int quad8_get_index_polarity(struct iio_dev *indio_dev, | |
496 | const struct iio_chan_spec *chan) | |
497 | { | |
498 | const struct quad8_iio *const priv = iio_priv(indio_dev); | |
499 | ||
500 | return priv->index_polarity[chan->channel]; | |
501 | } | |
502 | ||
503 | static const struct iio_enum quad8_index_polarity_enum = { | |
504 | .items = quad8_index_polarity_modes, | |
505 | .num_items = ARRAY_SIZE(quad8_index_polarity_modes), | |
506 | .set = quad8_set_index_polarity, | |
507 | .get = quad8_get_index_polarity | |
508 | }; | |
509 | ||
510 | static const struct iio_chan_spec_ext_info quad8_count_ext_info[] = { | |
511 | { | |
512 | .name = "preset", | |
513 | .shared = IIO_SEPARATE, | |
514 | .read = quad8_read_preset, | |
515 | .write = quad8_write_preset | |
516 | }, | |
517 | { | |
518 | .name = "set_to_preset_on_index", | |
519 | .shared = IIO_SEPARATE, | |
520 | .read = quad8_read_set_to_preset_on_index, | |
521 | .write = quad8_write_set_to_preset_on_index | |
522 | }, | |
523 | IIO_ENUM("noise_error", IIO_SEPARATE, &quad8_noise_error_enum), | |
524 | IIO_ENUM_AVAILABLE("noise_error", &quad8_noise_error_enum), | |
525 | IIO_ENUM("count_direction", IIO_SEPARATE, &quad8_count_direction_enum), | |
526 | IIO_ENUM_AVAILABLE("count_direction", &quad8_count_direction_enum), | |
527 | IIO_ENUM("count_mode", IIO_SEPARATE, &quad8_count_mode_enum), | |
528 | IIO_ENUM_AVAILABLE("count_mode", &quad8_count_mode_enum), | |
529 | IIO_ENUM("quadrature_mode", IIO_SEPARATE, &quad8_quadrature_mode_enum), | |
530 | IIO_ENUM_AVAILABLE("quadrature_mode", &quad8_quadrature_mode_enum), | |
531 | {} | |
532 | }; | |
533 | ||
534 | static const struct iio_chan_spec_ext_info quad8_index_ext_info[] = { | |
535 | IIO_ENUM("synchronous_mode", IIO_SEPARATE, | |
536 | &quad8_synchronous_mode_enum), | |
537 | IIO_ENUM_AVAILABLE("synchronous_mode", &quad8_synchronous_mode_enum), | |
538 | IIO_ENUM("index_polarity", IIO_SEPARATE, &quad8_index_polarity_enum), | |
539 | IIO_ENUM_AVAILABLE("index_polarity", &quad8_index_polarity_enum), | |
540 | {} | |
541 | }; | |
542 | ||
543 | #define QUAD8_COUNT_CHAN(_chan) { \ | |
544 | .type = IIO_COUNT, \ | |
545 | .channel = (_chan), \ | |
546 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ | |
547 | BIT(IIO_CHAN_INFO_ENABLE) | BIT(IIO_CHAN_INFO_SCALE), \ | |
548 | .ext_info = quad8_count_ext_info, \ | |
549 | .indexed = 1 \ | |
550 | } | |
551 | ||
552 | #define QUAD8_INDEX_CHAN(_chan) { \ | |
553 | .type = IIO_INDEX, \ | |
554 | .channel = (_chan), \ | |
555 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ | |
556 | .ext_info = quad8_index_ext_info, \ | |
557 | .indexed = 1 \ | |
558 | } | |
559 | ||
560 | static const struct iio_chan_spec quad8_channels[] = { | |
561 | QUAD8_COUNT_CHAN(0), QUAD8_INDEX_CHAN(0), | |
562 | QUAD8_COUNT_CHAN(1), QUAD8_INDEX_CHAN(1), | |
563 | QUAD8_COUNT_CHAN(2), QUAD8_INDEX_CHAN(2), | |
564 | QUAD8_COUNT_CHAN(3), QUAD8_INDEX_CHAN(3), | |
565 | QUAD8_COUNT_CHAN(4), QUAD8_INDEX_CHAN(4), | |
566 | QUAD8_COUNT_CHAN(5), QUAD8_INDEX_CHAN(5), | |
567 | QUAD8_COUNT_CHAN(6), QUAD8_INDEX_CHAN(6), | |
568 | QUAD8_COUNT_CHAN(7), QUAD8_INDEX_CHAN(7) | |
569 | }; | |
570 | ||
571 | static int quad8_signal_read(struct counter_device *counter, | |
d49e6ee2 | 572 | struct counter_signal *signal, enum counter_signal_value *val) |
f1d8a071 WBG |
573 | { |
574 | const struct quad8_iio *const priv = counter->priv; | |
575 | unsigned int state; | |
f1d8a071 WBG |
576 | |
577 | /* Only Index signal levels can be read */ | |
578 | if (signal->id < 16) | |
579 | return -EINVAL; | |
580 | ||
581 | state = inb(priv->base + QUAD8_REG_INDEX_INPUT_LEVELS) | |
582 | & BIT(signal->id - 16); | |
583 | ||
d49e6ee2 | 584 | *val = (state) ? COUNTER_SIGNAL_HIGH : COUNTER_SIGNAL_LOW; |
f1d8a071 WBG |
585 | |
586 | return 0; | |
587 | } | |
588 | ||
589 | static int quad8_count_read(struct counter_device *counter, | |
d49e6ee2 | 590 | struct counter_count *count, unsigned long *val) |
f1d8a071 WBG |
591 | { |
592 | const struct quad8_iio *const priv = counter->priv; | |
593 | const int base_offset = priv->base + 2 * count->id; | |
594 | unsigned int flags; | |
595 | unsigned int borrow; | |
596 | unsigned int carry; | |
f1d8a071 WBG |
597 | int i; |
598 | ||
599 | flags = inb(base_offset + 1); | |
600 | borrow = flags & QUAD8_FLAG_BT; | |
601 | carry = !!(flags & QUAD8_FLAG_CT); | |
602 | ||
603 | /* Borrow XOR Carry effectively doubles count range */ | |
d49e6ee2 | 604 | *val = (unsigned long)(borrow ^ carry) << 24; |
f1d8a071 WBG |
605 | |
606 | /* Reset Byte Pointer; transfer Counter to Output Latch */ | |
607 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_CNTR_OUT, | |
608 | base_offset + 1); | |
609 | ||
610 | for (i = 0; i < 3; i++) | |
d49e6ee2 | 611 | *val |= (unsigned long)inb(base_offset) << (8 * i); |
f1d8a071 WBG |
612 | |
613 | return 0; | |
614 | } | |
615 | ||
616 | static int quad8_count_write(struct counter_device *counter, | |
d49e6ee2 | 617 | struct counter_count *count, unsigned long val) |
f1d8a071 WBG |
618 | { |
619 | const struct quad8_iio *const priv = counter->priv; | |
620 | const int base_offset = priv->base + 2 * count->id; | |
f1d8a071 WBG |
621 | int i; |
622 | ||
f1d8a071 | 623 | /* Only 24-bit values are supported */ |
d49e6ee2 | 624 | if (val > 0xFFFFFF) |
f1d8a071 WBG |
625 | return -EINVAL; |
626 | ||
627 | /* Reset Byte Pointer */ | |
628 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
629 | ||
630 | /* Counter can only be set via Preset Register */ | |
631 | for (i = 0; i < 3; i++) | |
d49e6ee2 | 632 | outb(val >> (8 * i), base_offset); |
f1d8a071 WBG |
633 | |
634 | /* Transfer Preset Register to Counter */ | |
635 | outb(QUAD8_CTR_RLD | QUAD8_RLD_PRESET_CNTR, base_offset + 1); | |
636 | ||
637 | /* Reset Byte Pointer */ | |
638 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
639 | ||
640 | /* Set Preset Register back to original value */ | |
d49e6ee2 | 641 | val = priv->preset[count->id]; |
f1d8a071 | 642 | for (i = 0; i < 3; i++) |
d49e6ee2 | 643 | outb(val >> (8 * i), base_offset); |
f1d8a071 WBG |
644 | |
645 | /* Reset Borrow, Carry, Compare, and Sign flags */ | |
646 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); | |
647 | /* Reset Error flag */ | |
648 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); | |
649 | ||
650 | return 0; | |
651 | } | |
652 | ||
653 | enum quad8_count_function { | |
654 | QUAD8_COUNT_FUNCTION_PULSE_DIRECTION = 0, | |
655 | QUAD8_COUNT_FUNCTION_QUADRATURE_X1, | |
656 | QUAD8_COUNT_FUNCTION_QUADRATURE_X2, | |
657 | QUAD8_COUNT_FUNCTION_QUADRATURE_X4 | |
658 | }; | |
659 | ||
660 | static enum counter_count_function quad8_count_functions_list[] = { | |
661 | [QUAD8_COUNT_FUNCTION_PULSE_DIRECTION] = COUNTER_COUNT_FUNCTION_PULSE_DIRECTION, | |
662 | [QUAD8_COUNT_FUNCTION_QUADRATURE_X1] = COUNTER_COUNT_FUNCTION_QUADRATURE_X1_A, | |
663 | [QUAD8_COUNT_FUNCTION_QUADRATURE_X2] = COUNTER_COUNT_FUNCTION_QUADRATURE_X2_A, | |
664 | [QUAD8_COUNT_FUNCTION_QUADRATURE_X4] = COUNTER_COUNT_FUNCTION_QUADRATURE_X4 | |
665 | }; | |
666 | ||
667 | static int quad8_function_get(struct counter_device *counter, | |
668 | struct counter_count *count, size_t *function) | |
669 | { | |
670 | const struct quad8_iio *const priv = counter->priv; | |
671 | const int id = count->id; | |
672 | const unsigned int quadrature_mode = priv->quadrature_mode[id]; | |
673 | const unsigned int scale = priv->quadrature_scale[id]; | |
674 | ||
675 | if (quadrature_mode) | |
676 | switch (scale) { | |
677 | case 0: | |
678 | *function = QUAD8_COUNT_FUNCTION_QUADRATURE_X1; | |
679 | break; | |
680 | case 1: | |
681 | *function = QUAD8_COUNT_FUNCTION_QUADRATURE_X2; | |
682 | break; | |
683 | case 2: | |
684 | *function = QUAD8_COUNT_FUNCTION_QUADRATURE_X4; | |
685 | break; | |
686 | } | |
687 | else | |
688 | *function = QUAD8_COUNT_FUNCTION_PULSE_DIRECTION; | |
689 | ||
690 | return 0; | |
691 | } | |
692 | ||
693 | static int quad8_function_set(struct counter_device *counter, | |
694 | struct counter_count *count, size_t function) | |
695 | { | |
696 | struct quad8_iio *const priv = counter->priv; | |
697 | const int id = count->id; | |
698 | unsigned int *const quadrature_mode = priv->quadrature_mode + id; | |
699 | unsigned int *const scale = priv->quadrature_scale + id; | |
700 | unsigned int mode_cfg = priv->count_mode[id] << 1; | |
701 | unsigned int *const synchronous_mode = priv->synchronous_mode + id; | |
702 | const unsigned int idr_cfg = priv->index_polarity[id] << 1; | |
703 | const int base_offset = priv->base + 2 * id + 1; | |
704 | ||
705 | if (function == QUAD8_COUNT_FUNCTION_PULSE_DIRECTION) { | |
706 | *quadrature_mode = 0; | |
707 | ||
708 | /* Quadrature scaling only available in quadrature mode */ | |
709 | *scale = 0; | |
710 | ||
711 | /* Synchronous function not supported in non-quadrature mode */ | |
712 | if (*synchronous_mode) { | |
713 | *synchronous_mode = 0; | |
714 | /* Disable synchronous function mode */ | |
715 | outb(QUAD8_CTR_IDR | idr_cfg, base_offset); | |
716 | } | |
717 | } else { | |
718 | *quadrature_mode = 1; | |
719 | ||
720 | switch (function) { | |
721 | case QUAD8_COUNT_FUNCTION_QUADRATURE_X1: | |
722 | *scale = 0; | |
723 | mode_cfg |= QUAD8_CMR_QUADRATURE_X1; | |
724 | break; | |
725 | case QUAD8_COUNT_FUNCTION_QUADRATURE_X2: | |
726 | *scale = 1; | |
727 | mode_cfg |= QUAD8_CMR_QUADRATURE_X2; | |
728 | break; | |
729 | case QUAD8_COUNT_FUNCTION_QUADRATURE_X4: | |
730 | *scale = 2; | |
731 | mode_cfg |= QUAD8_CMR_QUADRATURE_X4; | |
732 | break; | |
733 | } | |
734 | } | |
735 | ||
736 | /* Load mode configuration to Counter Mode Register */ | |
737 | outb(QUAD8_CTR_CMR | mode_cfg, base_offset); | |
738 | ||
739 | return 0; | |
740 | } | |
741 | ||
742 | static void quad8_direction_get(struct counter_device *counter, | |
743 | struct counter_count *count, enum counter_count_direction *direction) | |
744 | { | |
745 | const struct quad8_iio *const priv = counter->priv; | |
746 | unsigned int ud_flag; | |
747 | const unsigned int flag_addr = priv->base + 2 * count->id + 1; | |
748 | ||
749 | /* U/D flag: nonzero = up, zero = down */ | |
750 | ud_flag = inb(flag_addr) & QUAD8_FLAG_UD; | |
751 | ||
752 | *direction = (ud_flag) ? COUNTER_COUNT_DIRECTION_FORWARD : | |
753 | COUNTER_COUNT_DIRECTION_BACKWARD; | |
754 | } | |
755 | ||
756 | enum quad8_synapse_action { | |
757 | QUAD8_SYNAPSE_ACTION_NONE = 0, | |
758 | QUAD8_SYNAPSE_ACTION_RISING_EDGE, | |
759 | QUAD8_SYNAPSE_ACTION_FALLING_EDGE, | |
760 | QUAD8_SYNAPSE_ACTION_BOTH_EDGES | |
761 | }; | |
762 | ||
763 | static enum counter_synapse_action quad8_index_actions_list[] = { | |
764 | [QUAD8_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, | |
765 | [QUAD8_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE | |
766 | }; | |
767 | ||
768 | static enum counter_synapse_action quad8_synapse_actions_list[] = { | |
769 | [QUAD8_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, | |
770 | [QUAD8_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE, | |
771 | [QUAD8_SYNAPSE_ACTION_FALLING_EDGE] = COUNTER_SYNAPSE_ACTION_FALLING_EDGE, | |
772 | [QUAD8_SYNAPSE_ACTION_BOTH_EDGES] = COUNTER_SYNAPSE_ACTION_BOTH_EDGES | |
773 | }; | |
774 | ||
775 | static int quad8_action_get(struct counter_device *counter, | |
776 | struct counter_count *count, struct counter_synapse *synapse, | |
777 | size_t *action) | |
778 | { | |
779 | struct quad8_iio *const priv = counter->priv; | |
780 | int err; | |
781 | size_t function = 0; | |
782 | const size_t signal_a_id = count->synapses[0].signal->id; | |
783 | enum counter_count_direction direction; | |
784 | ||
785 | /* Handle Index signals */ | |
786 | if (synapse->signal->id >= 16) { | |
787 | if (priv->preset_enable[count->id]) | |
788 | *action = QUAD8_SYNAPSE_ACTION_RISING_EDGE; | |
789 | else | |
790 | *action = QUAD8_SYNAPSE_ACTION_NONE; | |
791 | ||
792 | return 0; | |
793 | } | |
794 | ||
795 | err = quad8_function_get(counter, count, &function); | |
796 | if (err) | |
797 | return err; | |
798 | ||
799 | /* Default action mode */ | |
800 | *action = QUAD8_SYNAPSE_ACTION_NONE; | |
801 | ||
802 | /* Determine action mode based on current count function mode */ | |
803 | switch (function) { | |
804 | case QUAD8_COUNT_FUNCTION_PULSE_DIRECTION: | |
805 | if (synapse->signal->id == signal_a_id) | |
806 | *action = QUAD8_SYNAPSE_ACTION_RISING_EDGE; | |
807 | break; | |
808 | case QUAD8_COUNT_FUNCTION_QUADRATURE_X1: | |
809 | if (synapse->signal->id == signal_a_id) { | |
810 | quad8_direction_get(counter, count, &direction); | |
811 | ||
812 | if (direction == COUNTER_COUNT_DIRECTION_FORWARD) | |
813 | *action = QUAD8_SYNAPSE_ACTION_RISING_EDGE; | |
814 | else | |
815 | *action = QUAD8_SYNAPSE_ACTION_FALLING_EDGE; | |
816 | } | |
817 | break; | |
818 | case QUAD8_COUNT_FUNCTION_QUADRATURE_X2: | |
819 | if (synapse->signal->id == signal_a_id) | |
820 | *action = QUAD8_SYNAPSE_ACTION_BOTH_EDGES; | |
821 | break; | |
822 | case QUAD8_COUNT_FUNCTION_QUADRATURE_X4: | |
823 | *action = QUAD8_SYNAPSE_ACTION_BOTH_EDGES; | |
824 | break; | |
825 | } | |
826 | ||
827 | return 0; | |
828 | } | |
829 | ||
17aa207e | 830 | static const struct counter_ops quad8_ops = { |
f1d8a071 WBG |
831 | .signal_read = quad8_signal_read, |
832 | .count_read = quad8_count_read, | |
833 | .count_write = quad8_count_write, | |
834 | .function_get = quad8_function_get, | |
835 | .function_set = quad8_function_set, | |
836 | .action_get = quad8_action_get | |
837 | }; | |
838 | ||
839 | static int quad8_index_polarity_get(struct counter_device *counter, | |
840 | struct counter_signal *signal, size_t *index_polarity) | |
841 | { | |
842 | const struct quad8_iio *const priv = counter->priv; | |
843 | const size_t channel_id = signal->id - 16; | |
844 | ||
845 | *index_polarity = priv->index_polarity[channel_id]; | |
846 | ||
847 | return 0; | |
848 | } | |
849 | ||
850 | static int quad8_index_polarity_set(struct counter_device *counter, | |
851 | struct counter_signal *signal, size_t index_polarity) | |
852 | { | |
853 | struct quad8_iio *const priv = counter->priv; | |
854 | const size_t channel_id = signal->id - 16; | |
855 | const unsigned int idr_cfg = priv->synchronous_mode[channel_id] | | |
856 | index_polarity << 1; | |
857 | const int base_offset = priv->base + 2 * channel_id + 1; | |
858 | ||
859 | priv->index_polarity[channel_id] = index_polarity; | |
860 | ||
861 | /* Load Index Control configuration to Index Control Register */ | |
862 | outb(QUAD8_CTR_IDR | idr_cfg, base_offset); | |
863 | ||
864 | return 0; | |
865 | } | |
866 | ||
867 | static struct counter_signal_enum_ext quad8_index_pol_enum = { | |
868 | .items = quad8_index_polarity_modes, | |
869 | .num_items = ARRAY_SIZE(quad8_index_polarity_modes), | |
870 | .get = quad8_index_polarity_get, | |
871 | .set = quad8_index_polarity_set | |
872 | }; | |
873 | ||
874 | static int quad8_synchronous_mode_get(struct counter_device *counter, | |
875 | struct counter_signal *signal, size_t *synchronous_mode) | |
876 | { | |
877 | const struct quad8_iio *const priv = counter->priv; | |
878 | const size_t channel_id = signal->id - 16; | |
879 | ||
880 | *synchronous_mode = priv->synchronous_mode[channel_id]; | |
881 | ||
882 | return 0; | |
883 | } | |
884 | ||
885 | static int quad8_synchronous_mode_set(struct counter_device *counter, | |
886 | struct counter_signal *signal, size_t synchronous_mode) | |
887 | { | |
888 | struct quad8_iio *const priv = counter->priv; | |
889 | const size_t channel_id = signal->id - 16; | |
890 | const unsigned int idr_cfg = synchronous_mode | | |
891 | priv->index_polarity[channel_id] << 1; | |
892 | const int base_offset = priv->base + 2 * channel_id + 1; | |
893 | ||
894 | /* Index function must be non-synchronous in non-quadrature mode */ | |
895 | if (synchronous_mode && !priv->quadrature_mode[channel_id]) | |
896 | return -EINVAL; | |
897 | ||
898 | priv->synchronous_mode[channel_id] = synchronous_mode; | |
899 | ||
900 | /* Load Index Control configuration to Index Control Register */ | |
901 | outb(QUAD8_CTR_IDR | idr_cfg, base_offset); | |
902 | ||
903 | return 0; | |
904 | } | |
905 | ||
906 | static struct counter_signal_enum_ext quad8_syn_mode_enum = { | |
907 | .items = quad8_synchronous_modes, | |
908 | .num_items = ARRAY_SIZE(quad8_synchronous_modes), | |
909 | .get = quad8_synchronous_mode_get, | |
910 | .set = quad8_synchronous_mode_set | |
911 | }; | |
912 | ||
913 | static ssize_t quad8_count_floor_read(struct counter_device *counter, | |
914 | struct counter_count *count, void *private, char *buf) | |
915 | { | |
916 | /* Only a floor of 0 is supported */ | |
917 | return sprintf(buf, "0\n"); | |
918 | } | |
919 | ||
920 | static int quad8_count_mode_get(struct counter_device *counter, | |
921 | struct counter_count *count, size_t *cnt_mode) | |
922 | { | |
923 | const struct quad8_iio *const priv = counter->priv; | |
924 | ||
925 | /* Map 104-QUAD-8 count mode to Generic Counter count mode */ | |
926 | switch (priv->count_mode[count->id]) { | |
927 | case 0: | |
928 | *cnt_mode = COUNTER_COUNT_MODE_NORMAL; | |
929 | break; | |
930 | case 1: | |
931 | *cnt_mode = COUNTER_COUNT_MODE_RANGE_LIMIT; | |
932 | break; | |
933 | case 2: | |
934 | *cnt_mode = COUNTER_COUNT_MODE_NON_RECYCLE; | |
935 | break; | |
936 | case 3: | |
937 | *cnt_mode = COUNTER_COUNT_MODE_MODULO_N; | |
938 | break; | |
939 | } | |
940 | ||
941 | return 0; | |
942 | } | |
943 | ||
944 | static int quad8_count_mode_set(struct counter_device *counter, | |
945 | struct counter_count *count, size_t cnt_mode) | |
946 | { | |
947 | struct quad8_iio *const priv = counter->priv; | |
948 | unsigned int mode_cfg; | |
949 | const int base_offset = priv->base + 2 * count->id + 1; | |
950 | ||
951 | /* Map Generic Counter count mode to 104-QUAD-8 count mode */ | |
952 | switch (cnt_mode) { | |
953 | case COUNTER_COUNT_MODE_NORMAL: | |
954 | cnt_mode = 0; | |
955 | break; | |
956 | case COUNTER_COUNT_MODE_RANGE_LIMIT: | |
957 | cnt_mode = 1; | |
958 | break; | |
959 | case COUNTER_COUNT_MODE_NON_RECYCLE: | |
960 | cnt_mode = 2; | |
961 | break; | |
962 | case COUNTER_COUNT_MODE_MODULO_N: | |
963 | cnt_mode = 3; | |
964 | break; | |
965 | } | |
966 | ||
967 | priv->count_mode[count->id] = cnt_mode; | |
968 | ||
969 | /* Set count mode configuration value */ | |
970 | mode_cfg = cnt_mode << 1; | |
971 | ||
972 | /* Add quadrature mode configuration */ | |
973 | if (priv->quadrature_mode[count->id]) | |
974 | mode_cfg |= (priv->quadrature_scale[count->id] + 1) << 3; | |
975 | ||
976 | /* Load mode configuration to Counter Mode Register */ | |
977 | outb(QUAD8_CTR_CMR | mode_cfg, base_offset); | |
978 | ||
979 | return 0; | |
980 | } | |
981 | ||
982 | static struct counter_count_enum_ext quad8_cnt_mode_enum = { | |
983 | .items = counter_count_mode_str, | |
984 | .num_items = ARRAY_SIZE(counter_count_mode_str), | |
985 | .get = quad8_count_mode_get, | |
986 | .set = quad8_count_mode_set | |
987 | }; | |
988 | ||
989 | static ssize_t quad8_count_direction_read(struct counter_device *counter, | |
990 | struct counter_count *count, void *priv, char *buf) | |
991 | { | |
992 | enum counter_count_direction dir; | |
993 | ||
994 | quad8_direction_get(counter, count, &dir); | |
995 | ||
996 | return sprintf(buf, "%s\n", counter_count_direction_str[dir]); | |
997 | } | |
998 | ||
999 | static ssize_t quad8_count_enable_read(struct counter_device *counter, | |
1000 | struct counter_count *count, void *private, char *buf) | |
1001 | { | |
1002 | const struct quad8_iio *const priv = counter->priv; | |
1003 | ||
1004 | return sprintf(buf, "%u\n", priv->ab_enable[count->id]); | |
1005 | } | |
1006 | ||
1007 | static ssize_t quad8_count_enable_write(struct counter_device *counter, | |
1008 | struct counter_count *count, void *private, const char *buf, size_t len) | |
1009 | { | |
1010 | struct quad8_iio *const priv = counter->priv; | |
1011 | const int base_offset = priv->base + 2 * count->id; | |
1012 | int err; | |
1013 | bool ab_enable; | |
1014 | unsigned int ior_cfg; | |
1015 | ||
1016 | err = kstrtobool(buf, &ab_enable); | |
1017 | if (err) | |
1018 | return err; | |
1019 | ||
1020 | priv->ab_enable[count->id] = ab_enable; | |
1021 | ||
1022 | ior_cfg = ab_enable | priv->preset_enable[count->id] << 1; | |
1023 | ||
1024 | /* Load I/O control configuration */ | |
1025 | outb(QUAD8_CTR_IOR | ior_cfg, base_offset + 1); | |
1026 | ||
1027 | return len; | |
1028 | } | |
1029 | ||
1030 | static int quad8_error_noise_get(struct counter_device *counter, | |
1031 | struct counter_count *count, size_t *noise_error) | |
1032 | { | |
1033 | const struct quad8_iio *const priv = counter->priv; | |
1034 | const int base_offset = priv->base + 2 * count->id + 1; | |
1035 | ||
1036 | *noise_error = !!(inb(base_offset) & QUAD8_FLAG_E); | |
1037 | ||
1038 | return 0; | |
1039 | } | |
1040 | ||
1041 | static struct counter_count_enum_ext quad8_error_noise_enum = { | |
1042 | .items = quad8_noise_error_states, | |
1043 | .num_items = ARRAY_SIZE(quad8_noise_error_states), | |
1044 | .get = quad8_error_noise_get | |
1045 | }; | |
1046 | ||
1047 | static ssize_t quad8_count_preset_read(struct counter_device *counter, | |
1048 | struct counter_count *count, void *private, char *buf) | |
1049 | { | |
1050 | const struct quad8_iio *const priv = counter->priv; | |
1051 | ||
1052 | return sprintf(buf, "%u\n", priv->preset[count->id]); | |
1053 | } | |
1054 | ||
1055 | static ssize_t quad8_count_preset_write(struct counter_device *counter, | |
1056 | struct counter_count *count, void *private, const char *buf, size_t len) | |
1057 | { | |
1058 | struct quad8_iio *const priv = counter->priv; | |
1059 | const int base_offset = priv->base + 2 * count->id; | |
1060 | unsigned int preset; | |
1061 | int ret; | |
1062 | int i; | |
1063 | ||
1064 | ret = kstrtouint(buf, 0, &preset); | |
1065 | if (ret) | |
1066 | return ret; | |
1067 | ||
1068 | /* Only 24-bit values are supported */ | |
1069 | if (preset > 0xFFFFFF) | |
1070 | return -EINVAL; | |
1071 | ||
1072 | priv->preset[count->id] = preset; | |
1073 | ||
1074 | /* Reset Byte Pointer */ | |
1075 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
1076 | ||
1077 | /* Set Preset Register */ | |
1078 | for (i = 0; i < 3; i++) | |
1079 | outb(preset >> (8 * i), base_offset); | |
1080 | ||
1081 | return len; | |
1082 | } | |
1083 | ||
1084 | static ssize_t quad8_count_ceiling_read(struct counter_device *counter, | |
1085 | struct counter_count *count, void *private, char *buf) | |
1086 | { | |
1087 | const struct quad8_iio *const priv = counter->priv; | |
1088 | ||
1089 | /* Range Limit and Modulo-N count modes use preset value as ceiling */ | |
1090 | switch (priv->count_mode[count->id]) { | |
1091 | case 1: | |
1092 | case 3: | |
1093 | return quad8_count_preset_read(counter, count, private, buf); | |
1094 | } | |
1095 | ||
1096 | /* By default 0x1FFFFFF (25 bits unsigned) is maximum count */ | |
1097 | return sprintf(buf, "33554431\n"); | |
1098 | } | |
1099 | ||
1100 | static ssize_t quad8_count_ceiling_write(struct counter_device *counter, | |
1101 | struct counter_count *count, void *private, const char *buf, size_t len) | |
1102 | { | |
1103 | struct quad8_iio *const priv = counter->priv; | |
1104 | ||
1105 | /* Range Limit and Modulo-N count modes use preset value as ceiling */ | |
1106 | switch (priv->count_mode[count->id]) { | |
1107 | case 1: | |
1108 | case 3: | |
1109 | return quad8_count_preset_write(counter, count, private, buf, | |
1110 | len); | |
1111 | } | |
1112 | ||
1113 | return len; | |
1114 | } | |
1115 | ||
1116 | static ssize_t quad8_count_preset_enable_read(struct counter_device *counter, | |
1117 | struct counter_count *count, void *private, char *buf) | |
1118 | { | |
1119 | const struct quad8_iio *const priv = counter->priv; | |
1120 | ||
1121 | return sprintf(buf, "%u\n", !priv->preset_enable[count->id]); | |
1122 | } | |
1123 | ||
1124 | static ssize_t quad8_count_preset_enable_write(struct counter_device *counter, | |
1125 | struct counter_count *count, void *private, const char *buf, size_t len) | |
1126 | { | |
1127 | struct quad8_iio *const priv = counter->priv; | |
1128 | const int base_offset = priv->base + 2 * count->id + 1; | |
1129 | bool preset_enable; | |
1130 | int ret; | |
1131 | unsigned int ior_cfg; | |
1132 | ||
1133 | ret = kstrtobool(buf, &preset_enable); | |
1134 | if (ret) | |
1135 | return ret; | |
1136 | ||
1137 | /* Preset enable is active low in Input/Output Control register */ | |
1138 | preset_enable = !preset_enable; | |
1139 | ||
1140 | priv->preset_enable[count->id] = preset_enable; | |
1141 | ||
1142 | ior_cfg = priv->ab_enable[count->id] | (unsigned int)preset_enable << 1; | |
1143 | ||
1144 | /* Load I/O control configuration to Input / Output Control Register */ | |
1145 | outb(QUAD8_CTR_IOR | ior_cfg, base_offset); | |
1146 | ||
1147 | return len; | |
1148 | } | |
1149 | ||
954ab5cc WBG |
1150 | static ssize_t quad8_signal_cable_fault_read(struct counter_device *counter, |
1151 | struct counter_signal *signal, | |
1152 | void *private, char *buf) | |
1153 | { | |
1154 | const struct quad8_iio *const priv = counter->priv; | |
1155 | const size_t channel_id = signal->id / 2; | |
1156 | const bool disabled = !(priv->cable_fault_enable & BIT(channel_id)); | |
1157 | unsigned int status; | |
1158 | unsigned int fault; | |
1159 | ||
1160 | if (disabled) | |
1161 | return -EINVAL; | |
1162 | ||
1163 | /* Logic 0 = cable fault */ | |
1164 | status = inb(priv->base + QUAD8_DIFF_ENCODER_CABLE_STATUS); | |
1165 | ||
1166 | /* Mask respective channel and invert logic */ | |
1167 | fault = !(status & BIT(channel_id)); | |
1168 | ||
1169 | return sprintf(buf, "%u\n", fault); | |
1170 | } | |
1171 | ||
1172 | static ssize_t quad8_signal_cable_fault_enable_read( | |
1173 | struct counter_device *counter, struct counter_signal *signal, | |
1174 | void *private, char *buf) | |
1175 | { | |
1176 | const struct quad8_iio *const priv = counter->priv; | |
1177 | const size_t channel_id = signal->id / 2; | |
1178 | const unsigned int enb = !!(priv->cable_fault_enable & BIT(channel_id)); | |
1179 | ||
1180 | return sprintf(buf, "%u\n", enb); | |
1181 | } | |
1182 | ||
1183 | static ssize_t quad8_signal_cable_fault_enable_write( | |
1184 | struct counter_device *counter, struct counter_signal *signal, | |
1185 | void *private, const char *buf, size_t len) | |
1186 | { | |
1187 | struct quad8_iio *const priv = counter->priv; | |
1188 | const size_t channel_id = signal->id / 2; | |
1189 | bool enable; | |
1190 | int ret; | |
1191 | unsigned int cable_fault_enable; | |
1192 | ||
1193 | ret = kstrtobool(buf, &enable); | |
1194 | if (ret) | |
1195 | return ret; | |
1196 | ||
1197 | if (enable) | |
1198 | priv->cable_fault_enable |= BIT(channel_id); | |
1199 | else | |
1200 | priv->cable_fault_enable &= ~BIT(channel_id); | |
1201 | ||
1202 | /* Enable is active low in Differential Encoder Cable Status register */ | |
1203 | cable_fault_enable = ~priv->cable_fault_enable; | |
1204 | ||
1205 | outb(cable_fault_enable, priv->base + QUAD8_DIFF_ENCODER_CABLE_STATUS); | |
1206 | ||
1207 | return len; | |
1208 | } | |
1209 | ||
de65d055 WBG |
1210 | static ssize_t quad8_signal_fck_prescaler_read(struct counter_device *counter, |
1211 | struct counter_signal *signal, void *private, char *buf) | |
1212 | { | |
1213 | const struct quad8_iio *const priv = counter->priv; | |
1214 | const size_t channel_id = signal->id / 2; | |
1215 | ||
1216 | return sprintf(buf, "%u\n", priv->fck_prescaler[channel_id]); | |
1217 | } | |
1218 | ||
1219 | static ssize_t quad8_signal_fck_prescaler_write(struct counter_device *counter, | |
1220 | struct counter_signal *signal, void *private, const char *buf, | |
1221 | size_t len) | |
1222 | { | |
1223 | struct quad8_iio *const priv = counter->priv; | |
1224 | const size_t channel_id = signal->id / 2; | |
1225 | const int base_offset = priv->base + 2 * channel_id; | |
1226 | u8 prescaler; | |
1227 | int ret; | |
1228 | ||
1229 | ret = kstrtou8(buf, 0, &prescaler); | |
1230 | if (ret) | |
1231 | return ret; | |
1232 | ||
1233 | priv->fck_prescaler[channel_id] = prescaler; | |
1234 | ||
1235 | /* Reset Byte Pointer */ | |
1236 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
1237 | ||
1238 | /* Set filter clock factor */ | |
1239 | outb(prescaler, base_offset); | |
1240 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_PRESET_PSC, | |
1241 | base_offset + 1); | |
1242 | ||
1243 | return len; | |
1244 | } | |
1245 | ||
1246 | static const struct counter_signal_ext quad8_signal_ext[] = { | |
954ab5cc WBG |
1247 | { |
1248 | .name = "cable_fault", | |
1249 | .read = quad8_signal_cable_fault_read | |
1250 | }, | |
1251 | { | |
1252 | .name = "cable_fault_enable", | |
1253 | .read = quad8_signal_cable_fault_enable_read, | |
1254 | .write = quad8_signal_cable_fault_enable_write | |
1255 | }, | |
de65d055 WBG |
1256 | { |
1257 | .name = "filter_clock_prescaler", | |
1258 | .read = quad8_signal_fck_prescaler_read, | |
1259 | .write = quad8_signal_fck_prescaler_write | |
1260 | } | |
1261 | }; | |
1262 | ||
f1d8a071 WBG |
1263 | static const struct counter_signal_ext quad8_index_ext[] = { |
1264 | COUNTER_SIGNAL_ENUM("index_polarity", &quad8_index_pol_enum), | |
1265 | COUNTER_SIGNAL_ENUM_AVAILABLE("index_polarity", &quad8_index_pol_enum), | |
1266 | COUNTER_SIGNAL_ENUM("synchronous_mode", &quad8_syn_mode_enum), | |
1267 | COUNTER_SIGNAL_ENUM_AVAILABLE("synchronous_mode", &quad8_syn_mode_enum) | |
1268 | }; | |
1269 | ||
de65d055 WBG |
1270 | #define QUAD8_QUAD_SIGNAL(_id, _name) { \ |
1271 | .id = (_id), \ | |
1272 | .name = (_name), \ | |
1273 | .ext = quad8_signal_ext, \ | |
1274 | .num_ext = ARRAY_SIZE(quad8_signal_ext) \ | |
f1d8a071 WBG |
1275 | } |
1276 | ||
1277 | #define QUAD8_INDEX_SIGNAL(_id, _name) { \ | |
1278 | .id = (_id), \ | |
1279 | .name = (_name), \ | |
1280 | .ext = quad8_index_ext, \ | |
1281 | .num_ext = ARRAY_SIZE(quad8_index_ext) \ | |
1282 | } | |
1283 | ||
1284 | static struct counter_signal quad8_signals[] = { | |
1285 | QUAD8_QUAD_SIGNAL(0, "Channel 1 Quadrature A"), | |
1286 | QUAD8_QUAD_SIGNAL(1, "Channel 1 Quadrature B"), | |
1287 | QUAD8_QUAD_SIGNAL(2, "Channel 2 Quadrature A"), | |
1288 | QUAD8_QUAD_SIGNAL(3, "Channel 2 Quadrature B"), | |
1289 | QUAD8_QUAD_SIGNAL(4, "Channel 3 Quadrature A"), | |
1290 | QUAD8_QUAD_SIGNAL(5, "Channel 3 Quadrature B"), | |
1291 | QUAD8_QUAD_SIGNAL(6, "Channel 4 Quadrature A"), | |
1292 | QUAD8_QUAD_SIGNAL(7, "Channel 4 Quadrature B"), | |
1293 | QUAD8_QUAD_SIGNAL(8, "Channel 5 Quadrature A"), | |
1294 | QUAD8_QUAD_SIGNAL(9, "Channel 5 Quadrature B"), | |
1295 | QUAD8_QUAD_SIGNAL(10, "Channel 6 Quadrature A"), | |
1296 | QUAD8_QUAD_SIGNAL(11, "Channel 6 Quadrature B"), | |
1297 | QUAD8_QUAD_SIGNAL(12, "Channel 7 Quadrature A"), | |
1298 | QUAD8_QUAD_SIGNAL(13, "Channel 7 Quadrature B"), | |
1299 | QUAD8_QUAD_SIGNAL(14, "Channel 8 Quadrature A"), | |
1300 | QUAD8_QUAD_SIGNAL(15, "Channel 8 Quadrature B"), | |
1301 | QUAD8_INDEX_SIGNAL(16, "Channel 1 Index"), | |
1302 | QUAD8_INDEX_SIGNAL(17, "Channel 2 Index"), | |
1303 | QUAD8_INDEX_SIGNAL(18, "Channel 3 Index"), | |
1304 | QUAD8_INDEX_SIGNAL(19, "Channel 4 Index"), | |
1305 | QUAD8_INDEX_SIGNAL(20, "Channel 5 Index"), | |
1306 | QUAD8_INDEX_SIGNAL(21, "Channel 6 Index"), | |
1307 | QUAD8_INDEX_SIGNAL(22, "Channel 7 Index"), | |
1308 | QUAD8_INDEX_SIGNAL(23, "Channel 8 Index") | |
1309 | }; | |
1310 | ||
1311 | #define QUAD8_COUNT_SYNAPSES(_id) { \ | |
1312 | { \ | |
1313 | .actions_list = quad8_synapse_actions_list, \ | |
1314 | .num_actions = ARRAY_SIZE(quad8_synapse_actions_list), \ | |
1315 | .signal = quad8_signals + 2 * (_id) \ | |
1316 | }, \ | |
1317 | { \ | |
1318 | .actions_list = quad8_synapse_actions_list, \ | |
1319 | .num_actions = ARRAY_SIZE(quad8_synapse_actions_list), \ | |
1320 | .signal = quad8_signals + 2 * (_id) + 1 \ | |
1321 | }, \ | |
1322 | { \ | |
1323 | .actions_list = quad8_index_actions_list, \ | |
1324 | .num_actions = ARRAY_SIZE(quad8_index_actions_list), \ | |
1325 | .signal = quad8_signals + 2 * (_id) + 16 \ | |
1326 | } \ | |
1327 | } | |
1328 | ||
1329 | static struct counter_synapse quad8_count_synapses[][3] = { | |
1330 | QUAD8_COUNT_SYNAPSES(0), QUAD8_COUNT_SYNAPSES(1), | |
1331 | QUAD8_COUNT_SYNAPSES(2), QUAD8_COUNT_SYNAPSES(3), | |
1332 | QUAD8_COUNT_SYNAPSES(4), QUAD8_COUNT_SYNAPSES(5), | |
1333 | QUAD8_COUNT_SYNAPSES(6), QUAD8_COUNT_SYNAPSES(7) | |
1334 | }; | |
1335 | ||
1336 | static const struct counter_count_ext quad8_count_ext[] = { | |
1337 | { | |
1338 | .name = "ceiling", | |
1339 | .read = quad8_count_ceiling_read, | |
1340 | .write = quad8_count_ceiling_write | |
1341 | }, | |
1342 | { | |
1343 | .name = "floor", | |
1344 | .read = quad8_count_floor_read | |
1345 | }, | |
1346 | COUNTER_COUNT_ENUM("count_mode", &quad8_cnt_mode_enum), | |
1347 | COUNTER_COUNT_ENUM_AVAILABLE("count_mode", &quad8_cnt_mode_enum), | |
1348 | { | |
1349 | .name = "direction", | |
1350 | .read = quad8_count_direction_read | |
1351 | }, | |
1352 | { | |
1353 | .name = "enable", | |
1354 | .read = quad8_count_enable_read, | |
1355 | .write = quad8_count_enable_write | |
1356 | }, | |
1357 | COUNTER_COUNT_ENUM("error_noise", &quad8_error_noise_enum), | |
1358 | COUNTER_COUNT_ENUM_AVAILABLE("error_noise", &quad8_error_noise_enum), | |
1359 | { | |
1360 | .name = "preset", | |
1361 | .read = quad8_count_preset_read, | |
1362 | .write = quad8_count_preset_write | |
1363 | }, | |
1364 | { | |
1365 | .name = "preset_enable", | |
1366 | .read = quad8_count_preset_enable_read, | |
1367 | .write = quad8_count_preset_enable_write | |
1368 | } | |
1369 | }; | |
1370 | ||
1371 | #define QUAD8_COUNT(_id, _cntname) { \ | |
1372 | .id = (_id), \ | |
1373 | .name = (_cntname), \ | |
1374 | .functions_list = quad8_count_functions_list, \ | |
1375 | .num_functions = ARRAY_SIZE(quad8_count_functions_list), \ | |
1376 | .synapses = quad8_count_synapses[(_id)], \ | |
1377 | .num_synapses = 2, \ | |
1378 | .ext = quad8_count_ext, \ | |
1379 | .num_ext = ARRAY_SIZE(quad8_count_ext) \ | |
1380 | } | |
1381 | ||
1382 | static struct counter_count quad8_counts[] = { | |
1383 | QUAD8_COUNT(0, "Channel 1 Count"), | |
1384 | QUAD8_COUNT(1, "Channel 2 Count"), | |
1385 | QUAD8_COUNT(2, "Channel 3 Count"), | |
1386 | QUAD8_COUNT(3, "Channel 4 Count"), | |
1387 | QUAD8_COUNT(4, "Channel 5 Count"), | |
1388 | QUAD8_COUNT(5, "Channel 6 Count"), | |
1389 | QUAD8_COUNT(6, "Channel 7 Count"), | |
1390 | QUAD8_COUNT(7, "Channel 8 Count") | |
1391 | }; | |
1392 | ||
1393 | static int quad8_probe(struct device *dev, unsigned int id) | |
1394 | { | |
1395 | struct iio_dev *indio_dev; | |
1396 | struct quad8_iio *quad8iio; | |
1397 | int i, j; | |
1398 | unsigned int base_offset; | |
1399 | int err; | |
1400 | ||
1401 | if (!devm_request_region(dev, base[id], QUAD8_EXTENT, dev_name(dev))) { | |
1402 | dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", | |
1403 | base[id], base[id] + QUAD8_EXTENT); | |
1404 | return -EBUSY; | |
1405 | } | |
1406 | ||
1407 | /* Allocate IIO device; this also allocates driver data structure */ | |
1408 | indio_dev = devm_iio_device_alloc(dev, sizeof(*quad8iio)); | |
1409 | if (!indio_dev) | |
1410 | return -ENOMEM; | |
1411 | ||
1412 | /* Initialize IIO device */ | |
1413 | indio_dev->info = &quad8_info; | |
1414 | indio_dev->modes = INDIO_DIRECT_MODE; | |
1415 | indio_dev->num_channels = ARRAY_SIZE(quad8_channels); | |
1416 | indio_dev->channels = quad8_channels; | |
1417 | indio_dev->name = dev_name(dev); | |
1418 | indio_dev->dev.parent = dev; | |
1419 | ||
1420 | /* Initialize Counter device and driver data */ | |
1421 | quad8iio = iio_priv(indio_dev); | |
1422 | quad8iio->counter.name = dev_name(dev); | |
1423 | quad8iio->counter.parent = dev; | |
1424 | quad8iio->counter.ops = &quad8_ops; | |
1425 | quad8iio->counter.counts = quad8_counts; | |
1426 | quad8iio->counter.num_counts = ARRAY_SIZE(quad8_counts); | |
1427 | quad8iio->counter.signals = quad8_signals; | |
1428 | quad8iio->counter.num_signals = ARRAY_SIZE(quad8_signals); | |
1429 | quad8iio->counter.priv = quad8iio; | |
1430 | quad8iio->base = base[id]; | |
1431 | ||
1432 | /* Reset all counters and disable interrupt function */ | |
1433 | outb(QUAD8_CHAN_OP_RESET_COUNTERS, base[id] + QUAD8_REG_CHAN_OP); | |
1434 | /* Set initial configuration for all counters */ | |
1435 | for (i = 0; i < QUAD8_NUM_COUNTERS; i++) { | |
1436 | base_offset = base[id] + 2 * i; | |
1437 | /* Reset Byte Pointer */ | |
1438 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
de65d055 WBG |
1439 | /* Reset filter clock factor */ |
1440 | outb(0, base_offset); | |
1441 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_PRESET_PSC, | |
1442 | base_offset + 1); | |
1443 | /* Reset Byte Pointer */ | |
1444 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); | |
f1d8a071 WBG |
1445 | /* Reset Preset Register */ |
1446 | for (j = 0; j < 3; j++) | |
1447 | outb(0x00, base_offset); | |
1448 | /* Reset Borrow, Carry, Compare, and Sign flags */ | |
1449 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); | |
1450 | /* Reset Error flag */ | |
1451 | outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); | |
1452 | /* Binary encoding; Normal count; non-quadrature mode */ | |
1453 | outb(QUAD8_CTR_CMR, base_offset + 1); | |
1454 | /* Disable A and B inputs; preset on index; FLG1 as Carry */ | |
1455 | outb(QUAD8_CTR_IOR, base_offset + 1); | |
1456 | /* Disable index function; negative index polarity */ | |
1457 | outb(QUAD8_CTR_IDR, base_offset + 1); | |
1458 | } | |
954ab5cc WBG |
1459 | /* Disable Differential Encoder Cable Status for all channels */ |
1460 | outb(0xFF, base[id] + QUAD8_DIFF_ENCODER_CABLE_STATUS); | |
f1d8a071 WBG |
1461 | /* Enable all counters */ |
1462 | outb(QUAD8_CHAN_OP_ENABLE_COUNTERS, base[id] + QUAD8_REG_CHAN_OP); | |
1463 | ||
1464 | /* Register IIO device */ | |
1465 | err = devm_iio_device_register(dev, indio_dev); | |
1466 | if (err) | |
1467 | return err; | |
1468 | ||
1469 | /* Register Counter device */ | |
1470 | return devm_counter_register(dev, &quad8iio->counter); | |
1471 | } | |
1472 | ||
1473 | static struct isa_driver quad8_driver = { | |
1474 | .probe = quad8_probe, | |
1475 | .driver = { | |
1476 | .name = "104-quad-8" | |
1477 | } | |
1478 | }; | |
1479 | ||
1480 | module_isa_driver(quad8_driver, num_quad8); | |
1481 | ||
1482 | MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); | |
1483 | MODULE_DESCRIPTION("ACCES 104-QUAD-8 IIO driver"); | |
1484 | MODULE_LICENSE("GPL v2"); |