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 | ||
39eb312d BP |
83 | struct pcmda12_private { |
84 | ||
790c5541 | 85 | unsigned int ao_readback[CHANS]; |
647d8b45 | 86 | int simultaneous_xfer_mode; |
39eb312d BP |
87 | }; |
88 | ||
39eb312d | 89 | #define devpriv ((struct pcmda12_private *)(dev->private)) |
647d8b45 | 90 | |
924f4685 HS |
91 | static void zero_chans(struct comedi_device *dev) |
92 | { /* sets up an | |
93 | ASIC chip to defaults */ | |
94 | int i; | |
95 | for (i = 0; i < CHANS; ++i) { | |
96 | /* /\* do this as one instruction?? *\/ */ | |
97 | /* outw(0, LSB_PORT(chan)); */ | |
98 | outb(0, LSB_PORT(i)); | |
99 | outb(0, MSB_PORT(i)); | |
100 | } | |
101 | inb(LSB_PORT(0)); /* update chans. */ | |
102 | } | |
647d8b45 | 103 | |
924f4685 HS |
104 | static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
105 | struct comedi_insn *insn, unsigned int *data) | |
106 | { | |
107 | int i; | |
108 | int chan = CR_CHAN(insn->chanspec); | |
647d8b45 | 109 | |
924f4685 HS |
110 | /* Writing a list of values to an AO channel is probably not |
111 | * very useful, but that's how the interface is defined. */ | |
112 | for (i = 0; i < insn->n; ++i) { | |
647d8b45 | 113 | |
924f4685 HS |
114 | /* /\* do this as one instruction?? *\/ */ |
115 | /* outw(data[i], LSB_PORT(chan)); */ | |
116 | ||
117 | /* Need to do this as two instructions due to 8-bit bus?? */ | |
118 | /* first, load the low byte */ | |
119 | outb(LSB(data[i]), LSB_PORT(chan)); | |
120 | /* next, write the high byte */ | |
121 | outb(MSB(data[i]), MSB_PORT(chan)); | |
122 | ||
123 | /* save shadow register */ | |
124 | devpriv->ao_readback[chan] = data[i]; | |
125 | ||
126 | if (!devpriv->simultaneous_xfer_mode) | |
127 | inb(LSB_PORT(chan)); | |
128 | } | |
129 | ||
130 | /* return the number of samples written */ | |
131 | return i; | |
132 | } | |
133 | ||
134 | /* AO subdevices should have a read insn as well as a write insn. | |
135 | ||
136 | Usually this means copying a value stored in devpriv->ao_readback. | |
137 | However, since this driver supports simultaneous xfer then sometimes | |
138 | this function actually accomplishes work. | |
139 | ||
140 | Simultaneaous xfer mode is accomplished by loading ALL the values | |
141 | you want for AO in all the channels, then READing off one of the AO | |
142 | registers to initiate the instantaneous simultaneous update of all | |
143 | DAC outputs, which makes all AO channels update simultaneously. | |
144 | This is useful for some control applications, I would imagine. | |
145 | */ | |
da91b269 | 146 | static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
924f4685 HS |
147 | struct comedi_insn *insn, unsigned int *data) |
148 | { | |
149 | int i; | |
150 | int chan = CR_CHAN(insn->chanspec); | |
151 | ||
152 | for (i = 0; i < insn->n; i++) { | |
153 | if (devpriv->simultaneous_xfer_mode) | |
154 | inb(LSB_PORT(chan)); | |
155 | /* read back shadow register */ | |
156 | data[i] = devpriv->ao_readback[chan]; | |
157 | } | |
158 | ||
159 | return i; | |
160 | } | |
647d8b45 | 161 | |
0a85b6f0 MT |
162 | static int pcmda12_attach(struct comedi_device *dev, |
163 | struct comedi_devconfig *it) | |
647d8b45 | 164 | { |
2fbffee0 | 165 | const struct pcmda12_board *board = comedi_board(dev); |
34c43922 | 166 | struct comedi_subdevice *s; |
647d8b45 | 167 | unsigned long iobase; |
8b6c5694 | 168 | int ret; |
647d8b45 CC |
169 | |
170 | iobase = it->options[0]; | |
8b83e005 | 171 | printk(KERN_INFO |
2d2111ea | 172 | "comedi%d: %s: io: %lx %s ", dev->minor, dev->driver->driver_name, |
0a85b6f0 | 173 | iobase, it->options[1] ? "simultaneous xfer mode enabled" : ""); |
647d8b45 | 174 | |
2d2111ea | 175 | if (!request_region(iobase, IOSIZE, dev->driver->driver_name)) { |
647d8b45 CC |
176 | printk("I/O port conflict\n"); |
177 | return -EIO; | |
178 | } | |
179 | dev->iobase = iobase; | |
180 | ||
2fbffee0 | 181 | dev->board_name = board->name; |
647d8b45 CC |
182 | |
183 | /* | |
184 | * Allocate the private structure area. alloc_private() is a | |
185 | * convenient macro defined in comedidev.h. | |
186 | */ | |
39eb312d | 187 | if (alloc_private(dev, sizeof(struct pcmda12_private)) < 0) { |
8b83e005 | 188 | printk(KERN_ERR "cannot allocate private data structure\n"); |
647d8b45 CC |
189 | return -ENOMEM; |
190 | } | |
191 | ||
192 | devpriv->simultaneous_xfer_mode = it->options[1]; | |
193 | ||
8b6c5694 HS |
194 | ret = comedi_alloc_subdevices(dev, 1); |
195 | if (ret) | |
196 | return ret; | |
647d8b45 CC |
197 | |
198 | s = dev->subdevices; | |
199 | s->private = NULL; | |
200 | s->maxdata = (0x1 << BITS) - 1; | |
201 | s->range_table = &pcmda12_ranges; | |
202 | s->type = COMEDI_SUBD_AO; | |
203 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
204 | s->n_chan = CHANS; | |
205 | s->insn_write = &ao_winsn; | |
206 | s->insn_read = &ao_rinsn; | |
207 | ||
208 | zero_chans(dev); /* clear out all the registers, basically */ | |
209 | ||
8b83e005 | 210 | printk(KERN_INFO "attached\n"); |
647d8b45 CC |
211 | |
212 | return 1; | |
213 | } | |
214 | ||
484ecc95 | 215 | static void pcmda12_detach(struct comedi_device *dev) |
647d8b45 | 216 | { |
647d8b45 CC |
217 | if (dev->iobase) |
218 | release_region(dev->iobase, IOSIZE); | |
647d8b45 CC |
219 | } |
220 | ||
924f4685 HS |
221 | static const struct pcmda12_board pcmda12_boards[] = { |
222 | { | |
223 | .name = "pcmda12", | |
224 | }, | |
225 | }; | |
647d8b45 | 226 | |
294f930d | 227 | static struct comedi_driver pcmda12_driver = { |
924f4685 HS |
228 | .driver_name = "pcmda12", |
229 | .module = THIS_MODULE, | |
230 | .attach = pcmda12_attach, | |
231 | .detach = pcmda12_detach, | |
232 | .board_name = &pcmda12_boards[0].name, | |
233 | .offset = sizeof(struct pcmda12_board), | |
234 | .num_names = ARRAY_SIZE(pcmda12_boards), | |
235 | }; | |
294f930d | 236 | module_comedi_driver(pcmda12_driver); |
90f703d3 AT |
237 | |
238 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
239 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
240 | MODULE_LICENSE("GPL"); |