Commit | Line | Data |
---|---|---|
18222f98 RS |
1 | /* |
2 | comedi/drivers/ssv_dnp.c | |
3 | generic comedi driver for SSV Embedded Systems' DIL/Net-PCs | |
4 | Copyright (C) 2001 Robert Schwebel <robert@schwebel.de> | |
5 | ||
6 | COMEDI - Linux Control and Measurement Device Interface | |
7 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
8 | ||
9 | This program is free software; you can redistribute it and/or modify | |
10 | it under the terms of the GNU General Public License as published by | |
11 | the Free Software Foundation; either version 2 of the License, or | |
12 | (at your option) any later version. | |
13 | ||
14 | This program is distributed in the hope that it will be useful, | |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | GNU General Public License for more details. | |
18 | ||
19 | You should have received a copy of the GNU General Public License | |
20 | along with this program; if not, write to the Free Software | |
21 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
22 | ||
23 | */ | |
24 | /* | |
25 | Driver: ssv_dnp | |
26 | Description: SSV Embedded Systems DIL/Net-PC | |
27 | Author: Robert Schwebel <robert@schwebel.de> | |
28 | Devices: [SSV Embedded Systems] DIL/Net-PC 1486 (dnp-1486) | |
29 | Status: unknown | |
30 | */ | |
31 | ||
32 | /* include files ----------------------------------------------------------- */ | |
33 | ||
34 | #include "../comedidev.h" | |
35 | ||
36 | /* Some global definitions: the registers of the DNP ----------------------- */ | |
37 | /* */ | |
38 | /* For port A and B the mode register has bits corresponding to the output */ | |
39 | /* pins, where Bit-N = 0 -> input, Bit-N = 1 -> output. Note that bits */ | |
40 | /* 4 to 7 correspond to pin 0..3 for port C data register. Ensure that bits */ | |
41 | /* 0..3 remain unchanged! For details about Port C Mode Register see */ | |
42 | /* the remarks in dnp_insn_config() below. */ | |
43 | ||
06033fce DA |
44 | #define CSCIR 0x22 /* Chip Setup and Control Index Register */ |
45 | #define CSCDR 0x23 /* Chip Setup and Control Data Register */ | |
46 | #define PAMR 0xa5 /* Port A Mode Register */ | |
47 | #define PADR 0xa9 /* Port A Data Register */ | |
48 | #define PBMR 0xa4 /* Port B Mode Register */ | |
49 | #define PBDR 0xa8 /* Port B Data Register */ | |
50 | #define PCMR 0xa3 /* Port C Mode Register */ | |
51 | #define PCDR 0xa7 /* Port C Data Register */ | |
18222f98 RS |
52 | |
53 | /* This data structure holds information about the supported boards -------- */ | |
54 | ||
c0d14822 | 55 | struct dnp_board { |
18222f98 RS |
56 | const char *name; |
57 | int ai_chans; | |
58 | int ai_bits; | |
59 | int have_dio; | |
c0d14822 | 60 | }; |
18222f98 | 61 | |
06033fce DA |
62 | /* We only support one DNP 'board' variant at the moment */ |
63 | static const struct dnp_board dnp_boards[] = { | |
64 | { | |
0a85b6f0 MT |
65 | .name = "dnp-1486", |
66 | .ai_chans = 16, | |
67 | .ai_bits = 12, | |
68 | .have_dio = 1, | |
69 | }, | |
18222f98 RS |
70 | }; |
71 | ||
72 | /* Useful for shorthand access to the particular board structure ----------- */ | |
c0d14822 | 73 | #define thisboard ((const struct dnp_board *)dev->board_ptr) |
18222f98 RS |
74 | |
75 | /* This structure is for data unique to the DNP driver --------------------- */ | |
85325300 BP |
76 | struct dnp_private_data { |
77 | ||
85325300 BP |
78 | }; |
79 | ||
18222f98 RS |
80 | /* Shorthand macro for faster access to the private data ------------------- */ |
81 | #define devpriv ((dnp_private *)dev->private) | |
82 | ||
83 | /* ------------------------------------------------------------------------- */ | |
06033fce DA |
84 | /* The struct comedi_driver structure tells the Comedi core module which */ |
85 | /* functions to call to configure/deconfigure (attach/detach) the board, and */ | |
86 | /* also about the kernel module that contains the device code. */ | |
18222f98 RS |
87 | /* */ |
88 | /* In the following section we define the API of this driver. */ | |
89 | /* ------------------------------------------------------------------------- */ | |
90 | ||
da91b269 BP |
91 | static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it); |
92 | static int dnp_detach(struct comedi_device *dev); | |
18222f98 | 93 | |
139dfbdf | 94 | static struct comedi_driver driver_dnp = { |
68c3dbff BP |
95 | .driver_name = "ssv_dnp", |
96 | .module = THIS_MODULE, | |
97 | .attach = dnp_attach, | |
98 | .detach = dnp_detach, | |
99 | .board_name = &dnp_boards[0].name, | |
18222f98 | 100 | /* only necessary for non-PnP devs */ |
06033fce | 101 | .offset = sizeof(struct dnp_board), /* like ISA-PnP, PCI or PCMCIA */ |
8629efa4 | 102 | .num_names = ARRAY_SIZE(dnp_boards), |
18222f98 RS |
103 | }; |
104 | ||
7114a280 AT |
105 | static int __init driver_dnp_init_module(void) |
106 | { | |
107 | return comedi_driver_register(&driver_dnp); | |
108 | } | |
109 | ||
110 | static void __exit driver_dnp_cleanup_module(void) | |
111 | { | |
112 | comedi_driver_unregister(&driver_dnp); | |
113 | } | |
114 | ||
115 | module_init(driver_dnp_init_module); | |
116 | module_exit(driver_dnp_cleanup_module); | |
18222f98 | 117 | |
da91b269 | 118 | static int dnp_dio_insn_bits(struct comedi_device *dev, |
0a85b6f0 MT |
119 | struct comedi_subdevice *s, |
120 | struct comedi_insn *insn, unsigned int *data); | |
18222f98 | 121 | |
da91b269 | 122 | static int dnp_dio_insn_config(struct comedi_device *dev, |
0a85b6f0 MT |
123 | struct comedi_subdevice *s, |
124 | struct comedi_insn *insn, unsigned int *data); | |
18222f98 RS |
125 | |
126 | /* ------------------------------------------------------------------------- */ | |
127 | /* Attach is called by comedi core to configure the driver for a particular */ | |
128 | /* board. If you specified a board_name array in the driver structure, */ | |
129 | /* dev->board_ptr contains that address. */ | |
130 | /* ------------------------------------------------------------------------- */ | |
131 | ||
da91b269 | 132 | static int dnp_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
18222f98 RS |
133 | { |
134 | ||
34c43922 | 135 | struct comedi_subdevice *s; |
18222f98 | 136 | |
06033fce | 137 | printk(KERN_INFO "comedi%d: dnp: ", dev->minor); |
18222f98 | 138 | |
06033fce DA |
139 | /* Autoprobing: this should find out which board we have. Currently */ |
140 | /* only the 1486 board is supported and autoprobing is not */ | |
141 | /* implemented :-) */ | |
2696fb57 | 142 | /* dev->board_ptr = dnp_probe(dev); */ |
18222f98 | 143 | |
06033fce DA |
144 | /* Initialize the name of the board. */ |
145 | /* We can use the "thisboard" macro now. */ | |
18222f98 RS |
146 | dev->board_name = thisboard->name; |
147 | ||
06033fce DA |
148 | /* Allocate the private structure area. alloc_private() is a */ |
149 | /* convenient macro defined in comedidev.h. */ | |
85325300 | 150 | if (alloc_private(dev, sizeof(struct dnp_private_data)) < 0) |
18222f98 RS |
151 | return -ENOMEM; |
152 | ||
06033fce DA |
153 | /* Allocate the subdevice structures. alloc_subdevice() is a */ |
154 | /* convenient macro defined in comedidev.h. */ | |
18222f98 RS |
155 | |
156 | if (alloc_subdevices(dev, 1) < 0) | |
157 | return -ENOMEM; | |
158 | ||
159 | s = dev->subdevices + 0; | |
06033fce | 160 | /* digital i/o subdevice */ |
18222f98 RS |
161 | s->type = COMEDI_SUBD_DIO; |
162 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
163 | s->n_chan = 20; | |
164 | s->maxdata = 1; | |
165 | s->range_table = &range_digital; | |
166 | s->insn_bits = dnp_dio_insn_bits; | |
167 | s->insn_config = dnp_dio_insn_config; | |
168 | ||
169 | printk("attached\n"); | |
170 | ||
171 | /* We use the I/O ports 0x22,0x23 and 0xa3-0xa9, which are always | |
172 | * allocated for the primary 8259, so we don't need to allocate them | |
173 | * ourselves. */ | |
174 | ||
06033fce | 175 | /* configure all ports as input (default) */ |
18222f98 RS |
176 | outb(PAMR, CSCIR); |
177 | outb(0x00, CSCDR); | |
178 | outb(PBMR, CSCIR); | |
179 | outb(0x00, CSCDR); | |
180 | outb(PCMR, CSCIR); | |
181 | outb((inb(CSCDR) & 0xAA), CSCDR); | |
182 | ||
183 | return 1; | |
184 | ||
185 | } | |
186 | ||
187 | /* ------------------------------------------------------------------------- */ | |
188 | /* detach is called to deconfigure a device. It should deallocate the */ | |
189 | /* resources. This function is also called when _attach() fails, so it */ | |
190 | /* should be careful not to release resources that were not necessarily */ | |
191 | /* allocated by _attach(). dev->private and dev->subdevices are */ | |
192 | /* deallocated automatically by the core. */ | |
193 | /* ------------------------------------------------------------------------- */ | |
194 | ||
da91b269 | 195 | static int dnp_detach(struct comedi_device *dev) |
18222f98 RS |
196 | { |
197 | ||
06033fce | 198 | /* configure all ports as input (default) */ |
18222f98 RS |
199 | outb(PAMR, CSCIR); |
200 | outb(0x00, CSCDR); | |
201 | outb(PBMR, CSCIR); | |
202 | outb(0x00, CSCDR); | |
203 | outb(PCMR, CSCIR); | |
204 | outb((inb(CSCDR) & 0xAA), CSCDR); | |
205 | ||
06033fce DA |
206 | /* announce that we are finished */ |
207 | printk(KERN_INFO "comedi%d: dnp: remove\n", dev->minor); | |
18222f98 RS |
208 | |
209 | return 0; | |
210 | ||
211 | } | |
212 | ||
213 | /* ------------------------------------------------------------------------- */ | |
214 | /* The insn_bits interface allows packed reading/writing of DIO channels. */ | |
215 | /* The comedi core can convert between insn_bits and insn_read/write, so you */ | |
216 | /* are able to use these instructions as well. */ | |
217 | /* ------------------------------------------------------------------------- */ | |
218 | ||
da91b269 | 219 | static int dnp_dio_insn_bits(struct comedi_device *dev, |
0a85b6f0 MT |
220 | struct comedi_subdevice *s, |
221 | struct comedi_insn *insn, unsigned int *data) | |
18222f98 RS |
222 | { |
223 | ||
224 | if (insn->n != 2) | |
225 | return -EINVAL; /* insn uses data[0] and data[1] */ | |
226 | ||
06033fce DA |
227 | /* The insn data is a mask in data[0] and the new data in data[1], */ |
228 | /* each channel cooresponding to a bit. */ | |
18222f98 | 229 | |
06033fce DA |
230 | /* Ports A and B are straight forward: each bit corresponds to an */ |
231 | /* output pin with the same order. Port C is different: bits 0...3 */ | |
232 | /* correspond to bits 4...7 of the output register (PCDR). */ | |
18222f98 RS |
233 | |
234 | if (data[0]) { | |
235 | ||
236 | outb(PADR, CSCIR); | |
237 | outb((inb(CSCDR) | |
0a85b6f0 MT |
238 | & ~(u8) (data[0] & 0x0000FF)) |
239 | | (u8) (data[1] & 0x0000FF), CSCDR); | |
18222f98 RS |
240 | |
241 | outb(PBDR, CSCIR); | |
242 | outb((inb(CSCDR) | |
0a85b6f0 MT |
243 | & ~(u8) ((data[0] & 0x00FF00) >> 8)) |
244 | | (u8) ((data[1] & 0x00FF00) >> 8), CSCDR); | |
18222f98 RS |
245 | |
246 | outb(PCDR, CSCIR); | |
247 | outb((inb(CSCDR) | |
0a85b6f0 MT |
248 | & ~(u8) ((data[0] & 0x0F0000) >> 12)) |
249 | | (u8) ((data[1] & 0x0F0000) >> 12), CSCDR); | |
18222f98 RS |
250 | } |
251 | ||
06033fce | 252 | /* on return, data[1] contains the value of the digital input lines. */ |
18222f98 RS |
253 | outb(PADR, CSCIR); |
254 | data[0] = inb(CSCDR); | |
255 | outb(PBDR, CSCIR); | |
256 | data[0] += inb(CSCDR) << 8; | |
257 | outb(PCDR, CSCIR); | |
258 | data[0] += ((inb(CSCDR) & 0xF0) << 12); | |
259 | ||
260 | return 2; | |
261 | ||
262 | } | |
263 | ||
264 | /* ------------------------------------------------------------------------- */ | |
265 | /* Configure the direction of the bidirectional digital i/o pins. chanspec */ | |
266 | /* contains the channel to be changed and data[0] contains either */ | |
267 | /* COMEDI_INPUT or COMEDI_OUTPUT. */ | |
268 | /* ------------------------------------------------------------------------- */ | |
269 | ||
da91b269 | 270 | static int dnp_dio_insn_config(struct comedi_device *dev, |
0a85b6f0 MT |
271 | struct comedi_subdevice *s, |
272 | struct comedi_insn *insn, unsigned int *data) | |
18222f98 RS |
273 | { |
274 | ||
275 | u8 register_buffer; | |
276 | ||
06033fce DA |
277 | /* reduces chanspec to lower 16 bits */ |
278 | int chan = CR_CHAN(insn->chanspec); | |
18222f98 RS |
279 | |
280 | switch (data[0]) { | |
281 | case INSN_CONFIG_DIO_OUTPUT: | |
282 | case INSN_CONFIG_DIO_INPUT: | |
283 | break; | |
284 | case INSN_CONFIG_DIO_QUERY: | |
285 | data[1] = | |
0a85b6f0 | 286 | (inb(CSCDR) & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; |
18222f98 RS |
287 | return insn->n; |
288 | break; | |
289 | default: | |
290 | return -EINVAL; | |
291 | break; | |
292 | } | |
06033fce | 293 | /* Test: which port does the channel belong to? */ |
18222f98 | 294 | |
06033fce DA |
295 | /* We have to pay attention with port C: this is the meaning of PCMR: */ |
296 | /* Bit in PCMR: 7 6 5 4 3 2 1 0 */ | |
297 | /* Corresponding port C pin: d 3 d 2 d 1 d 0 d= don't touch */ | |
18222f98 RS |
298 | |
299 | if ((chan >= 0) && (chan <= 7)) { | |
300 | /* this is port A */ | |
301 | outb(PAMR, CSCIR); | |
302 | } else if ((chan >= 8) && (chan <= 15)) { | |
303 | /* this is port B */ | |
304 | chan -= 8; | |
305 | outb(PBMR, CSCIR); | |
306 | } else if ((chan >= 16) && (chan <= 19)) { | |
06033fce DA |
307 | /* this is port C; multiplication with 2 brings bits into */ |
308 | /* correct position for PCMR! */ | |
18222f98 RS |
309 | chan -= 16; |
310 | chan *= 2; | |
311 | outb(PCMR, CSCIR); | |
312 | } else { | |
313 | return -EINVAL; | |
314 | } | |
315 | ||
06033fce | 316 | /* read 'old' direction of the port and set bits (out=1, in=0) */ |
18222f98 | 317 | register_buffer = inb(CSCDR); |
dee86e8c | 318 | if (data[0] == COMEDI_OUTPUT) |
18222f98 | 319 | register_buffer |= (1 << chan); |
dee86e8c | 320 | else |
18222f98 | 321 | register_buffer &= ~(1 << chan); |
dee86e8c | 322 | |
18222f98 RS |
323 | outb(register_buffer, CSCDR); |
324 | ||
325 | return 1; | |
326 | ||
327 | } | |
90f703d3 AT |
328 | |
329 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
330 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
331 | MODULE_LICENSE("GPL"); |