Commit | Line | Data |
---|---|---|
647d8b45 CC |
1 | /* |
2 | comedi/drivers/pcmda12.c | |
3 | Driver for Winsystems PC-104 based PCM-D/A-12 8-channel AO board. | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org> | |
7 | ||
8 | This program is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | This program is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with this program; if not, write to the Free Software | |
20 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
21 | */ | |
22 | /* | |
23 | Driver: pcmda12 | |
24 | Description: A driver for the Winsystems PCM-D/A-12 | |
25 | Devices: [Winsystems] PCM-D/A-12 (pcmda12) | |
26 | Author: Calin Culianu <calin@ajvar.org> | |
27 | Updated: Fri, 13 Jan 2006 12:01:01 -0500 | |
28 | Status: works | |
29 | ||
30 | A driver for the relatively straightforward-to-program PCM-D/A-12. | |
31 | This board doesn't support commands, and the only way to set its | |
32 | analog output range is to jumper the board. As such, | |
33 | comedi_data_write() ignores the range value specified. | |
34 | ||
35 | The board uses 16 consecutive I/O addresses starting at the I/O port | |
36 | base address. Each address corresponds to the LSB then MSB of a | |
37 | particular channel from 0-7. | |
38 | ||
39 | Note that the board is not ISA-PNP capable and thus | |
40 | needs the I/O port comedi_config parameter. | |
41 | ||
42 | Note that passing a nonzero value as the second config option will | |
43 | enable "simultaneous xfer" mode for this board, in which AO writes | |
44 | will not take effect until a subsequent read of any AO channel. This | |
45 | is so that one can speed up programming by preloading all AO registers | |
46 | with values before simultaneously setting them to take effect with one | |
47 | read command. | |
48 | ||
49 | Configuration Options: | |
50 | [0] - I/O port base address | |
51 | [1] - Do Simultaneous Xfer (see description) | |
52 | */ | |
53 | ||
54 | #include "../comedidev.h" | |
55 | ||
56 | #include <linux/pci.h> /* for PCI devices */ | |
57 | ||
647d8b45 CC |
58 | #define SDEV_NO ((int)(s - dev->subdevices)) |
59 | #define CHANS 8 | |
60 | #define IOSIZE 16 | |
61 | #define LSB(x) ((unsigned char)((x) & 0xff)) | |
62 | #define MSB(x) ((unsigned char)((((unsigned short)(x))>>8) & 0xff)) | |
63 | #define LSB_PORT(chan) (dev->iobase + (chan)*2) | |
64 | #define MSB_PORT(chan) (LSB_PORT(chan)+1) | |
65 | #define BITS 12 | |
66 | ||
67 | /* | |
68 | * Bords | |
69 | */ | |
387c136a | 70 | struct pcmda12_board { |
647d8b45 | 71 | const char *name; |
387c136a | 72 | }; |
647d8b45 CC |
73 | |
74 | /* note these have no effect and are merely here for reference.. | |
75 | these are configured by jumpering the board! */ | |
9ced1de6 | 76 | static const struct comedi_lrange pcmda12_ranges = { |
647d8b45 CC |
77 | 3, |
78 | { | |
0a85b6f0 MT |
79 | UNI_RANGE(5), UNI_RANGE(10), BIP_RANGE(5) |
80 | } | |
647d8b45 CC |
81 | }; |
82 | ||
387c136a | 83 | static const struct pcmda12_board pcmda12_boards[] = { |
647d8b45 | 84 | { |
0a85b6f0 MT |
85 | .name = "pcmda12", |
86 | }, | |
647d8b45 CC |
87 | }; |
88 | ||
89 | /* | |
90 | * Useful for shorthand access to the particular board structure | |
91 | */ | |
387c136a | 92 | #define thisboard ((const struct pcmda12_board *)dev->board_ptr) |
647d8b45 | 93 | |
39eb312d BP |
94 | struct pcmda12_private { |
95 | ||
790c5541 | 96 | unsigned int ao_readback[CHANS]; |
647d8b45 | 97 | int simultaneous_xfer_mode; |
39eb312d BP |
98 | }; |
99 | ||
39eb312d | 100 | #define devpriv ((struct pcmda12_private *)(dev->private)) |
647d8b45 CC |
101 | |
102 | /* | |
139dfbdf | 103 | * The struct comedi_driver structure tells the Comedi core module |
647d8b45 CC |
104 | * which functions to call to configure/deconfigure (attach/detach) |
105 | * the board, and also about the kernel module that contains | |
106 | * the device code. | |
107 | */ | |
0a85b6f0 MT |
108 | static int pcmda12_attach(struct comedi_device *dev, |
109 | struct comedi_devconfig *it); | |
da91b269 | 110 | static int pcmda12_detach(struct comedi_device *dev); |
647d8b45 | 111 | |
da91b269 | 112 | static void zero_chans(struct comedi_device *dev); |
647d8b45 | 113 | |
139dfbdf | 114 | static struct comedi_driver driver = { |
68c3dbff BP |
115 | .driver_name = "pcmda12", |
116 | .module = THIS_MODULE, | |
117 | .attach = pcmda12_attach, | |
118 | .detach = pcmda12_detach, | |
647d8b45 CC |
119 | /* It is not necessary to implement the following members if you are |
120 | * writing a driver for a ISA PnP or PCI card */ | |
121 | /* Most drivers will support multiple types of boards by | |
122 | * having an array of board structures. These were defined | |
123 | * in pcmda12_boards[] above. Note that the element 'name' | |
124 | * was first in the structure -- Comedi uses this fact to | |
125 | * extract the name of the board without knowing any details | |
126 | * about the structure except for its length. | |
127 | * When a device is attached (by comedi_config), the name | |
128 | * of the device is given to Comedi, and Comedi tries to | |
129 | * match it by going through the list of board names. If | |
130 | * there is a match, the address of the pointer is put | |
131 | * into dev->board_ptr and driver->attach() is called. | |
132 | * | |
133 | * Note that these are not necessary if you can determine | |
134 | * the type of board in software. ISA PnP, PCI, and PCMCIA | |
135 | * devices are such boards. | |
136 | */ | |
68c3dbff BP |
137 | .board_name = &pcmda12_boards[0].name, |
138 | .offset = sizeof(struct pcmda12_board), | |
8629efa4 | 139 | .num_names = ARRAY_SIZE(pcmda12_boards), |
647d8b45 CC |
140 | }; |
141 | ||
da91b269 | 142 | static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 143 | struct comedi_insn *insn, unsigned int *data); |
da91b269 | 144 | static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 145 | struct comedi_insn *insn, unsigned int *data); |
647d8b45 CC |
146 | |
147 | /* | |
148 | * Attach is called by the Comedi core to configure the driver | |
149 | * for a particular board. If you specified a board_name array | |
150 | * in the driver structure, dev->board_ptr contains that | |
151 | * address. | |
152 | */ | |
0a85b6f0 MT |
153 | static int pcmda12_attach(struct comedi_device *dev, |
154 | struct comedi_devconfig *it) | |
647d8b45 | 155 | { |
34c43922 | 156 | struct comedi_subdevice *s; |
647d8b45 CC |
157 | unsigned long iobase; |
158 | ||
159 | iobase = it->options[0]; | |
8b83e005 GS |
160 | printk(KERN_INFO |
161 | "comedi%d: %s: io: %lx %s ", dev->minor, driver.driver_name, | |
0a85b6f0 | 162 | iobase, it->options[1] ? "simultaneous xfer mode enabled" : ""); |
647d8b45 CC |
163 | |
164 | if (!request_region(iobase, IOSIZE, driver.driver_name)) { | |
165 | printk("I/O port conflict\n"); | |
166 | return -EIO; | |
167 | } | |
168 | dev->iobase = iobase; | |
169 | ||
170 | /* | |
171 | * Initialize dev->board_name. Note that we can use the "thisboard" | |
172 | * macro now, since we just initialized it in the last line. | |
173 | */ | |
174 | dev->board_name = thisboard->name; | |
175 | ||
176 | /* | |
177 | * Allocate the private structure area. alloc_private() is a | |
178 | * convenient macro defined in comedidev.h. | |
179 | */ | |
39eb312d | 180 | if (alloc_private(dev, sizeof(struct pcmda12_private)) < 0) { |
8b83e005 | 181 | printk(KERN_ERR "cannot allocate private data structure\n"); |
647d8b45 CC |
182 | return -ENOMEM; |
183 | } | |
184 | ||
185 | devpriv->simultaneous_xfer_mode = it->options[1]; | |
186 | ||
187 | /* | |
188 | * Allocate the subdevice structures. alloc_subdevice() is a | |
189 | * convenient macro defined in comedidev.h. | |
190 | * | |
191 | * Allocate 2 subdevs (32 + 16 DIO lines) or 3 32 DIO subdevs for the | |
192 | * 96-channel version of the board. | |
193 | */ | |
194 | if (alloc_subdevices(dev, 1) < 0) { | |
8b83e005 | 195 | printk(KERN_ERR "cannot allocate subdevice data structures\n"); |
647d8b45 CC |
196 | return -ENOMEM; |
197 | } | |
198 | ||
199 | s = dev->subdevices; | |
200 | s->private = NULL; | |
201 | s->maxdata = (0x1 << BITS) - 1; | |
202 | s->range_table = &pcmda12_ranges; | |
203 | s->type = COMEDI_SUBD_AO; | |
204 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
205 | s->n_chan = CHANS; | |
206 | s->insn_write = &ao_winsn; | |
207 | s->insn_read = &ao_rinsn; | |
208 | ||
209 | zero_chans(dev); /* clear out all the registers, basically */ | |
210 | ||
8b83e005 | 211 | printk(KERN_INFO "attached\n"); |
647d8b45 CC |
212 | |
213 | return 1; | |
214 | } | |
215 | ||
216 | /* | |
217 | * _detach is called to deconfigure a device. It should deallocate | |
218 | * resources. | |
219 | * This function is also called when _attach() fails, so it should be | |
220 | * careful not to release resources that were not necessarily | |
221 | * allocated by _attach(). dev->private and dev->subdevices are | |
222 | * deallocated automatically by the core. | |
223 | */ | |
da91b269 | 224 | static int pcmda12_detach(struct comedi_device *dev) |
647d8b45 | 225 | { |
8b83e005 GS |
226 | printk(KERN_INFO |
227 | "comedi%d: %s: remove\n", dev->minor, driver.driver_name); | |
647d8b45 CC |
228 | if (dev->iobase) |
229 | release_region(dev->iobase, IOSIZE); | |
230 | return 0; | |
231 | } | |
232 | ||
da91b269 | 233 | static void zero_chans(struct comedi_device *dev) |
647d8b45 CC |
234 | { /* sets up an |
235 | ASIC chip to defaults */ | |
236 | int i; | |
237 | for (i = 0; i < CHANS; ++i) { | |
238 | /* /\* do this as one instruction?? *\/ */ | |
239 | /* outw(0, LSB_PORT(chan)); */ | |
240 | outb(0, LSB_PORT(i)); | |
241 | outb(0, MSB_PORT(i)); | |
242 | } | |
243 | inb(LSB_PORT(0)); /* update chans. */ | |
244 | } | |
245 | ||
da91b269 | 246 | static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 247 | struct comedi_insn *insn, unsigned int *data) |
647d8b45 CC |
248 | { |
249 | int i; | |
250 | int chan = CR_CHAN(insn->chanspec); | |
251 | ||
252 | /* Writing a list of values to an AO channel is probably not | |
253 | * very useful, but that's how the interface is defined. */ | |
254 | for (i = 0; i < insn->n; ++i) { | |
255 | ||
256 | /* /\* do this as one instruction?? *\/ */ | |
257 | /* outw(data[i], LSB_PORT(chan)); */ | |
258 | ||
259 | /* Need to do this as two instructions due to 8-bit bus?? */ | |
260 | /* first, load the low byte */ | |
261 | outb(LSB(data[i]), LSB_PORT(chan)); | |
262 | /* next, write the high byte */ | |
263 | outb(MSB(data[i]), MSB_PORT(chan)); | |
264 | ||
265 | /* save shadow register */ | |
266 | devpriv->ao_readback[chan] = data[i]; | |
267 | ||
268 | if (!devpriv->simultaneous_xfer_mode) | |
269 | inb(LSB_PORT(chan)); | |
270 | } | |
271 | ||
272 | /* return the number of samples written */ | |
273 | return i; | |
274 | } | |
275 | ||
276 | /* AO subdevices should have a read insn as well as a write insn. | |
277 | ||
278 | Usually this means copying a value stored in devpriv->ao_readback. | |
279 | However, since this driver supports simultaneous xfer then sometimes | |
280 | this function actually accomplishes work. | |
281 | ||
282 | Simultaneaous xfer mode is accomplished by loading ALL the values | |
283 | you want for AO in all the channels, then READing off one of the AO | |
284 | registers to initiate the instantaneous simultaneous update of all | |
285 | DAC outputs, which makes all AO channels update simultaneously. | |
286 | This is useful for some control applications, I would imagine. | |
287 | */ | |
da91b269 | 288 | static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 289 | struct comedi_insn *insn, unsigned int *data) |
647d8b45 CC |
290 | { |
291 | int i; | |
292 | int chan = CR_CHAN(insn->chanspec); | |
293 | ||
294 | for (i = 0; i < insn->n; i++) { | |
295 | if (devpriv->simultaneous_xfer_mode) | |
296 | inb(LSB_PORT(chan)); | |
297 | /* read back shadow register */ | |
298 | data[i] = devpriv->ao_readback[chan]; | |
299 | } | |
300 | ||
301 | return i; | |
302 | } | |
303 | ||
304 | /* | |
305 | * A convenient macro that defines init_module() and cleanup_module(), | |
306 | * as necessary. | |
307 | */ | |
7114a280 AT |
308 | static int __init driver_init_module(void) |
309 | { | |
310 | return comedi_driver_register(&driver); | |
311 | } | |
312 | ||
313 | static void __exit driver_cleanup_module(void) | |
314 | { | |
315 | comedi_driver_unregister(&driver); | |
316 | } | |
317 | ||
318 | module_init(driver_init_module); | |
319 | module_exit(driver_cleanup_module); | |
90f703d3 AT |
320 | |
321 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
322 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
323 | MODULE_LICENSE("GPL"); |