1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * c6xdigio.c
4 * Hardware driver for Mechatronic Systems Inc. C6x_DIGIO DSP daughter card.
5 * http://web.archive.org/web/%2A/http://robot0.ge.uiuc.edu/~spong/mecha/
6 *
7 * COMEDI - Linux Control and Measurement Device Interface
8 * Copyright (C) 1999 Dan Block
9 */
10
11 /*
12 * Driver: c6xdigio
13 * Description: Mechatronic Systems Inc. C6x_DIGIO DSP daughter card
14 * Author: Dan Block
15 * Status: unknown
16 * Devices: [Mechatronic Systems Inc.] C6x_DIGIO DSP daughter card (c6xdigio)
17 * Updated: Sun Nov 20 20:18:34 EST 2005
18 *
19 * Configuration Options:
20 * [0] - base address
21 */
22
23 #include <linux/kernel.h>
24 #include <linux/module.h>
25 #include <linux/sched.h>
26 #include <linux/mm.h>
27 #include <linux/errno.h>
28 #include <linux/interrupt.h>
29 #include <linux/timex.h>
30 #include <linux/timer.h>
31 #include <linux/io.h>
32 #include <linux/pnp.h>
33
34 #include "../comedidev.h"
35
36 /*
37 * Register I/O map
38 */
39 #define C6XDIGIO_DATA_REG 0x00
40 #define C6XDIGIO_DATA_CHAN(x) (((x) + 1) << 4)
41 #define C6XDIGIO_DATA_PWM BIT(5)
42 #define C6XDIGIO_DATA_ENCODER BIT(6)
43 #define C6XDIGIO_STATUS_REG 0x01
44 #define C6XDIGIO_CTRL_REG 0x02
45
46 #define C6XDIGIO_TIME_OUT 20
47
c6xdigio_chk_status(struct comedi_device * dev,unsigned long context)48 static int c6xdigio_chk_status(struct comedi_device *dev, unsigned long context)
49 {
50 unsigned int status;
51 int timeout = 0;
52
53 do {
54 status = inb(dev->iobase + C6XDIGIO_STATUS_REG);
55 if ((status & 0x80) != context)
56 return 0;
57 timeout++;
58 } while (timeout < C6XDIGIO_TIME_OUT);
59
60 return -EBUSY;
61 }
62
c6xdigio_write_data(struct comedi_device * dev,unsigned int val,unsigned int status)63 static int c6xdigio_write_data(struct comedi_device *dev,
64 unsigned int val, unsigned int status)
65 {
66 outb_p(val, dev->iobase + C6XDIGIO_DATA_REG);
67 return c6xdigio_chk_status(dev, status);
68 }
69
c6xdigio_get_encoder_bits(struct comedi_device * dev,unsigned int * bits,unsigned int cmd,unsigned int status)70 static int c6xdigio_get_encoder_bits(struct comedi_device *dev,
71 unsigned int *bits,
72 unsigned int cmd,
73 unsigned int status)
74 {
75 unsigned int val;
76
77 val = inb(dev->iobase + C6XDIGIO_STATUS_REG);
78 val >>= 3;
79 val &= 0x07;
80
81 *bits = val;
82
83 return c6xdigio_write_data(dev, cmd, status);
84 }
85
c6xdigio_pwm_write(struct comedi_device * dev,unsigned int chan,unsigned int val)86 static void c6xdigio_pwm_write(struct comedi_device *dev,
87 unsigned int chan, unsigned int val)
88 {
89 unsigned int cmd = C6XDIGIO_DATA_PWM | C6XDIGIO_DATA_CHAN(chan);
90 unsigned int bits;
91
92 if (val > 498)
93 val = 498;
94 if (val < 2)
95 val = 2;
96
97 bits = (val >> 0) & 0x03;
98 c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
99 bits = (val >> 2) & 0x03;
100 c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80);
101 bits = (val >> 4) & 0x03;
102 c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
103 bits = (val >> 6) & 0x03;
104 c6xdigio_write_data(dev, cmd | bits | (1 << 2), 0x80);
105 bits = (val >> 8) & 0x03;
106 c6xdigio_write_data(dev, cmd | bits | (0 << 2), 0x00);
107
108 c6xdigio_write_data(dev, 0x00, 0x80);
109 }
110
c6xdigio_encoder_read(struct comedi_device * dev,unsigned int chan)111 static int c6xdigio_encoder_read(struct comedi_device *dev,
112 unsigned int chan)
113 {
114 unsigned int cmd = C6XDIGIO_DATA_ENCODER | C6XDIGIO_DATA_CHAN(chan);
115 unsigned int val = 0;
116 unsigned int bits;
117
118 c6xdigio_write_data(dev, cmd, 0x00);
119
120 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
121 val |= (bits << 0);
122
123 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
124 val |= (bits << 3);
125
126 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
127 val |= (bits << 6);
128
129 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
130 val |= (bits << 9);
131
132 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
133 val |= (bits << 12);
134
135 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
136 val |= (bits << 15);
137
138 c6xdigio_get_encoder_bits(dev, &bits, cmd | (1 << 2), 0x80);
139 val |= (bits << 18);
140
141 c6xdigio_get_encoder_bits(dev, &bits, cmd | (0 << 2), 0x00);
142 val |= (bits << 21);
143
144 c6xdigio_write_data(dev, 0x00, 0x80);
145
146 return val;
147 }
148
c6xdigio_pwm_insn_write(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)149 static int c6xdigio_pwm_insn_write(struct comedi_device *dev,
150 struct comedi_subdevice *s,
151 struct comedi_insn *insn,
152 unsigned int *data)
153 {
154 unsigned int chan = CR_CHAN(insn->chanspec);
155 unsigned int val = (s->state >> (16 * chan)) & 0xffff;
156 int i;
157
158 for (i = 0; i < insn->n; i++) {
159 val = data[i];
160 c6xdigio_pwm_write(dev, chan, val);
161 }
162
163 /*
164 * There are only 2 PWM channels and they have a maxdata of 500.
165 * Instead of allocating private data to save the values in for
166 * readback this driver just packs the values for the two channels
167 * in the s->state.
168 */
169 s->state &= (0xffff << (16 * chan));
170 s->state |= (val << (16 * chan));
171
172 return insn->n;
173 }
174
c6xdigio_pwm_insn_read(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)175 static int c6xdigio_pwm_insn_read(struct comedi_device *dev,
176 struct comedi_subdevice *s,
177 struct comedi_insn *insn,
178 unsigned int *data)
179 {
180 unsigned int chan = CR_CHAN(insn->chanspec);
181 unsigned int val;
182 int i;
183
184 val = (s->state >> (16 * chan)) & 0xffff;
185
186 for (i = 0; i < insn->n; i++)
187 data[i] = val;
188
189 return insn->n;
190 }
191
c6xdigio_encoder_insn_read(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)192 static int c6xdigio_encoder_insn_read(struct comedi_device *dev,
193 struct comedi_subdevice *s,
194 struct comedi_insn *insn,
195 unsigned int *data)
196 {
197 unsigned int chan = CR_CHAN(insn->chanspec);
198 unsigned int val;
199 int i;
200
201 for (i = 0; i < insn->n; i++) {
202 val = c6xdigio_encoder_read(dev, chan);
203
204 /* munge two's complement value to offset binary */
205 data[i] = comedi_offset_munge(s, val);
206 }
207
208 return insn->n;
209 }
210
c6xdigio_init(struct comedi_device * dev)211 static void c6xdigio_init(struct comedi_device *dev)
212 {
213 /* Initialize the PWM */
214 c6xdigio_write_data(dev, 0x70, 0x00);
215 c6xdigio_write_data(dev, 0x74, 0x80);
216 c6xdigio_write_data(dev, 0x70, 0x00);
217 c6xdigio_write_data(dev, 0x00, 0x80);
218
219 /* Reset the encoders */
220 c6xdigio_write_data(dev, 0x68, 0x00);
221 c6xdigio_write_data(dev, 0x6c, 0x80);
222 c6xdigio_write_data(dev, 0x68, 0x00);
223 c6xdigio_write_data(dev, 0x00, 0x80);
224 }
225
226 static const struct pnp_device_id c6xdigio_pnp_tbl[] = {
227 /* Standard LPT Printer Port */
228 {.id = "PNP0400", .driver_data = 0},
229 /* ECP Printer Port */
230 {.id = "PNP0401", .driver_data = 0},
231 {}
232 };
233
234 static struct pnp_driver c6xdigio_pnp_driver = {
235 .name = "c6xdigio",
236 .id_table = c6xdigio_pnp_tbl,
237 };
238
c6xdigio_attach(struct comedi_device * dev,struct comedi_devconfig * it)239 static int c6xdigio_attach(struct comedi_device *dev,
240 struct comedi_devconfig *it)
241 {
242 struct comedi_subdevice *s;
243 int ret;
244
245 ret = comedi_request_region(dev, it->options[0], 0x03);
246 if (ret)
247 return ret;
248
249 ret = comedi_alloc_subdevices(dev, 2);
250 if (ret)
251 return ret;
252
253 /* Make sure that PnP ports get activated */
254 pnp_register_driver(&c6xdigio_pnp_driver);
255
256 s = &dev->subdevices[0];
257 /* pwm output subdevice */
258 s->type = COMEDI_SUBD_PWM;
259 s->subdev_flags = SDF_WRITABLE;
260 s->n_chan = 2;
261 s->maxdata = 500;
262 s->range_table = &range_unknown;
263 s->insn_write = c6xdigio_pwm_insn_write;
264 s->insn_read = c6xdigio_pwm_insn_read;
265
266 s = &dev->subdevices[1];
267 /* encoder (counter) subdevice */
268 s->type = COMEDI_SUBD_COUNTER;
269 s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
270 s->n_chan = 2;
271 s->maxdata = 0xffffff;
272 s->range_table = &range_unknown;
273 s->insn_read = c6xdigio_encoder_insn_read;
274
275 /* I will call this init anyway but more than likely the DSP board */
276 /* will not be connected when device driver is loaded. */
277 c6xdigio_init(dev);
278
279 return 0;
280 }
281
c6xdigio_detach(struct comedi_device * dev)282 static void c6xdigio_detach(struct comedi_device *dev)
283 {
284 comedi_legacy_detach(dev);
285 pnp_unregister_driver(&c6xdigio_pnp_driver);
286 }
287
288 static struct comedi_driver c6xdigio_driver = {
289 .driver_name = "c6xdigio",
290 .module = THIS_MODULE,
291 .attach = c6xdigio_attach,
292 .detach = c6xdigio_detach,
293 };
294 module_comedi_driver(c6xdigio_driver);
295
296 MODULE_AUTHOR("Comedi https://www.comedi.org");
297 MODULE_DESCRIPTION("Comedi driver for the C6x_DIGIO DSP daughter card");
298 MODULE_LICENSE("GPL");
299