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 | ||
58 | #define MIN(a,b) ( ((a) < (b)) ? (a) : (b) ) | |
59 | #define SDEV_NO ((int)(s - dev->subdevices)) | |
60 | #define CHANS 8 | |
61 | #define IOSIZE 16 | |
62 | #define LSB(x) ((unsigned char)((x) & 0xff)) | |
63 | #define MSB(x) ((unsigned char)((((unsigned short)(x))>>8) & 0xff)) | |
64 | #define LSB_PORT(chan) (dev->iobase + (chan)*2) | |
65 | #define MSB_PORT(chan) (LSB_PORT(chan)+1) | |
66 | #define BITS 12 | |
67 | ||
68 | /* | |
69 | * Bords | |
70 | */ | |
71 | typedef struct pcmda12_board_struct { | |
72 | const char *name; | |
73 | } pcmda12_board; | |
74 | ||
75 | /* note these have no effect and are merely here for reference.. | |
76 | these are configured by jumpering the board! */ | |
77 | static const comedi_lrange pcmda12_ranges = { | |
78 | 3, | |
79 | { | |
80 | UNI_RANGE(5), UNI_RANGE(10), BIP_RANGE(5) | |
81 | } | |
82 | }; | |
83 | ||
84 | static const pcmda12_board pcmda12_boards[] = { | |
85 | { | |
86 | name: "pcmda12", | |
87 | }, | |
88 | }; | |
89 | ||
90 | /* | |
91 | * Useful for shorthand access to the particular board structure | |
92 | */ | |
93 | #define thisboard ((const pcmda12_board *)dev->board_ptr) | |
94 | ||
95 | typedef struct { | |
790c5541 | 96 | unsigned int ao_readback[CHANS]; |
647d8b45 CC |
97 | int simultaneous_xfer_mode; |
98 | } pcmda12_private; | |
99 | ||
100 | #define devpriv ((pcmda12_private *)(dev->private)) | |
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 | */ | |
71b5f4f1 BP |
108 | static int pcmda12_attach(struct comedi_device * dev, comedi_devconfig * it); |
109 | static int pcmda12_detach(struct comedi_device * dev); | |
647d8b45 | 110 | |
71b5f4f1 | 111 | static void zero_chans(struct comedi_device * dev); |
647d8b45 | 112 | |
139dfbdf | 113 | static struct comedi_driver driver = { |
647d8b45 CC |
114 | driver_name:"pcmda12", |
115 | module:THIS_MODULE, | |
116 | attach:pcmda12_attach, | |
117 | detach:pcmda12_detach, | |
118 | /* It is not necessary to implement the following members if you are | |
119 | * writing a driver for a ISA PnP or PCI card */ | |
120 | /* Most drivers will support multiple types of boards by | |
121 | * having an array of board structures. These were defined | |
122 | * in pcmda12_boards[] above. Note that the element 'name' | |
123 | * was first in the structure -- Comedi uses this fact to | |
124 | * extract the name of the board without knowing any details | |
125 | * about the structure except for its length. | |
126 | * When a device is attached (by comedi_config), the name | |
127 | * of the device is given to Comedi, and Comedi tries to | |
128 | * match it by going through the list of board names. If | |
129 | * there is a match, the address of the pointer is put | |
130 | * into dev->board_ptr and driver->attach() is called. | |
131 | * | |
132 | * Note that these are not necessary if you can determine | |
133 | * the type of board in software. ISA PnP, PCI, and PCMCIA | |
134 | * devices are such boards. | |
135 | */ | |
136 | board_name:&pcmda12_boards[0].name, | |
137 | offset:sizeof(pcmda12_board), | |
138 | num_names:sizeof(pcmda12_boards) / sizeof(pcmda12_board), | |
139 | }; | |
140 | ||
34c43922 | 141 | static int ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
790c5541 | 142 | comedi_insn * insn, unsigned int * data); |
34c43922 | 143 | static int ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
790c5541 | 144 | comedi_insn * insn, unsigned int * data); |
647d8b45 CC |
145 | |
146 | /* | |
147 | * Attach is called by the Comedi core to configure the driver | |
148 | * for a particular board. If you specified a board_name array | |
149 | * in the driver structure, dev->board_ptr contains that | |
150 | * address. | |
151 | */ | |
71b5f4f1 | 152 | static int pcmda12_attach(struct comedi_device * dev, comedi_devconfig * it) |
647d8b45 | 153 | { |
34c43922 | 154 | struct comedi_subdevice *s; |
647d8b45 CC |
155 | unsigned long iobase; |
156 | ||
157 | iobase = it->options[0]; | |
158 | printk("comedi%d: %s: io: %lx %s ", dev->minor, driver.driver_name, | |
159 | iobase, it->options[1] ? "simultaneous xfer mode enabled" : ""); | |
160 | ||
161 | if (!request_region(iobase, IOSIZE, driver.driver_name)) { | |
162 | printk("I/O port conflict\n"); | |
163 | return -EIO; | |
164 | } | |
165 | dev->iobase = iobase; | |
166 | ||
167 | /* | |
168 | * Initialize dev->board_name. Note that we can use the "thisboard" | |
169 | * macro now, since we just initialized it in the last line. | |
170 | */ | |
171 | dev->board_name = thisboard->name; | |
172 | ||
173 | /* | |
174 | * Allocate the private structure area. alloc_private() is a | |
175 | * convenient macro defined in comedidev.h. | |
176 | */ | |
177 | if (alloc_private(dev, sizeof(pcmda12_private)) < 0) { | |
178 | printk("cannot allocate private data structure\n"); | |
179 | return -ENOMEM; | |
180 | } | |
181 | ||
182 | devpriv->simultaneous_xfer_mode = it->options[1]; | |
183 | ||
184 | /* | |
185 | * Allocate the subdevice structures. alloc_subdevice() is a | |
186 | * convenient macro defined in comedidev.h. | |
187 | * | |
188 | * Allocate 2 subdevs (32 + 16 DIO lines) or 3 32 DIO subdevs for the | |
189 | * 96-channel version of the board. | |
190 | */ | |
191 | if (alloc_subdevices(dev, 1) < 0) { | |
192 | printk("cannot allocate subdevice data structures\n"); | |
193 | return -ENOMEM; | |
194 | } | |
195 | ||
196 | s = dev->subdevices; | |
197 | s->private = NULL; | |
198 | s->maxdata = (0x1 << BITS) - 1; | |
199 | s->range_table = &pcmda12_ranges; | |
200 | s->type = COMEDI_SUBD_AO; | |
201 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
202 | s->n_chan = CHANS; | |
203 | s->insn_write = &ao_winsn; | |
204 | s->insn_read = &ao_rinsn; | |
205 | ||
206 | zero_chans(dev); /* clear out all the registers, basically */ | |
207 | ||
208 | printk("attached\n"); | |
209 | ||
210 | return 1; | |
211 | } | |
212 | ||
213 | /* | |
214 | * _detach is called to deconfigure a device. It should deallocate | |
215 | * resources. | |
216 | * This function is also called when _attach() fails, so it should be | |
217 | * careful not to release resources that were not necessarily | |
218 | * allocated by _attach(). dev->private and dev->subdevices are | |
219 | * deallocated automatically by the core. | |
220 | */ | |
71b5f4f1 | 221 | static int pcmda12_detach(struct comedi_device * dev) |
647d8b45 CC |
222 | { |
223 | printk("comedi%d: %s: remove\n", dev->minor, driver.driver_name); | |
224 | if (dev->iobase) | |
225 | release_region(dev->iobase, IOSIZE); | |
226 | return 0; | |
227 | } | |
228 | ||
71b5f4f1 | 229 | static void zero_chans(struct comedi_device * dev) |
647d8b45 CC |
230 | { /* sets up an |
231 | ASIC chip to defaults */ | |
232 | int i; | |
233 | for (i = 0; i < CHANS; ++i) { | |
234 | /* /\* do this as one instruction?? *\/ */ | |
235 | /* outw(0, LSB_PORT(chan)); */ | |
236 | outb(0, LSB_PORT(i)); | |
237 | outb(0, MSB_PORT(i)); | |
238 | } | |
239 | inb(LSB_PORT(0)); /* update chans. */ | |
240 | } | |
241 | ||
34c43922 | 242 | static int ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
790c5541 | 243 | comedi_insn * insn, unsigned int * data) |
647d8b45 CC |
244 | { |
245 | int i; | |
246 | int chan = CR_CHAN(insn->chanspec); | |
247 | ||
248 | /* Writing a list of values to an AO channel is probably not | |
249 | * very useful, but that's how the interface is defined. */ | |
250 | for (i = 0; i < insn->n; ++i) { | |
251 | ||
252 | /* /\* do this as one instruction?? *\/ */ | |
253 | /* outw(data[i], LSB_PORT(chan)); */ | |
254 | ||
255 | /* Need to do this as two instructions due to 8-bit bus?? */ | |
256 | /* first, load the low byte */ | |
257 | outb(LSB(data[i]), LSB_PORT(chan)); | |
258 | /* next, write the high byte */ | |
259 | outb(MSB(data[i]), MSB_PORT(chan)); | |
260 | ||
261 | /* save shadow register */ | |
262 | devpriv->ao_readback[chan] = data[i]; | |
263 | ||
264 | if (!devpriv->simultaneous_xfer_mode) | |
265 | inb(LSB_PORT(chan)); | |
266 | } | |
267 | ||
268 | /* return the number of samples written */ | |
269 | return i; | |
270 | } | |
271 | ||
272 | /* AO subdevices should have a read insn as well as a write insn. | |
273 | ||
274 | Usually this means copying a value stored in devpriv->ao_readback. | |
275 | However, since this driver supports simultaneous xfer then sometimes | |
276 | this function actually accomplishes work. | |
277 | ||
278 | Simultaneaous xfer mode is accomplished by loading ALL the values | |
279 | you want for AO in all the channels, then READing off one of the AO | |
280 | registers to initiate the instantaneous simultaneous update of all | |
281 | DAC outputs, which makes all AO channels update simultaneously. | |
282 | This is useful for some control applications, I would imagine. | |
283 | */ | |
34c43922 | 284 | static int ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
790c5541 | 285 | comedi_insn * insn, unsigned int * data) |
647d8b45 CC |
286 | { |
287 | int i; | |
288 | int chan = CR_CHAN(insn->chanspec); | |
289 | ||
290 | for (i = 0; i < insn->n; i++) { | |
291 | if (devpriv->simultaneous_xfer_mode) | |
292 | inb(LSB_PORT(chan)); | |
293 | /* read back shadow register */ | |
294 | data[i] = devpriv->ao_readback[chan]; | |
295 | } | |
296 | ||
297 | return i; | |
298 | } | |
299 | ||
300 | /* | |
301 | * A convenient macro that defines init_module() and cleanup_module(), | |
302 | * as necessary. | |
303 | */ | |
304 | COMEDI_INITCLEANUP(driver); |