1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * aio_aio12_8.c |
4 | * Driver for Access I/O Products PC-104 AIO12-8 Analog I/O Board |
5 | * Copyright (C) 2006 C&C Technologies, Inc. |
6 | */ |
7 | |
8 | /* |
9 | * Driver: aio_aio12_8 |
10 | * Description: Access I/O Products PC-104 AIO12-8 Analog I/O Board |
11 | * Author: Pablo Mejia <pablo.mejia@cctechnol.com> |
12 | * Devices: [Access I/O] PC-104 AIO12-8 (aio_aio12_8), |
13 | * [Access I/O] PC-104 AI12-8 (aio_ai12_8), |
14 | * [Access I/O] PC-104 AO12-4 (aio_ao12_4) |
15 | * Status: experimental |
16 | * |
17 | * Configuration Options: |
18 | * [0] - I/O port base address |
19 | * |
20 | * Notes: |
21 | * Only synchronous operations are supported. |
22 | */ |
23 | |
24 | #include <linux/module.h> |
25 | #include <linux/comedi/comedidev.h> |
26 | #include <linux/comedi/comedi_8255.h> |
27 | #include <linux/comedi/comedi_8254.h> |
28 | |
29 | /* |
30 | * Register map |
31 | */ |
32 | #define AIO12_8_STATUS_REG 0x00 |
33 | #define AIO12_8_STATUS_ADC_EOC BIT(7) |
34 | #define AIO12_8_STATUS_PORT_C_COS BIT(6) |
35 | #define AIO12_8_STATUS_IRQ_ENA BIT(2) |
36 | #define AIO12_8_INTERRUPT_REG 0x01 |
37 | #define AIO12_8_INTERRUPT_ADC BIT(7) |
38 | #define AIO12_8_INTERRUPT_COS BIT(6) |
39 | #define AIO12_8_INTERRUPT_COUNTER1 BIT(5) |
40 | #define AIO12_8_INTERRUPT_PORT_C3 BIT(4) |
41 | #define AIO12_8_INTERRUPT_PORT_C0 BIT(3) |
42 | #define AIO12_8_INTERRUPT_ENA BIT(2) |
43 | #define AIO12_8_ADC_REG 0x02 |
44 | #define AIO12_8_ADC_MODE(x) (((x) & 0x3) << 6) |
45 | #define AIO12_8_ADC_MODE_NORMAL AIO12_8_ADC_MODE(0) |
46 | #define AIO12_8_ADC_MODE_INT_CLK AIO12_8_ADC_MODE(1) |
47 | #define AIO12_8_ADC_MODE_STANDBY AIO12_8_ADC_MODE(2) |
48 | #define AIO12_8_ADC_MODE_POWERDOWN AIO12_8_ADC_MODE(3) |
49 | #define AIO12_8_ADC_ACQ(x) (((x) & 0x1) << 5) |
50 | #define AIO12_8_ADC_ACQ_3USEC AIO12_8_ADC_ACQ(0) |
51 | #define AIO12_8_ADC_ACQ_PROGRAM AIO12_8_ADC_ACQ(1) |
52 | #define AIO12_8_ADC_RANGE(x) ((x) << 3) |
53 | #define AIO12_8_ADC_CHAN(x) ((x) << 0) |
54 | #define AIO12_8_DAC_REG(x) (0x04 + (x) * 2) |
55 | #define AIO12_8_8254_BASE_REG 0x0c |
56 | #define AIO12_8_8255_BASE_REG 0x10 |
57 | #define AIO12_8_DIO_CONTROL_REG 0x14 |
58 | #define AIO12_8_DIO_CONTROL_TST BIT(0) |
59 | #define AIO12_8_ADC_TRIGGER_REG 0x15 |
60 | #define AIO12_8_ADC_TRIGGER_RANGE(x) ((x) << 3) |
61 | #define AIO12_8_ADC_TRIGGER_CHAN(x) ((x) << 0) |
62 | #define AIO12_8_TRIGGER_REG 0x16 |
63 | #define AIO12_8_TRIGGER_ADTRIG BIT(1) |
64 | #define AIO12_8_TRIGGER_DACTRIG BIT(0) |
65 | #define AIO12_8_COS_REG 0x17 |
66 | #define AIO12_8_DAC_ENABLE_REG 0x18 |
67 | #define AIO12_8_DAC_ENABLE_REF_ENA BIT(0) |
68 | |
69 | static const struct comedi_lrange aio_aio12_8_range = { |
70 | 4, { |
71 | UNI_RANGE(5), |
72 | BIP_RANGE(5), |
73 | UNI_RANGE(10), |
74 | BIP_RANGE(10) |
75 | } |
76 | }; |
77 | |
78 | struct aio12_8_boardtype { |
79 | const char *name; |
80 | unsigned int has_ai:1; |
81 | unsigned int has_ao:1; |
82 | }; |
83 | |
84 | static const struct aio12_8_boardtype board_types[] = { |
85 | { |
86 | .name = "aio_aio12_8" , |
87 | .has_ai = 1, |
88 | .has_ao = 1, |
89 | }, { |
90 | .name = "aio_ai12_8" , |
91 | .has_ai = 1, |
92 | }, { |
93 | .name = "aio_ao12_4" , |
94 | .has_ao = 1, |
95 | }, |
96 | }; |
97 | |
98 | static int aio_aio12_8_ai_eoc(struct comedi_device *dev, |
99 | struct comedi_subdevice *s, |
100 | struct comedi_insn *insn, |
101 | unsigned long context) |
102 | { |
103 | unsigned int status; |
104 | |
105 | status = inb(port: dev->iobase + AIO12_8_STATUS_REG); |
106 | if (status & AIO12_8_STATUS_ADC_EOC) |
107 | return 0; |
108 | return -EBUSY; |
109 | } |
110 | |
111 | static int aio_aio12_8_ai_read(struct comedi_device *dev, |
112 | struct comedi_subdevice *s, |
113 | struct comedi_insn *insn, |
114 | unsigned int *data) |
115 | { |
116 | unsigned int chan = CR_CHAN(insn->chanspec); |
117 | unsigned int range = CR_RANGE(insn->chanspec); |
118 | unsigned int val; |
119 | unsigned char control; |
120 | int ret; |
121 | int i; |
122 | |
123 | /* |
124 | * Setup the control byte for internal 2MHz clock, 3uS conversion, |
125 | * at the desired range of the requested channel. |
126 | */ |
127 | control = AIO12_8_ADC_MODE_NORMAL | AIO12_8_ADC_ACQ_3USEC | |
128 | AIO12_8_ADC_RANGE(range) | AIO12_8_ADC_CHAN(chan); |
129 | |
130 | /* Read status to clear EOC latch */ |
131 | inb(port: dev->iobase + AIO12_8_STATUS_REG); |
132 | |
133 | for (i = 0; i < insn->n; i++) { |
134 | /* Setup and start conversion */ |
135 | outb(value: control, port: dev->iobase + AIO12_8_ADC_REG); |
136 | |
137 | /* Wait for conversion to complete */ |
138 | ret = comedi_timeout(dev, s, insn, cb: aio_aio12_8_ai_eoc, context: 0); |
139 | if (ret) |
140 | return ret; |
141 | |
142 | val = inw(port: dev->iobase + AIO12_8_ADC_REG) & s->maxdata; |
143 | |
144 | /* munge bipolar 2's complement data to offset binary */ |
145 | if (comedi_range_is_bipolar(s, range)) |
146 | val = comedi_offset_munge(s, val); |
147 | |
148 | data[i] = val; |
149 | } |
150 | |
151 | return insn->n; |
152 | } |
153 | |
154 | static int aio_aio12_8_ao_insn_write(struct comedi_device *dev, |
155 | struct comedi_subdevice *s, |
156 | struct comedi_insn *insn, |
157 | unsigned int *data) |
158 | { |
159 | unsigned int chan = CR_CHAN(insn->chanspec); |
160 | unsigned int val = s->readback[chan]; |
161 | int i; |
162 | |
163 | /* enable DACs */ |
164 | outb(AIO12_8_DAC_ENABLE_REF_ENA, port: dev->iobase + AIO12_8_DAC_ENABLE_REG); |
165 | |
166 | for (i = 0; i < insn->n; i++) { |
167 | val = data[i]; |
168 | outw(value: val, port: dev->iobase + AIO12_8_DAC_REG(chan)); |
169 | } |
170 | s->readback[chan] = val; |
171 | |
172 | return insn->n; |
173 | } |
174 | |
175 | static int aio_aio12_8_counter_insn_config(struct comedi_device *dev, |
176 | struct comedi_subdevice *s, |
177 | struct comedi_insn *insn, |
178 | unsigned int *data) |
179 | { |
180 | unsigned int chan = CR_CHAN(insn->chanspec); |
181 | |
182 | switch (data[0]) { |
183 | case INSN_CONFIG_GET_CLOCK_SRC: |
184 | /* |
185 | * Channels 0 and 2 have external clock sources. |
186 | * Channel 1 has a fixed 1 MHz clock source. |
187 | */ |
188 | data[0] = 0; |
189 | data[1] = (chan == 1) ? I8254_OSC_BASE_1MHZ : 0; |
190 | break; |
191 | default: |
192 | return -EINVAL; |
193 | } |
194 | |
195 | return insn->n; |
196 | } |
197 | |
198 | static int aio_aio12_8_attach(struct comedi_device *dev, |
199 | struct comedi_devconfig *it) |
200 | { |
201 | const struct aio12_8_boardtype *board = dev->board_ptr; |
202 | struct comedi_subdevice *s; |
203 | int ret; |
204 | |
205 | ret = comedi_request_region(dev, start: it->options[0], len: 32); |
206 | if (ret) |
207 | return ret; |
208 | |
209 | dev->pacer = comedi_8254_io_alloc(iobase: dev->iobase + AIO12_8_8254_BASE_REG, |
210 | osc_base: 0, I8254_IO8, regshift: 0); |
211 | if (IS_ERR(ptr: dev->pacer)) |
212 | return PTR_ERR(ptr: dev->pacer); |
213 | |
214 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
215 | if (ret) |
216 | return ret; |
217 | |
218 | /* Analog Input subdevice */ |
219 | s = &dev->subdevices[0]; |
220 | if (board->has_ai) { |
221 | s->type = COMEDI_SUBD_AI; |
222 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; |
223 | s->n_chan = 8; |
224 | s->maxdata = 0x0fff; |
225 | s->range_table = &aio_aio12_8_range; |
226 | s->insn_read = aio_aio12_8_ai_read; |
227 | } else { |
228 | s->type = COMEDI_SUBD_UNUSED; |
229 | } |
230 | |
231 | /* Analog Output subdevice */ |
232 | s = &dev->subdevices[1]; |
233 | if (board->has_ao) { |
234 | s->type = COMEDI_SUBD_AO; |
235 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND; |
236 | s->n_chan = 4; |
237 | s->maxdata = 0x0fff; |
238 | s->range_table = &aio_aio12_8_range; |
239 | s->insn_write = aio_aio12_8_ao_insn_write; |
240 | |
241 | ret = comedi_alloc_subdev_readback(s); |
242 | if (ret) |
243 | return ret; |
244 | } else { |
245 | s->type = COMEDI_SUBD_UNUSED; |
246 | } |
247 | |
248 | /* Digital I/O subdevice (8255) */ |
249 | s = &dev->subdevices[2]; |
250 | ret = subdev_8255_io_init(dev, s, AIO12_8_8255_BASE_REG); |
251 | if (ret) |
252 | return ret; |
253 | |
254 | /* Counter subdevice (8254) */ |
255 | s = &dev->subdevices[3]; |
256 | comedi_8254_subdevice_init(s, i8254: dev->pacer); |
257 | |
258 | dev->pacer->insn_config = aio_aio12_8_counter_insn_config; |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | static struct comedi_driver aio_aio12_8_driver = { |
264 | .driver_name = "aio_aio12_8" , |
265 | .module = THIS_MODULE, |
266 | .attach = aio_aio12_8_attach, |
267 | .detach = comedi_legacy_detach, |
268 | .board_name = &board_types[0].name, |
269 | .num_names = ARRAY_SIZE(board_types), |
270 | .offset = sizeof(struct aio12_8_boardtype), |
271 | }; |
272 | module_comedi_driver(aio_aio12_8_driver); |
273 | |
274 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
275 | MODULE_DESCRIPTION("Comedi driver for Access I/O AIO12-8 Analog I/O Board" ); |
276 | MODULE_LICENSE("GPL" ); |
277 | |