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