1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * addi_apci_16xx.c |
4 | * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. |
5 | * Project manager: S. Weber |
6 | * |
7 | * ADDI-DATA GmbH |
8 | * Dieselstrasse 3 |
9 | * D-77833 Ottersweier |
10 | * Tel: +19(0)7223/9493-0 |
11 | * Fax: +49(0)7223/9493-92 |
12 | * http://www.addi-data.com |
13 | * info@addi-data.com |
14 | */ |
15 | |
16 | #include <linux/module.h> |
17 | #include <linux/comedi/comedi_pci.h> |
18 | |
19 | /* |
20 | * Register I/O map |
21 | */ |
22 | #define APCI16XX_IN_REG(x) (((x) * 4) + 0x08) |
23 | #define APCI16XX_OUT_REG(x) (((x) * 4) + 0x14) |
24 | #define APCI16XX_DIR_REG(x) (((x) * 4) + 0x20) |
25 | |
26 | enum apci16xx_boardid { |
27 | BOARD_APCI1648, |
28 | BOARD_APCI1696, |
29 | }; |
30 | |
31 | struct apci16xx_boardinfo { |
32 | const char *name; |
33 | int n_chan; |
34 | }; |
35 | |
36 | static const struct apci16xx_boardinfo apci16xx_boardtypes[] = { |
37 | [BOARD_APCI1648] = { |
38 | .name = "apci1648" , |
39 | .n_chan = 48, /* 2 subdevices */ |
40 | }, |
41 | [BOARD_APCI1696] = { |
42 | .name = "apci1696" , |
43 | .n_chan = 96, /* 3 subdevices */ |
44 | }, |
45 | }; |
46 | |
47 | static int apci16xx_insn_config(struct comedi_device *dev, |
48 | struct comedi_subdevice *s, |
49 | struct comedi_insn *insn, |
50 | unsigned int *data) |
51 | { |
52 | unsigned int chan = CR_CHAN(insn->chanspec); |
53 | unsigned int mask; |
54 | int ret; |
55 | |
56 | if (chan < 8) |
57 | mask = 0x000000ff; |
58 | else if (chan < 16) |
59 | mask = 0x0000ff00; |
60 | else if (chan < 24) |
61 | mask = 0x00ff0000; |
62 | else |
63 | mask = 0xff000000; |
64 | |
65 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
66 | if (ret) |
67 | return ret; |
68 | |
69 | outl(value: s->io_bits, port: dev->iobase + APCI16XX_DIR_REG(s->index)); |
70 | |
71 | return insn->n; |
72 | } |
73 | |
74 | static int apci16xx_dio_insn_bits(struct comedi_device *dev, |
75 | struct comedi_subdevice *s, |
76 | struct comedi_insn *insn, |
77 | unsigned int *data) |
78 | { |
79 | if (comedi_dio_update_state(s, data)) |
80 | outl(value: s->state, port: dev->iobase + APCI16XX_OUT_REG(s->index)); |
81 | |
82 | data[1] = inl(port: dev->iobase + APCI16XX_IN_REG(s->index)); |
83 | |
84 | return insn->n; |
85 | } |
86 | |
87 | static int apci16xx_auto_attach(struct comedi_device *dev, |
88 | unsigned long context) |
89 | { |
90 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
91 | const struct apci16xx_boardinfo *board = NULL; |
92 | struct comedi_subdevice *s; |
93 | unsigned int n_subdevs; |
94 | unsigned int last; |
95 | int i; |
96 | int ret; |
97 | |
98 | if (context < ARRAY_SIZE(apci16xx_boardtypes)) |
99 | board = &apci16xx_boardtypes[context]; |
100 | if (!board) |
101 | return -ENODEV; |
102 | dev->board_ptr = board; |
103 | dev->board_name = board->name; |
104 | |
105 | ret = comedi_pci_enable(dev); |
106 | if (ret) |
107 | return ret; |
108 | |
109 | dev->iobase = pci_resource_start(pcidev, 0); |
110 | |
111 | /* |
112 | * Work out the number of subdevices needed to support all the |
113 | * digital i/o channels on the board. Each subdevice supports |
114 | * up to 32 channels. |
115 | */ |
116 | n_subdevs = board->n_chan / 32; |
117 | if ((n_subdevs * 32) < board->n_chan) { |
118 | last = board->n_chan - (n_subdevs * 32); |
119 | n_subdevs++; |
120 | } else { |
121 | last = 0; |
122 | } |
123 | |
124 | ret = comedi_alloc_subdevices(dev, num_subdevices: n_subdevs); |
125 | if (ret) |
126 | return ret; |
127 | |
128 | /* Initialize the TTL digital i/o subdevices */ |
129 | for (i = 0; i < n_subdevs; i++) { |
130 | s = &dev->subdevices[i]; |
131 | s->type = COMEDI_SUBD_DIO; |
132 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; |
133 | s->n_chan = ((i * 32) < board->n_chan) ? 32 : last; |
134 | s->maxdata = 1; |
135 | s->range_table = &range_digital; |
136 | s->insn_config = apci16xx_insn_config; |
137 | s->insn_bits = apci16xx_dio_insn_bits; |
138 | |
139 | /* Default all channels to inputs */ |
140 | s->io_bits = 0; |
141 | outl(value: s->io_bits, port: dev->iobase + APCI16XX_DIR_REG(i)); |
142 | } |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static struct comedi_driver apci16xx_driver = { |
148 | .driver_name = "addi_apci_16xx" , |
149 | .module = THIS_MODULE, |
150 | .auto_attach = apci16xx_auto_attach, |
151 | .detach = comedi_pci_detach, |
152 | }; |
153 | |
154 | static int apci16xx_pci_probe(struct pci_dev *dev, |
155 | const struct pci_device_id *id) |
156 | { |
157 | return comedi_pci_auto_config(pcidev: dev, driver: &apci16xx_driver, context: id->driver_data); |
158 | } |
159 | |
160 | static const struct pci_device_id apci16xx_pci_table[] = { |
161 | { PCI_VDEVICE(ADDIDATA, 0x1009), BOARD_APCI1648 }, |
162 | { PCI_VDEVICE(ADDIDATA, 0x100a), BOARD_APCI1696 }, |
163 | { 0 } |
164 | }; |
165 | MODULE_DEVICE_TABLE(pci, apci16xx_pci_table); |
166 | |
167 | static struct pci_driver apci16xx_pci_driver = { |
168 | .name = "addi_apci_16xx" , |
169 | .id_table = apci16xx_pci_table, |
170 | .probe = apci16xx_pci_probe, |
171 | .remove = comedi_pci_auto_unconfig, |
172 | }; |
173 | module_comedi_pci_driver(apci16xx_driver, apci16xx_pci_driver); |
174 | |
175 | MODULE_DESCRIPTION("ADDI-DATA APCI-1648/1696, TTL I/O boards" ); |
176 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
177 | MODULE_LICENSE("GPL" ); |
178 | |