1/*
2    comedi/drivers/pcm724.c
3
4    Drew Csillag <drew_csillag@yahoo.com>
5
6    hardware driver for Advantech card:
7     card:   PCM-3724
8     driver: pcm3724
9
10    Options for PCM-3724
11     [0] - IO Base
12*/
13/*
14Driver: pcm3724
15Description: Advantech PCM-3724
16Author: Drew Csillag <drew_csillag@yahoo.com>
17Devices: [Advantech] PCM-3724 (pcm724)
18Status: tested
19
20This is driver for digital I/O boards PCM-3724 with 48 DIO.
21It needs 8255.o for operations and only immediate mode is supported.
22See the source for configuration details.
23
24Copy/pasted/hacked from pcm724.c
25*/
26/*
27 * check_driver overrides:
28 *   struct comedi_insn
29 */
30
31#include <linux/module.h>
32#include "../comedidev.h"
33
34#include "8255.h"
35
36#define BUF_C0 0x1
37#define BUF_B0 0x2
38#define BUF_A0 0x4
39#define BUF_C1 0x8
40#define BUF_B1 0x10
41#define BUF_A1 0x20
42
43#define GATE_A0 0x4
44#define GATE_B0	0x2
45#define GATE_C0	0x1
46#define GATE_A1	0x20
47#define GATE_B1	0x10
48#define GATE_C1 0x8
49
50/* used to track configured dios */
51struct priv_pcm3724 {
52	int dio_1;
53	int dio_2;
54};
55
56static int compute_buffer(int config, int devno, struct comedi_subdevice *s)
57{
58	/* 1 in io_bits indicates output */
59	if (s->io_bits & 0x0000ff) {
60		if (devno == 0)
61			config |= BUF_A0;
62		else
63			config |= BUF_A1;
64	}
65	if (s->io_bits & 0x00ff00) {
66		if (devno == 0)
67			config |= BUF_B0;
68		else
69			config |= BUF_B1;
70	}
71	if (s->io_bits & 0xff0000) {
72		if (devno == 0)
73			config |= BUF_C0;
74		else
75			config |= BUF_C1;
76	}
77	return config;
78}
79
80static void do_3724_config(struct comedi_device *dev,
81			   struct comedi_subdevice *s, int chanspec)
82{
83	struct comedi_subdevice *s_dio1 = &dev->subdevices[0];
84	struct comedi_subdevice *s_dio2 = &dev->subdevices[1];
85	int config;
86	int buffer_config;
87	unsigned long port_8255_cfg;
88
89	config = I8255_CTRL_CW;
90	buffer_config = 0;
91
92	/* 1 in io_bits indicates output, 1 in config indicates input */
93	if (!(s->io_bits & 0x0000ff))
94		config |= I8255_CTRL_A_IO;
95
96	if (!(s->io_bits & 0x00ff00))
97		config |= I8255_CTRL_B_IO;
98
99	if (!(s->io_bits & 0xff0000))
100		config |= I8255_CTRL_C_HI_IO | I8255_CTRL_C_LO_IO;
101
102	buffer_config = compute_buffer(0, 0, s_dio1);
103	buffer_config = compute_buffer(buffer_config, 1, s_dio2);
104
105	if (s == s_dio1)
106		port_8255_cfg = dev->iobase + I8255_CTRL_REG;
107	else
108		port_8255_cfg = dev->iobase + I8255_SIZE + I8255_CTRL_REG;
109
110	outb(buffer_config, dev->iobase + 8);	/* update buffer register */
111
112	outb(config, port_8255_cfg);
113}
114
115static void enable_chan(struct comedi_device *dev, struct comedi_subdevice *s,
116			int chanspec)
117{
118	struct priv_pcm3724 *priv = dev->private;
119	struct comedi_subdevice *s_dio1 = &dev->subdevices[0];
120	unsigned int mask;
121	int gatecfg;
122
123	gatecfg = 0;
124
125	mask = 1 << CR_CHAN(chanspec);
126	if (s == s_dio1)
127		priv->dio_1 |= mask;
128	else
129		priv->dio_2 |= mask;
130
131	if (priv->dio_1 & 0xff0000)
132		gatecfg |= GATE_C0;
133
134	if (priv->dio_1 & 0xff00)
135		gatecfg |= GATE_B0;
136
137	if (priv->dio_1 & 0xff)
138		gatecfg |= GATE_A0;
139
140	if (priv->dio_2 & 0xff0000)
141		gatecfg |= GATE_C1;
142
143	if (priv->dio_2 & 0xff00)
144		gatecfg |= GATE_B1;
145
146	if (priv->dio_2 & 0xff)
147		gatecfg |= GATE_A1;
148
149	outb(gatecfg, dev->iobase + 9);
150}
151
152/* overriding the 8255 insn config */
153static int subdev_3724_insn_config(struct comedi_device *dev,
154				   struct comedi_subdevice *s,
155				   struct comedi_insn *insn,
156				   unsigned int *data)
157{
158	unsigned int chan = CR_CHAN(insn->chanspec);
159	unsigned int mask;
160	int ret;
161
162	if (chan < 8)
163		mask = 0x0000ff;
164	else if (chan < 16)
165		mask = 0x00ff00;
166	else if (chan < 20)
167		mask = 0x0f0000;
168	else
169		mask = 0xf00000;
170
171	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
172	if (ret)
173		return ret;
174
175	do_3724_config(dev, s, insn->chanspec);
176	enable_chan(dev, s, insn->chanspec);
177
178	return insn->n;
179}
180
181static int pcm3724_attach(struct comedi_device *dev,
182			  struct comedi_devconfig *it)
183{
184	struct priv_pcm3724 *priv;
185	struct comedi_subdevice *s;
186	int ret, i;
187
188	priv = comedi_alloc_devpriv(dev, sizeof(*priv));
189	if (!priv)
190		return -ENOMEM;
191
192	ret = comedi_request_region(dev, it->options[0], 0x10);
193	if (ret)
194		return ret;
195
196	ret = comedi_alloc_subdevices(dev, 2);
197	if (ret)
198		return ret;
199
200	for (i = 0; i < dev->n_subdevices; i++) {
201		s = &dev->subdevices[i];
202		ret = subdev_8255_init(dev, s, NULL, i * I8255_SIZE);
203		if (ret)
204			return ret;
205		s->insn_config = subdev_3724_insn_config;
206	}
207	return 0;
208}
209
210static struct comedi_driver pcm3724_driver = {
211	.driver_name	= "pcm3724",
212	.module		= THIS_MODULE,
213	.attach		= pcm3724_attach,
214	.detach		= comedi_legacy_detach,
215};
216module_comedi_driver(pcm3724_driver);
217
218MODULE_AUTHOR("Comedi http://www.comedi.org");
219MODULE_DESCRIPTION("Comedi low-level driver");
220MODULE_LICENSE("GPL");
221