1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * adl_pci6208.c |
4 | * Comedi driver for ADLink 6208 series cards |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
8 | */ |
9 | |
10 | /* |
11 | * Driver: adl_pci6208 |
12 | * Description: ADLink PCI-6208/6216 Series Multi-channel Analog Output Cards |
13 | * Devices: [ADLink] PCI-6208 (adl_pci6208), PCI-6216 |
14 | * Author: nsyeow <nsyeow@pd.jaring.my> |
15 | * Updated: Wed, 11 Feb 2015 11:37:18 +0000 |
16 | * Status: untested |
17 | * |
18 | * Configuration Options: not applicable, uses PCI auto config |
19 | * |
20 | * All supported devices share the same PCI device ID and are treated as a |
21 | * PCI-6216 with 16 analog output channels. On a PCI-6208, the upper 8 |
22 | * channels exist in registers, but don't go to DAC chips. |
23 | */ |
24 | |
25 | #include <linux/module.h> |
26 | #include <linux/delay.h> |
27 | #include <linux/comedi/comedi_pci.h> |
28 | |
29 | /* |
30 | * PCI-6208/6216-GL register map |
31 | */ |
32 | #define PCI6208_AO_CONTROL(x) (0x00 + (2 * (x))) |
33 | #define PCI6208_AO_STATUS 0x00 |
34 | #define PCI6208_AO_STATUS_DATA_SEND BIT(0) |
35 | #define PCI6208_DIO 0x40 |
36 | #define PCI6208_DIO_DO_MASK (0x0f) |
37 | #define PCI6208_DIO_DO_SHIFT (0) |
38 | #define PCI6208_DIO_DI_MASK (0xf0) |
39 | #define PCI6208_DIO_DI_SHIFT (4) |
40 | |
41 | static int pci6208_ao_eoc(struct comedi_device *dev, |
42 | struct comedi_subdevice *s, |
43 | struct comedi_insn *insn, |
44 | unsigned long context) |
45 | { |
46 | unsigned int status; |
47 | |
48 | status = inw(port: dev->iobase + PCI6208_AO_STATUS); |
49 | if ((status & PCI6208_AO_STATUS_DATA_SEND) == 0) |
50 | return 0; |
51 | return -EBUSY; |
52 | } |
53 | |
54 | static int pci6208_ao_insn_write(struct comedi_device *dev, |
55 | struct comedi_subdevice *s, |
56 | struct comedi_insn *insn, |
57 | unsigned int *data) |
58 | { |
59 | unsigned int chan = CR_CHAN(insn->chanspec); |
60 | int ret; |
61 | int i; |
62 | |
63 | for (i = 0; i < insn->n; i++) { |
64 | unsigned int val = data[i]; |
65 | |
66 | /* D/A transfer rate is 2.2us */ |
67 | ret = comedi_timeout(dev, s, insn, cb: pci6208_ao_eoc, context: 0); |
68 | if (ret) |
69 | return ret; |
70 | |
71 | /* the hardware expects two's complement values */ |
72 | outw(value: comedi_offset_munge(s, val), |
73 | port: dev->iobase + PCI6208_AO_CONTROL(chan)); |
74 | |
75 | s->readback[chan] = val; |
76 | } |
77 | |
78 | return insn->n; |
79 | } |
80 | |
81 | static int pci6208_di_insn_bits(struct comedi_device *dev, |
82 | struct comedi_subdevice *s, |
83 | struct comedi_insn *insn, |
84 | unsigned int *data) |
85 | { |
86 | unsigned int val; |
87 | |
88 | val = inw(port: dev->iobase + PCI6208_DIO); |
89 | val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT; |
90 | |
91 | data[1] = val; |
92 | |
93 | return insn->n; |
94 | } |
95 | |
96 | static int pci6208_do_insn_bits(struct comedi_device *dev, |
97 | struct comedi_subdevice *s, |
98 | struct comedi_insn *insn, |
99 | unsigned int *data) |
100 | { |
101 | if (comedi_dio_update_state(s, data)) |
102 | outw(value: s->state, port: dev->iobase + PCI6208_DIO); |
103 | |
104 | data[1] = s->state; |
105 | |
106 | return insn->n; |
107 | } |
108 | |
109 | static int pci6208_auto_attach(struct comedi_device *dev, |
110 | unsigned long context_unused) |
111 | { |
112 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
113 | struct comedi_subdevice *s; |
114 | unsigned int val; |
115 | int ret; |
116 | |
117 | ret = comedi_pci_enable(dev); |
118 | if (ret) |
119 | return ret; |
120 | dev->iobase = pci_resource_start(pcidev, 2); |
121 | |
122 | ret = comedi_alloc_subdevices(dev, num_subdevices: 3); |
123 | if (ret) |
124 | return ret; |
125 | |
126 | s = &dev->subdevices[0]; |
127 | /* analog output subdevice */ |
128 | s->type = COMEDI_SUBD_AO; |
129 | s->subdev_flags = SDF_WRITABLE; |
130 | s->n_chan = 16; /* Only 8 usable on PCI-6208 */ |
131 | s->maxdata = 0xffff; |
132 | s->range_table = &range_bipolar10; |
133 | s->insn_write = pci6208_ao_insn_write; |
134 | |
135 | ret = comedi_alloc_subdev_readback(s); |
136 | if (ret) |
137 | return ret; |
138 | |
139 | s = &dev->subdevices[1]; |
140 | /* digital input subdevice */ |
141 | s->type = COMEDI_SUBD_DI; |
142 | s->subdev_flags = SDF_READABLE; |
143 | s->n_chan = 4; |
144 | s->maxdata = 1; |
145 | s->range_table = &range_digital; |
146 | s->insn_bits = pci6208_di_insn_bits; |
147 | |
148 | s = &dev->subdevices[2]; |
149 | /* digital output subdevice */ |
150 | s->type = COMEDI_SUBD_DO; |
151 | s->subdev_flags = SDF_WRITABLE; |
152 | s->n_chan = 4; |
153 | s->maxdata = 1; |
154 | s->range_table = &range_digital; |
155 | s->insn_bits = pci6208_do_insn_bits; |
156 | |
157 | /* |
158 | * Get the read back signals from the digital outputs |
159 | * and save it as the initial state for the subdevice. |
160 | */ |
161 | val = inw(port: dev->iobase + PCI6208_DIO); |
162 | val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT; |
163 | s->state = val; |
164 | |
165 | return 0; |
166 | } |
167 | |
168 | static struct comedi_driver adl_pci6208_driver = { |
169 | .driver_name = "adl_pci6208" , |
170 | .module = THIS_MODULE, |
171 | .auto_attach = pci6208_auto_attach, |
172 | .detach = comedi_pci_detach, |
173 | }; |
174 | |
175 | static int adl_pci6208_pci_probe(struct pci_dev *dev, |
176 | const struct pci_device_id *id) |
177 | { |
178 | return comedi_pci_auto_config(pcidev: dev, driver: &adl_pci6208_driver, |
179 | context: id->driver_data); |
180 | } |
181 | |
182 | static const struct pci_device_id adl_pci6208_pci_table[] = { |
183 | { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x6208) }, |
184 | { PCI_DEVICE_SUB(PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, |
185 | 0x9999, 0x6208) }, |
186 | { 0 } |
187 | }; |
188 | MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table); |
189 | |
190 | static struct pci_driver adl_pci6208_pci_driver = { |
191 | .name = "adl_pci6208" , |
192 | .id_table = adl_pci6208_pci_table, |
193 | .probe = adl_pci6208_pci_probe, |
194 | .remove = comedi_pci_auto_unconfig, |
195 | }; |
196 | module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver); |
197 | |
198 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
199 | MODULE_DESCRIPTION("Comedi driver for ADLink 6208 series cards" ); |
200 | MODULE_LICENSE("GPL" ); |
201 | |