1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * comedi/drivers/ni_usb6501.c
4 * Comedi driver for National Instruments USB-6501
5 *
6 * COMEDI - Linux Control and Measurement Device Interface
7 * Copyright (C) 2014 Luca Ellero <luca.ellero@brickedbrain.com>
8 */
9
10/*
11 * Driver: ni_usb6501
12 * Description: National Instruments USB-6501 module
13 * Devices: [National Instruments] USB-6501 (ni_usb6501)
14 * Author: Luca Ellero <luca.ellero@brickedbrain.com>
15 * Updated: 8 Sep 2014
16 * Status: works
17 *
18 *
19 * Configuration Options:
20 * none
21 */
22
23/*
24 * NI-6501 - USB PROTOCOL DESCRIPTION
25 *
26 * Every command is composed by two USB packets:
27 * - request (out)
28 * - response (in)
29 *
30 * Every packet is at least 12 bytes long, here is the meaning of
31 * every field (all values are hex):
32 *
33 * byte 0 is always 00
34 * byte 1 is always 01
35 * byte 2 is always 00
36 * byte 3 is the total packet length
37 *
38 * byte 4 is always 00
39 * byte 5 is the total packet length - 4
40 * byte 6 is always 01
41 * byte 7 is the command
42 *
43 * byte 8 is 02 (request) or 00 (response)
44 * byte 9 is 00 (response) or 10 (port request) or 20 (counter request)
45 * byte 10 is always 00
46 * byte 11 is 00 (request) or 02 (response)
47 *
48 * PORT PACKETS
49 *
50 * CMD: 0xE READ_PORT
51 * REQ: 00 01 00 10 00 0C 01 0E 02 10 00 00 00 03 <PORT> 00
52 * RES: 00 01 00 10 00 0C 01 00 00 00 00 02 00 03 <BMAP> 00
53 *
54 * CMD: 0xF WRITE_PORT
55 * REQ: 00 01 00 14 00 10 01 0F 02 10 00 00 00 03 <PORT> 00 03 <BMAP> 00 00
56 * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
57 *
58 * CMD: 0x12 SET_PORT_DIR (0 = input, 1 = output)
59 * REQ: 00 01 00 18 00 14 01 12 02 10 00 00
60 * 00 05 <PORT 0> <PORT 1> <PORT 2> 00 05 00 00 00 00 00
61 * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
62 *
63 * COUNTER PACKETS
64 *
65 * CMD 0x9: START_COUNTER
66 * REQ: 00 01 00 0C 00 08 01 09 02 20 00 00
67 * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
68 *
69 * CMD 0xC: STOP_COUNTER
70 * REQ: 00 01 00 0C 00 08 01 0C 02 20 00 00
71 * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
72 *
73 * CMD 0xE: READ_COUNTER
74 * REQ: 00 01 00 0C 00 08 01 0E 02 20 00 00
75 * RES: 00 01 00 10 00 0C 01 00 00 00 00 02 <u32 counter value, Big Endian>
76 *
77 * CMD 0xF: WRITE_COUNTER
78 * REQ: 00 01 00 10 00 0C 01 0F 02 20 00 00 <u32 counter value, Big Endian>
79 * RES: 00 01 00 0C 00 08 01 00 00 00 00 02
80 *
81 *
82 * Please visit https://www.brickedbrain.com if you need
83 * additional information or have any questions.
84 *
85 */
86
87#include <linux/kernel.h>
88#include <linux/module.h>
89#include <linux/slab.h>
90#include <linux/comedi/comedi_usb.h>
91
92#define NI6501_TIMEOUT 1000
93
94/* Port request packets */
95static const u8 READ_PORT_REQUEST[] = {0x00, 0x01, 0x00, 0x10,
96 0x00, 0x0C, 0x01, 0x0E,
97 0x02, 0x10, 0x00, 0x00,
98 0x00, 0x03, 0x00, 0x00};
99
100static const u8 WRITE_PORT_REQUEST[] = {0x00, 0x01, 0x00, 0x14,
101 0x00, 0x10, 0x01, 0x0F,
102 0x02, 0x10, 0x00, 0x00,
103 0x00, 0x03, 0x00, 0x00,
104 0x03, 0x00, 0x00, 0x00};
105
106static const u8 SET_PORT_DIR_REQUEST[] = {0x00, 0x01, 0x00, 0x18,
107 0x00, 0x14, 0x01, 0x12,
108 0x02, 0x10, 0x00, 0x00,
109 0x00, 0x05, 0x00, 0x00,
110 0x00, 0x00, 0x05, 0x00,
111 0x00, 0x00, 0x00, 0x00};
112
113/* Counter request packets */
114static const u8 START_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C,
115 0x00, 0x08, 0x01, 0x09,
116 0x02, 0x20, 0x00, 0x00};
117
118static const u8 STOP_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C,
119 0x00, 0x08, 0x01, 0x0C,
120 0x02, 0x20, 0x00, 0x00};
121
122static const u8 READ_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x0C,
123 0x00, 0x08, 0x01, 0x0E,
124 0x02, 0x20, 0x00, 0x00};
125
126static const u8 WRITE_COUNTER_REQUEST[] = {0x00, 0x01, 0x00, 0x10,
127 0x00, 0x0C, 0x01, 0x0F,
128 0x02, 0x20, 0x00, 0x00,
129 0x00, 0x00, 0x00, 0x00};
130
131/* Response packets */
132static const u8 GENERIC_RESPONSE[] = {0x00, 0x01, 0x00, 0x0C,
133 0x00, 0x08, 0x01, 0x00,
134 0x00, 0x00, 0x00, 0x02};
135
136static const u8 READ_PORT_RESPONSE[] = {0x00, 0x01, 0x00, 0x10,
137 0x00, 0x0C, 0x01, 0x00,
138 0x00, 0x00, 0x00, 0x02,
139 0x00, 0x03, 0x00, 0x00};
140
141static const u8 READ_COUNTER_RESPONSE[] = {0x00, 0x01, 0x00, 0x10,
142 0x00, 0x0C, 0x01, 0x00,
143 0x00, 0x00, 0x00, 0x02,
144 0x00, 0x00, 0x00, 0x00};
145
146/* Largest supported packets */
147static const size_t TX_MAX_SIZE = sizeof(SET_PORT_DIR_REQUEST);
148static const size_t RX_MAX_SIZE = sizeof(READ_PORT_RESPONSE);
149
150enum commands {
151 READ_PORT,
152 WRITE_PORT,
153 SET_PORT_DIR,
154 START_COUNTER,
155 STOP_COUNTER,
156 READ_COUNTER,
157 WRITE_COUNTER
158};
159
160struct ni6501_private {
161 struct usb_endpoint_descriptor *ep_rx;
162 struct usb_endpoint_descriptor *ep_tx;
163 struct mutex mut;
164 u8 *usb_rx_buf;
165 u8 *usb_tx_buf;
166};
167
168static int ni6501_port_command(struct comedi_device *dev, int command,
169 unsigned int val, u8 *bitmap)
170{
171 struct usb_device *usb = comedi_to_usb_dev(dev);
172 struct ni6501_private *devpriv = dev->private;
173 int request_size, response_size;
174 u8 *tx = devpriv->usb_tx_buf;
175 int ret;
176
177 if (command != SET_PORT_DIR && !bitmap)
178 return -EINVAL;
179
180 mutex_lock(&devpriv->mut);
181
182 switch (command) {
183 case READ_PORT:
184 request_size = sizeof(READ_PORT_REQUEST);
185 response_size = sizeof(READ_PORT_RESPONSE);
186 memcpy(tx, READ_PORT_REQUEST, request_size);
187 tx[14] = val & 0xff;
188 break;
189 case WRITE_PORT:
190 request_size = sizeof(WRITE_PORT_REQUEST);
191 response_size = sizeof(GENERIC_RESPONSE);
192 memcpy(tx, WRITE_PORT_REQUEST, request_size);
193 tx[14] = val & 0xff;
194 tx[17] = *bitmap;
195 break;
196 case SET_PORT_DIR:
197 request_size = sizeof(SET_PORT_DIR_REQUEST);
198 response_size = sizeof(GENERIC_RESPONSE);
199 memcpy(tx, SET_PORT_DIR_REQUEST, request_size);
200 tx[14] = val & 0xff;
201 tx[15] = (val >> 8) & 0xff;
202 tx[16] = (val >> 16) & 0xff;
203 break;
204 default:
205 ret = -EINVAL;
206 goto end;
207 }
208
209 ret = usb_bulk_msg(usb_dev: usb,
210 usb_sndbulkpipe(usb,
211 devpriv->ep_tx->bEndpointAddress),
212 data: devpriv->usb_tx_buf,
213 len: request_size,
214 NULL,
215 NI6501_TIMEOUT);
216 if (ret)
217 goto end;
218
219 ret = usb_bulk_msg(usb_dev: usb,
220 usb_rcvbulkpipe(usb,
221 devpriv->ep_rx->bEndpointAddress),
222 data: devpriv->usb_rx_buf,
223 len: response_size,
224 NULL,
225 NI6501_TIMEOUT);
226 if (ret)
227 goto end;
228
229 /* Check if results are valid */
230
231 if (command == READ_PORT) {
232 *bitmap = devpriv->usb_rx_buf[14];
233 /* mask bitmap for comparing */
234 devpriv->usb_rx_buf[14] = 0x00;
235
236 if (memcmp(p: devpriv->usb_rx_buf, q: READ_PORT_RESPONSE,
237 size: sizeof(READ_PORT_RESPONSE))) {
238 ret = -EINVAL;
239 }
240 } else if (memcmp(p: devpriv->usb_rx_buf, q: GENERIC_RESPONSE,
241 size: sizeof(GENERIC_RESPONSE))) {
242 ret = -EINVAL;
243 }
244end:
245 mutex_unlock(lock: &devpriv->mut);
246
247 return ret;
248}
249
250static int ni6501_counter_command(struct comedi_device *dev, int command,
251 u32 *val)
252{
253 struct usb_device *usb = comedi_to_usb_dev(dev);
254 struct ni6501_private *devpriv = dev->private;
255 int request_size, response_size;
256 u8 *tx = devpriv->usb_tx_buf;
257 int ret;
258
259 if ((command == READ_COUNTER || command == WRITE_COUNTER) && !val)
260 return -EINVAL;
261
262 mutex_lock(&devpriv->mut);
263
264 switch (command) {
265 case START_COUNTER:
266 request_size = sizeof(START_COUNTER_REQUEST);
267 response_size = sizeof(GENERIC_RESPONSE);
268 memcpy(tx, START_COUNTER_REQUEST, request_size);
269 break;
270 case STOP_COUNTER:
271 request_size = sizeof(STOP_COUNTER_REQUEST);
272 response_size = sizeof(GENERIC_RESPONSE);
273 memcpy(tx, STOP_COUNTER_REQUEST, request_size);
274 break;
275 case READ_COUNTER:
276 request_size = sizeof(READ_COUNTER_REQUEST);
277 response_size = sizeof(READ_COUNTER_RESPONSE);
278 memcpy(tx, READ_COUNTER_REQUEST, request_size);
279 break;
280 case WRITE_COUNTER:
281 request_size = sizeof(WRITE_COUNTER_REQUEST);
282 response_size = sizeof(GENERIC_RESPONSE);
283 memcpy(tx, WRITE_COUNTER_REQUEST, request_size);
284 /* Setup tx packet: bytes 12,13,14,15 hold the */
285 /* u32 counter value (Big Endian) */
286 *((__be32 *)&tx[12]) = cpu_to_be32(*val);
287 break;
288 default:
289 ret = -EINVAL;
290 goto end;
291 }
292
293 ret = usb_bulk_msg(usb_dev: usb,
294 usb_sndbulkpipe(usb,
295 devpriv->ep_tx->bEndpointAddress),
296 data: devpriv->usb_tx_buf,
297 len: request_size,
298 NULL,
299 NI6501_TIMEOUT);
300 if (ret)
301 goto end;
302
303 ret = usb_bulk_msg(usb_dev: usb,
304 usb_rcvbulkpipe(usb,
305 devpriv->ep_rx->bEndpointAddress),
306 data: devpriv->usb_rx_buf,
307 len: response_size,
308 NULL,
309 NI6501_TIMEOUT);
310 if (ret)
311 goto end;
312
313 /* Check if results are valid */
314
315 if (command == READ_COUNTER) {
316 int i;
317
318 /* Read counter value: bytes 12,13,14,15 of rx packet */
319 /* hold the u32 counter value (Big Endian) */
320 *val = be32_to_cpu(*((__be32 *)&devpriv->usb_rx_buf[12]));
321
322 /* mask counter value for comparing */
323 for (i = 12; i < sizeof(READ_COUNTER_RESPONSE); ++i)
324 devpriv->usb_rx_buf[i] = 0x00;
325
326 if (memcmp(p: devpriv->usb_rx_buf, q: READ_COUNTER_RESPONSE,
327 size: sizeof(READ_COUNTER_RESPONSE))) {
328 ret = -EINVAL;
329 }
330 } else if (memcmp(p: devpriv->usb_rx_buf, q: GENERIC_RESPONSE,
331 size: sizeof(GENERIC_RESPONSE))) {
332 ret = -EINVAL;
333 }
334end:
335 mutex_unlock(lock: &devpriv->mut);
336
337 return ret;
338}
339
340static int ni6501_dio_insn_config(struct comedi_device *dev,
341 struct comedi_subdevice *s,
342 struct comedi_insn *insn,
343 unsigned int *data)
344{
345 int ret;
346
347 ret = comedi_dio_insn_config(dev, s, insn, data, mask: 0);
348 if (ret)
349 return ret;
350
351 ret = ni6501_port_command(dev, command: SET_PORT_DIR, val: s->io_bits, NULL);
352 if (ret)
353 return ret;
354
355 return insn->n;
356}
357
358static int ni6501_dio_insn_bits(struct comedi_device *dev,
359 struct comedi_subdevice *s,
360 struct comedi_insn *insn,
361 unsigned int *data)
362{
363 unsigned int mask;
364 int ret;
365 u8 port;
366 u8 bitmap;
367
368 mask = comedi_dio_update_state(s, data);
369
370 for (port = 0; port < 3; port++) {
371 if (mask & (0xFF << port * 8)) {
372 bitmap = (s->state >> port * 8) & 0xFF;
373 ret = ni6501_port_command(dev, command: WRITE_PORT,
374 val: port, bitmap: &bitmap);
375 if (ret)
376 return ret;
377 }
378 }
379
380 data[1] = 0;
381
382 for (port = 0; port < 3; port++) {
383 ret = ni6501_port_command(dev, command: READ_PORT, val: port, bitmap: &bitmap);
384 if (ret)
385 return ret;
386 data[1] |= bitmap << port * 8;
387 }
388
389 return insn->n;
390}
391
392static int ni6501_cnt_insn_config(struct comedi_device *dev,
393 struct comedi_subdevice *s,
394 struct comedi_insn *insn,
395 unsigned int *data)
396{
397 int ret;
398 u32 val = 0;
399
400 switch (data[0]) {
401 case INSN_CONFIG_ARM:
402 ret = ni6501_counter_command(dev, command: START_COUNTER, NULL);
403 break;
404 case INSN_CONFIG_DISARM:
405 ret = ni6501_counter_command(dev, command: STOP_COUNTER, NULL);
406 break;
407 case INSN_CONFIG_RESET:
408 ret = ni6501_counter_command(dev, command: STOP_COUNTER, NULL);
409 if (ret)
410 break;
411 ret = ni6501_counter_command(dev, command: WRITE_COUNTER, val: &val);
412 break;
413 default:
414 return -EINVAL;
415 }
416
417 return ret ? ret : insn->n;
418}
419
420static int ni6501_cnt_insn_read(struct comedi_device *dev,
421 struct comedi_subdevice *s,
422 struct comedi_insn *insn,
423 unsigned int *data)
424{
425 int ret;
426 u32 val;
427 unsigned int i;
428
429 for (i = 0; i < insn->n; i++) {
430 ret = ni6501_counter_command(dev, command: READ_COUNTER, val: &val);
431 if (ret)
432 return ret;
433 data[i] = val;
434 }
435
436 return insn->n;
437}
438
439static int ni6501_cnt_insn_write(struct comedi_device *dev,
440 struct comedi_subdevice *s,
441 struct comedi_insn *insn,
442 unsigned int *data)
443{
444 int ret;
445
446 if (insn->n) {
447 u32 val = data[insn->n - 1];
448
449 ret = ni6501_counter_command(dev, command: WRITE_COUNTER, val: &val);
450 if (ret)
451 return ret;
452 }
453
454 return insn->n;
455}
456
457static int ni6501_alloc_usb_buffers(struct comedi_device *dev)
458{
459 struct ni6501_private *devpriv = dev->private;
460 size_t size;
461
462 size = usb_endpoint_maxp(epd: devpriv->ep_rx);
463 devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
464 if (!devpriv->usb_rx_buf)
465 return -ENOMEM;
466
467 size = usb_endpoint_maxp(epd: devpriv->ep_tx);
468 devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
469 if (!devpriv->usb_tx_buf)
470 return -ENOMEM;
471
472 return 0;
473}
474
475static int ni6501_find_endpoints(struct comedi_device *dev)
476{
477 struct usb_interface *intf = comedi_to_usb_interface(dev);
478 struct ni6501_private *devpriv = dev->private;
479 struct usb_host_interface *iface_desc = intf->cur_altsetting;
480 struct usb_endpoint_descriptor *ep_desc;
481 int i;
482
483 if (iface_desc->desc.bNumEndpoints != 2) {
484 dev_err(dev->class_dev, "Wrong number of endpoints\n");
485 return -ENODEV;
486 }
487
488 for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
489 ep_desc = &iface_desc->endpoint[i].desc;
490
491 if (usb_endpoint_is_bulk_in(epd: ep_desc)) {
492 if (!devpriv->ep_rx)
493 devpriv->ep_rx = ep_desc;
494 continue;
495 }
496
497 if (usb_endpoint_is_bulk_out(epd: ep_desc)) {
498 if (!devpriv->ep_tx)
499 devpriv->ep_tx = ep_desc;
500 continue;
501 }
502 }
503
504 if (!devpriv->ep_rx || !devpriv->ep_tx)
505 return -ENODEV;
506
507 if (usb_endpoint_maxp(epd: devpriv->ep_rx) < RX_MAX_SIZE)
508 return -ENODEV;
509
510 if (usb_endpoint_maxp(epd: devpriv->ep_tx) < TX_MAX_SIZE)
511 return -ENODEV;
512
513 return 0;
514}
515
516static int ni6501_auto_attach(struct comedi_device *dev,
517 unsigned long context)
518{
519 struct usb_interface *intf = comedi_to_usb_interface(dev);
520 struct ni6501_private *devpriv;
521 struct comedi_subdevice *s;
522 int ret;
523
524 devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv));
525 if (!devpriv)
526 return -ENOMEM;
527
528 mutex_init(&devpriv->mut);
529 usb_set_intfdata(intf, data: devpriv);
530
531 ret = ni6501_find_endpoints(dev);
532 if (ret)
533 return ret;
534
535 ret = ni6501_alloc_usb_buffers(dev);
536 if (ret)
537 return ret;
538
539 ret = comedi_alloc_subdevices(dev, num_subdevices: 2);
540 if (ret)
541 return ret;
542
543 /* Digital Input/Output subdevice */
544 s = &dev->subdevices[0];
545 s->type = COMEDI_SUBD_DIO;
546 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
547 s->n_chan = 24;
548 s->maxdata = 1;
549 s->range_table = &range_digital;
550 s->insn_bits = ni6501_dio_insn_bits;
551 s->insn_config = ni6501_dio_insn_config;
552
553 /* Counter subdevice */
554 s = &dev->subdevices[1];
555 s->type = COMEDI_SUBD_COUNTER;
556 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
557 s->n_chan = 1;
558 s->maxdata = 0xffffffff;
559 s->insn_read = ni6501_cnt_insn_read;
560 s->insn_write = ni6501_cnt_insn_write;
561 s->insn_config = ni6501_cnt_insn_config;
562
563 return 0;
564}
565
566static void ni6501_detach(struct comedi_device *dev)
567{
568 struct usb_interface *intf = comedi_to_usb_interface(dev);
569 struct ni6501_private *devpriv = dev->private;
570
571 if (!devpriv)
572 return;
573
574 mutex_destroy(lock: &devpriv->mut);
575
576 usb_set_intfdata(intf, NULL);
577
578 kfree(objp: devpriv->usb_rx_buf);
579 kfree(objp: devpriv->usb_tx_buf);
580}
581
582static struct comedi_driver ni6501_driver = {
583 .module = THIS_MODULE,
584 .driver_name = "ni6501",
585 .auto_attach = ni6501_auto_attach,
586 .detach = ni6501_detach,
587};
588
589static int ni6501_usb_probe(struct usb_interface *intf,
590 const struct usb_device_id *id)
591{
592 return comedi_usb_auto_config(intf, driver: &ni6501_driver, context: id->driver_info);
593}
594
595static const struct usb_device_id ni6501_usb_table[] = {
596 { USB_DEVICE(0x3923, 0x718a) },
597 { }
598};
599MODULE_DEVICE_TABLE(usb, ni6501_usb_table);
600
601static struct usb_driver ni6501_usb_driver = {
602 .name = "ni6501",
603 .id_table = ni6501_usb_table,
604 .probe = ni6501_usb_probe,
605 .disconnect = comedi_usb_auto_unconfig,
606};
607module_comedi_usb_driver(ni6501_driver, ni6501_usb_driver);
608
609MODULE_AUTHOR("Luca Ellero");
610MODULE_DESCRIPTION("Comedi driver for National Instruments USB-6501");
611MODULE_LICENSE("GPL");
612

source code of linux/drivers/comedi/drivers/ni_usb6501.c