Commit | Line | Data |
---|---|---|
e30ec011 DC |
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 | /* | |
14 | Driver: pcm3724 | |
15 | Description: Advantech PCM-3724 | |
16 | Author: Drew Csillag <drew_csillag@yahoo.com> | |
17 | Devices: [Advantech] PCM-3724 (pcm724) | |
18 | Status: tested | |
19 | ||
20 | This is driver for digital I/O boards PCM-3724 with 48 DIO. | |
21 | It needs 8255.o for operations and only immediate mode is supported. | |
22 | See the source for configuration details. | |
23 | ||
24 | Copy/pasted/hacked from pcm724.c | |
25 | */ | |
26 | /* | |
27 | * check_driver overrides: | |
90035c08 | 28 | * struct comedi_insn |
e30ec011 DC |
29 | */ |
30 | ||
31 | #include "../comedidev.h" | |
32 | ||
33 | #include <linux/ioport.h> | |
34 | #include <linux/delay.h> | |
35 | ||
36 | #include "8255.h" | |
37 | ||
38 | #define PCM3724_SIZE 16 | |
39 | #define SIZE_8255 4 | |
40 | ||
41 | #define BUF_C0 0x1 | |
42 | #define BUF_B0 0x2 | |
43 | #define BUF_A0 0x4 | |
44 | #define BUF_C1 0x8 | |
45 | #define BUF_B1 0x10 | |
46 | #define BUF_A1 0x20 | |
47 | ||
48 | #define GATE_A0 0x4 | |
49 | #define GATE_B0 0x2 | |
50 | #define GATE_C0 0x1 | |
51 | #define GATE_A1 0x20 | |
52 | #define GATE_B1 0x10 | |
53 | #define GATE_C1 0x8 | |
54 | ||
55 | /* from 8255.c */ | |
56 | #define CR_CW 0x80 | |
57 | #define _8255_CR 3 | |
58 | #define CR_B_IO 0x02 | |
59 | #define CR_B_MODE 0x04 | |
60 | #define CR_C_IO 0x09 | |
61 | #define CR_A_IO 0x10 | |
62 | #define CR_A_MODE(a) ((a)<<5) | |
63 | #define CR_CW 0x80 | |
64 | ||
2696fb57 | 65 | /* used to track configured dios */ |
8e7b864b | 66 | struct priv_pcm3724 { |
e30ec011 DC |
67 | int dio_1; |
68 | int dio_2; | |
8e7b864b BP |
69 | }; |
70 | ||
e30ec011 DC |
71 | static int subdev_8255_cb(int dir, int port, int data, unsigned long arg) |
72 | { | |
73 | unsigned long iobase = arg; | |
74 | unsigned char inbres; | |
2696fb57 | 75 | /* printk("8255cb %d %d %d %lx\n", dir,port,data,arg); */ |
e30ec011 | 76 | if (dir) { |
2696fb57 | 77 | /* printk("8255 cb outb(%x, %lx)\n", data, iobase+port); */ |
e30ec011 DC |
78 | outb(data, iobase + port); |
79 | return 0; | |
80 | } else { | |
81 | inbres = inb(iobase + port); | |
2696fb57 | 82 | /* printk("8255 cb inb(%lx) = %x\n", iobase+port, inbres); */ |
e30ec011 DC |
83 | return inbres; |
84 | } | |
85 | } | |
86 | ||
da91b269 | 87 | static int compute_buffer(int config, int devno, struct comedi_subdevice *s) |
e30ec011 DC |
88 | { |
89 | /* 1 in io_bits indicates output */ | |
90 | if (s->io_bits & 0x0000ff) { | |
50ae2a52 | 91 | if (devno == 0) |
e30ec011 | 92 | config |= BUF_A0; |
50ae2a52 | 93 | else |
e30ec011 | 94 | config |= BUF_A1; |
e30ec011 DC |
95 | } |
96 | if (s->io_bits & 0x00ff00) { | |
50ae2a52 | 97 | if (devno == 0) |
e30ec011 | 98 | config |= BUF_B0; |
50ae2a52 | 99 | else |
e30ec011 | 100 | config |= BUF_B1; |
e30ec011 DC |
101 | } |
102 | if (s->io_bits & 0xff0000) { | |
50ae2a52 | 103 | if (devno == 0) |
e30ec011 | 104 | config |= BUF_C0; |
50ae2a52 | 105 | else |
e30ec011 | 106 | config |= BUF_C1; |
e30ec011 DC |
107 | } |
108 | return config; | |
109 | } | |
110 | ||
0a85b6f0 MT |
111 | static void do_3724_config(struct comedi_device *dev, |
112 | struct comedi_subdevice *s, int chanspec) | |
e30ec011 | 113 | { |
0a96639f HS |
114 | struct comedi_subdevice *s_dio1 = &dev->subdevices[0]; |
115 | struct comedi_subdevice *s_dio2 = &dev->subdevices[1]; | |
e30ec011 DC |
116 | int config; |
117 | int buffer_config; | |
118 | unsigned long port_8255_cfg; | |
119 | ||
120 | config = CR_CW; | |
121 | buffer_config = 0; | |
122 | ||
123 | /* 1 in io_bits indicates output, 1 in config indicates input */ | |
50ae2a52 | 124 | if (!(s->io_bits & 0x0000ff)) |
e30ec011 | 125 | config |= CR_A_IO; |
50ae2a52 BA |
126 | |
127 | if (!(s->io_bits & 0x00ff00)) | |
e30ec011 | 128 | config |= CR_B_IO; |
50ae2a52 BA |
129 | |
130 | if (!(s->io_bits & 0xff0000)) | |
e30ec011 | 131 | config |= CR_C_IO; |
e30ec011 | 132 | |
0a96639f HS |
133 | buffer_config = compute_buffer(0, 0, s_dio1); |
134 | buffer_config = compute_buffer(buffer_config, 1, s_dio2); | |
e30ec011 | 135 | |
0a96639f | 136 | if (s == s_dio1) |
e30ec011 | 137 | port_8255_cfg = dev->iobase + _8255_CR; |
50ae2a52 | 138 | else |
e30ec011 | 139 | port_8255_cfg = dev->iobase + SIZE_8255 + _8255_CR; |
50ae2a52 | 140 | |
e30ec011 | 141 | outb(buffer_config, dev->iobase + 8); /* update buffer register */ |
50ae2a52 BA |
142 | /* printk("pcm3724 buffer_config (%lx) %d, %x\n", |
143 | dev->iobase + _8255_CR, chanspec, buffer_config); */ | |
144 | ||
e30ec011 DC |
145 | outb(config, port_8255_cfg); |
146 | } | |
147 | ||
0a85b6f0 MT |
148 | static void enable_chan(struct comedi_device *dev, struct comedi_subdevice *s, |
149 | int chanspec) | |
e30ec011 | 150 | { |
9a1a6cf8 | 151 | struct priv_pcm3724 *priv = dev->private; |
0a96639f | 152 | struct comedi_subdevice *s_dio1 = &dev->subdevices[0]; |
e30ec011 DC |
153 | unsigned int mask; |
154 | int gatecfg; | |
e30ec011 DC |
155 | |
156 | gatecfg = 0; | |
e30ec011 DC |
157 | |
158 | mask = 1 << CR_CHAN(chanspec); | |
0a96639f | 159 | if (s == s_dio1) |
e30ec011 | 160 | priv->dio_1 |= mask; |
0a96639f | 161 | else |
e30ec011 | 162 | priv->dio_2 |= mask; |
50ae2a52 BA |
163 | |
164 | if (priv->dio_1 & 0xff0000) | |
e30ec011 | 165 | gatecfg |= GATE_C0; |
50ae2a52 BA |
166 | |
167 | if (priv->dio_1 & 0xff00) | |
e30ec011 | 168 | gatecfg |= GATE_B0; |
50ae2a52 BA |
169 | |
170 | if (priv->dio_1 & 0xff) | |
e30ec011 | 171 | gatecfg |= GATE_A0; |
50ae2a52 BA |
172 | |
173 | if (priv->dio_2 & 0xff0000) | |
e30ec011 | 174 | gatecfg |= GATE_C1; |
50ae2a52 BA |
175 | |
176 | if (priv->dio_2 & 0xff00) | |
e30ec011 | 177 | gatecfg |= GATE_B1; |
50ae2a52 BA |
178 | |
179 | if (priv->dio_2 & 0xff) | |
e30ec011 | 180 | gatecfg |= GATE_A1; |
50ae2a52 | 181 | |
2696fb57 | 182 | /* printk("gate control %x\n", gatecfg); */ |
e30ec011 DC |
183 | outb(gatecfg, dev->iobase + 9); |
184 | } | |
185 | ||
186 | /* overriding the 8255 insn config */ | |
0a85b6f0 MT |
187 | static int subdev_3724_insn_config(struct comedi_device *dev, |
188 | struct comedi_subdevice *s, | |
189 | struct comedi_insn *insn, unsigned int *data) | |
e30ec011 DC |
190 | { |
191 | unsigned int mask; | |
192 | unsigned int bits; | |
193 | ||
194 | mask = 1 << CR_CHAN(insn->chanspec); | |
50ae2a52 | 195 | if (mask & 0x0000ff) |
e30ec011 | 196 | bits = 0x0000ff; |
50ae2a52 | 197 | else if (mask & 0x00ff00) |
e30ec011 | 198 | bits = 0x00ff00; |
50ae2a52 | 199 | else if (mask & 0x0f0000) |
e30ec011 | 200 | bits = 0x0f0000; |
50ae2a52 | 201 | else |
e30ec011 | 202 | bits = 0xf00000; |
e30ec011 DC |
203 | |
204 | switch (data[0]) { | |
205 | case INSN_CONFIG_DIO_INPUT: | |
206 | s->io_bits &= ~bits; | |
207 | break; | |
208 | case INSN_CONFIG_DIO_OUTPUT: | |
209 | s->io_bits |= bits; | |
210 | break; | |
211 | case INSN_CONFIG_DIO_QUERY: | |
212 | data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT; | |
213 | return insn->n; | |
214 | break; | |
215 | default: | |
216 | return -EINVAL; | |
217 | } | |
218 | ||
219 | do_3724_config(dev, s, insn->chanspec); | |
220 | enable_chan(dev, s, insn->chanspec); | |
221 | return 1; | |
222 | } | |
223 | ||
0a85b6f0 MT |
224 | static int pcm3724_attach(struct comedi_device *dev, |
225 | struct comedi_devconfig *it) | |
e30ec011 | 226 | { |
9a1a6cf8 | 227 | struct priv_pcm3724 *priv; |
0a96639f | 228 | struct comedi_subdevice *s; |
e30ec011 DC |
229 | unsigned long iobase; |
230 | unsigned int iorange; | |
5f1514bf HS |
231 | int ret, i; |
232 | ||
233 | dev->board_name = dev->driver->driver_name; | |
e30ec011 DC |
234 | |
235 | iobase = it->options[0]; | |
5f1514bf | 236 | iorange = PCM3724_SIZE; |
c3744138 | 237 | |
c34fa261 HS |
238 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
239 | if (!priv) | |
240 | return -ENOMEM; | |
241 | dev->private = priv; | |
e30ec011 | 242 | |
50ae2a52 | 243 | printk(KERN_INFO "comedi%d: pcm3724: board=%s, 0x%03lx ", dev->minor, |
5f1514bf | 244 | dev->board_name, iobase); |
e30ec011 DC |
245 | if (!iobase || !request_region(iobase, iorange, "pcm3724")) { |
246 | printk("I/O port conflict\n"); | |
247 | return -EIO; | |
248 | } | |
249 | ||
250 | dev->iobase = iobase; | |
50ae2a52 | 251 | printk(KERN_INFO "\n"); |
e30ec011 | 252 | |
5f1514bf | 253 | ret = comedi_alloc_subdevices(dev, 2); |
8b6c5694 | 254 | if (ret) |
e30ec011 DC |
255 | return ret; |
256 | ||
257 | for (i = 0; i < dev->n_subdevices; i++) { | |
0a96639f HS |
258 | s = &dev->subdevices[i]; |
259 | subdev_8255_init(dev, s, subdev_8255_cb, | |
0a85b6f0 | 260 | (unsigned long)(dev->iobase + SIZE_8255 * i)); |
0a96639f | 261 | s->insn_config = subdev_3724_insn_config; |
273f4bef | 262 | } |
e30ec011 DC |
263 | return 0; |
264 | } | |
265 | ||
484ecc95 | 266 | static void pcm3724_detach(struct comedi_device *dev) |
e30ec011 | 267 | { |
0a96639f | 268 | struct comedi_subdevice *s; |
e30ec011 DC |
269 | int i; |
270 | ||
271 | if (dev->subdevices) { | |
0a96639f HS |
272 | for (i = 0; i < dev->n_subdevices; i++) { |
273 | s = &dev->subdevices[i]; | |
274 | subdev_8255_cleanup(dev, s); | |
275 | } | |
e30ec011 | 276 | } |
50ae2a52 | 277 | if (dev->iobase) |
5f1514bf | 278 | release_region(dev->iobase, PCM3724_SIZE); |
e30ec011 | 279 | } |
90f703d3 | 280 | |
294f930d | 281 | static struct comedi_driver pcm3724_driver = { |
5f01bd51 HS |
282 | .driver_name = "pcm3724", |
283 | .module = THIS_MODULE, | |
284 | .attach = pcm3724_attach, | |
285 | .detach = pcm3724_detach, | |
5f01bd51 | 286 | }; |
294f930d | 287 | module_comedi_driver(pcm3724_driver); |
5f01bd51 | 288 | |
90f703d3 AT |
289 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
290 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
291 | MODULE_LICENSE("GPL"); |