1// SPDX-License-Identifier: GPL-2.0
2/*
3 * comedi/drivers/pcl730.c
4 * Driver for Advantech PCL-730 and clones
5 * José Luis Sánchez
6 */
7
8/*
9 * Driver: pcl730
10 * Description: Advantech PCL-730 (& compatibles)
11 * Devices: [Advantech] PCL-730 (pcl730), PCM-3730 (pcm3730), PCL-725 (pcl725),
12 *   PCL-733 (pcl733), PCL-734 (pcl734),
13 *   [ADLink] ACL-7130 (acl7130), ACL-7225b (acl7225b),
14 *   [ICP] ISO-730 (iso730), P8R8-DIO (p8r8dio), P16R16-DIO (p16r16dio),
15 *   [Diamond Systems] OPMM-1616-XT (opmm-1616-xt), PEARL-MM-P (pearl-mm-p),
16 *   IR104-PBF (ir104-pbf),
17 * Author: José Luis Sánchez (jsanchezv@teleline.es)
18 * Status: untested
19 *
20 * Configuration options:
21 *   [0] - I/O port base
22 *
23 * Interrupts are not supported.
24 * The ACL-7130 card has an 8254 timer/counter not supported by this driver.
25 */
26
27#include <linux/module.h>
28#include <linux/comedi/comedidev.h>
29
30/*
31 * Register map
32 *
33 * The register map varies slightly depending on the board type but
34 * all registers are 8-bit.
35 *
36 * The boardinfo 'io_range' is used to allow comedi to request the
37 * proper range required by the board.
38 *
39 * The comedi_subdevice 'private' data is used to pass the register
40 * offset to the (*insn_bits) functions to read/write the correct
41 * registers.
42 *
43 * The basic register mapping looks like this:
44 *
45 *     BASE+0  Isolated outputs 0-7 (write) / inputs 0-7 (read)
46 *     BASE+1  Isolated outputs 8-15 (write) / inputs 8-15 (read)
47 *     BASE+2  TTL outputs 0-7 (write) / inputs 0-7 (read)
48 *     BASE+3  TTL outputs 8-15 (write) / inputs 8-15 (read)
49 *
50 * The pcm3730 board does not have register BASE+1.
51 *
52 * The pcl725 and p8r8dio only have registers BASE+0 and BASE+1:
53 *
54 *     BASE+0  Isolated outputs 0-7 (write) (read back on p8r8dio)
55 *     BASE+1  Isolated inputs 0-7 (read)
56 *
57 * The acl7225b and p16r16dio boards have this register mapping:
58 *
59 *     BASE+0  Isolated outputs 0-7 (write) (read back)
60 *     BASE+1  Isolated outputs 8-15 (write) (read back)
61 *     BASE+2  Isolated inputs 0-7 (read)
62 *     BASE+3  Isolated inputs 8-15 (read)
63 *
64 * The pcl733 and pcl733 boards have this register mapping:
65 *
66 *     BASE+0  Isolated outputs 0-7 (write) or inputs 0-7 (read)
67 *     BASE+1  Isolated outputs 8-15 (write) or inputs 8-15 (read)
68 *     BASE+2  Isolated outputs 16-23 (write) or inputs 16-23 (read)
69 *     BASE+3  Isolated outputs 24-31 (write) or inputs 24-31 (read)
70 *
71 * The opmm-1616-xt board has this register mapping:
72 *
73 *     BASE+0  Isolated outputs 0-7 (write) (read back)
74 *     BASE+1  Isolated outputs 8-15 (write) (read back)
75 *     BASE+2  Isolated inputs 0-7 (read)
76 *     BASE+3  Isolated inputs 8-15 (read)
77 *
78 *     These registers are not currently supported:
79 *
80 *     BASE+2  Relay select register (write)
81 *     BASE+3  Board reset control register (write)
82 *     BASE+4  Interrupt control register (write)
83 *     BASE+4  Change detect 7-0 status register (read)
84 *     BASE+5  LED control register (write)
85 *     BASE+5  Change detect 15-8 status register (read)
86 *
87 * The pearl-mm-p board has this register mapping:
88 *
89 *     BASE+0  Isolated outputs 0-7 (write)
90 *     BASE+1  Isolated outputs 8-15 (write)
91 *
92 * The ir104-pbf board has this register mapping:
93 *
94 *     BASE+0  Isolated outputs 0-7 (write) (read back)
95 *     BASE+1  Isolated outputs 8-15 (write) (read back)
96 *     BASE+2  Isolated outputs 16-19 (write) (read back)
97 *     BASE+4  Isolated inputs 0-7 (read)
98 *     BASE+5  Isolated inputs 8-15 (read)
99 *     BASE+6  Isolated inputs 16-19 (read)
100 */
101
102struct pcl730_board {
103	const char *name;
104	unsigned int io_range;
105	unsigned is_pcl725:1;
106	unsigned is_acl7225b:1;
107	unsigned is_ir104:1;
108	unsigned has_readback:1;
109	unsigned has_ttl_io:1;
110	int n_subdevs;
111	int n_iso_out_chan;
112	int n_iso_in_chan;
113	int n_ttl_chan;
114};
115
116static const struct pcl730_board pcl730_boards[] = {
117	{
118		.name		= "pcl730",
119		.io_range	= 0x04,
120		.has_ttl_io	= 1,
121		.n_subdevs	= 4,
122		.n_iso_out_chan	= 16,
123		.n_iso_in_chan	= 16,
124		.n_ttl_chan	= 16,
125	}, {
126		.name		= "iso730",
127		.io_range	= 0x04,
128		.n_subdevs	= 4,
129		.n_iso_out_chan	= 16,
130		.n_iso_in_chan	= 16,
131		.n_ttl_chan	= 16,
132	}, {
133		.name		= "acl7130",
134		.io_range	= 0x08,
135		.has_ttl_io	= 1,
136		.n_subdevs	= 4,
137		.n_iso_out_chan	= 16,
138		.n_iso_in_chan	= 16,
139		.n_ttl_chan	= 16,
140	}, {
141		.name		= "pcm3730",
142		.io_range	= 0x04,
143		.has_ttl_io	= 1,
144		.n_subdevs	= 4,
145		.n_iso_out_chan	= 8,
146		.n_iso_in_chan	= 8,
147		.n_ttl_chan	= 16,
148	}, {
149		.name		= "pcl725",
150		.io_range	= 0x02,
151		.is_pcl725	= 1,
152		.n_subdevs	= 2,
153		.n_iso_out_chan	= 8,
154		.n_iso_in_chan	= 8,
155	}, {
156		.name		= "p8r8dio",
157		.io_range	= 0x02,
158		.is_pcl725	= 1,
159		.has_readback	= 1,
160		.n_subdevs	= 2,
161		.n_iso_out_chan	= 8,
162		.n_iso_in_chan	= 8,
163	}, {
164		.name		= "acl7225b",
165		.io_range	= 0x08,		/* only 4 are used */
166		.is_acl7225b	= 1,
167		.has_readback	= 1,
168		.n_subdevs	= 2,
169		.n_iso_out_chan	= 16,
170		.n_iso_in_chan	= 16,
171	}, {
172		.name		= "p16r16dio",
173		.io_range	= 0x04,
174		.is_acl7225b	= 1,
175		.has_readback	= 1,
176		.n_subdevs	= 2,
177		.n_iso_out_chan	= 16,
178		.n_iso_in_chan	= 16,
179	}, {
180		.name		= "pcl733",
181		.io_range	= 0x04,
182		.n_subdevs	= 1,
183		.n_iso_in_chan	= 32,
184	}, {
185		.name		= "pcl734",
186		.io_range	= 0x04,
187		.n_subdevs	= 1,
188		.n_iso_out_chan	= 32,
189	}, {
190		.name		= "opmm-1616-xt",
191		.io_range	= 0x10,
192		.is_acl7225b	= 1,
193		.has_readback	= 1,
194		.n_subdevs	= 2,
195		.n_iso_out_chan	= 16,
196		.n_iso_in_chan	= 16,
197	}, {
198		.name		= "pearl-mm-p",
199		.io_range	= 0x02,
200		.n_subdevs	= 1,
201		.n_iso_out_chan	= 16,
202	}, {
203		.name		= "ir104-pbf",
204		.io_range	= 0x08,
205		.is_ir104	= 1,
206		.has_readback	= 1,
207		.n_iso_out_chan	= 20,
208		.n_iso_in_chan	= 20,
209	},
210};
211
212static int pcl730_do_insn_bits(struct comedi_device *dev,
213			       struct comedi_subdevice *s,
214			       struct comedi_insn *insn,
215			       unsigned int *data)
216{
217	unsigned long reg = (unsigned long)s->private;
218	unsigned int mask;
219
220	mask = comedi_dio_update_state(s, data);
221	if (mask) {
222		if (mask & 0x00ff)
223			outb(s->state & 0xff, dev->iobase + reg);
224		if ((mask & 0xff00) && (s->n_chan > 8))
225			outb((s->state >> 8) & 0xff, dev->iobase + reg + 1);
226		if ((mask & 0xff0000) && (s->n_chan > 16))
227			outb((s->state >> 16) & 0xff, dev->iobase + reg + 2);
228		if ((mask & 0xff000000) && (s->n_chan > 24))
229			outb((s->state >> 24) & 0xff, dev->iobase + reg + 3);
230	}
231
232	data[1] = s->state;
233
234	return insn->n;
235}
236
237static unsigned int pcl730_get_bits(struct comedi_device *dev,
238				    struct comedi_subdevice *s)
239{
240	unsigned long reg = (unsigned long)s->private;
241	unsigned int val;
242
243	val = inb(dev->iobase + reg);
244	if (s->n_chan > 8)
245		val |= (inb(dev->iobase + reg + 1) << 8);
246	if (s->n_chan > 16)
247		val |= (inb(dev->iobase + reg + 2) << 16);
248	if (s->n_chan > 24)
249		val |= (inb(dev->iobase + reg + 3) << 24);
250
251	return val;
252}
253
254static int pcl730_di_insn_bits(struct comedi_device *dev,
255			       struct comedi_subdevice *s,
256			       struct comedi_insn *insn,
257			       unsigned int *data)
258{
259	data[1] = pcl730_get_bits(dev, s);
260
261	return insn->n;
262}
263
264static int pcl730_attach(struct comedi_device *dev,
265			 struct comedi_devconfig *it)
266{
267	const struct pcl730_board *board = dev->board_ptr;
268	struct comedi_subdevice *s;
269	int subdev;
270	int ret;
271
272	ret = comedi_request_region(dev, it->options[0], board->io_range);
273	if (ret)
274		return ret;
275
276	ret = comedi_alloc_subdevices(dev, board->n_subdevs);
277	if (ret)
278		return ret;
279
280	subdev = 0;
281
282	if (board->n_iso_out_chan) {
283		/* Isolated Digital Outputs */
284		s = &dev->subdevices[subdev++];
285		s->type		= COMEDI_SUBD_DO;
286		s->subdev_flags	= SDF_WRITABLE;
287		s->n_chan	= board->n_iso_out_chan;
288		s->maxdata	= 1;
289		s->range_table	= &range_digital;
290		s->insn_bits	= pcl730_do_insn_bits;
291		s->private	= (void *)0;
292
293		/* get the initial state if supported */
294		if (board->has_readback)
295			s->state = pcl730_get_bits(dev, s);
296	}
297
298	if (board->n_iso_in_chan) {
299		/* Isolated Digital Inputs */
300		s = &dev->subdevices[subdev++];
301		s->type		= COMEDI_SUBD_DI;
302		s->subdev_flags	= SDF_READABLE;
303		s->n_chan	= board->n_iso_in_chan;
304		s->maxdata	= 1;
305		s->range_table	= &range_digital;
306		s->insn_bits	= pcl730_di_insn_bits;
307		s->private	= board->is_ir104 ? (void *)4 :
308				  board->is_acl7225b ? (void *)2 :
309				  board->is_pcl725 ? (void *)1 : (void *)0;
310	}
311
312	if (board->has_ttl_io) {
313		/* TTL Digital Outputs */
314		s = &dev->subdevices[subdev++];
315		s->type		= COMEDI_SUBD_DO;
316		s->subdev_flags	= SDF_WRITABLE;
317		s->n_chan	= board->n_ttl_chan;
318		s->maxdata	= 1;
319		s->range_table	= &range_digital;
320		s->insn_bits	= pcl730_do_insn_bits;
321		s->private	= (void *)2;
322
323		/* TTL Digital Inputs */
324		s = &dev->subdevices[subdev++];
325		s->type		= COMEDI_SUBD_DI;
326		s->subdev_flags	= SDF_READABLE;
327		s->n_chan	= board->n_ttl_chan;
328		s->maxdata	= 1;
329		s->range_table	= &range_digital;
330		s->insn_bits	= pcl730_di_insn_bits;
331		s->private	= (void *)2;
332	}
333
334	return 0;
335}
336
337static struct comedi_driver pcl730_driver = {
338	.driver_name	= "pcl730",
339	.module		= THIS_MODULE,
340	.attach		= pcl730_attach,
341	.detach		= comedi_legacy_detach,
342	.board_name	= &pcl730_boards[0].name,
343	.num_names	= ARRAY_SIZE(pcl730_boards),
344	.offset		= sizeof(struct pcl730_board),
345};
346module_comedi_driver(pcl730_driver);
347
348MODULE_AUTHOR("Comedi https://www.comedi.org");
349MODULE_DESCRIPTION("Comedi low-level driver");
350MODULE_LICENSE("GPL");
351