1/*
2 * pcmad.c
3 * Hardware driver for Winsystems PCM-A/D12 and PCM-A/D16
4 *
5 * COMEDI - Linux Control and Measurement Device Interface
6 * Copyright (C) 2000,2001 David A. Schleef <ds@schleef.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
19/*
20 * Driver: pcmad
21 * Description: Winsystems PCM-A/D12, PCM-A/D16
22 * Devices: (Winsystems) PCM-A/D12 [pcmad12]
23 *	    (Winsystems) PCM-A/D16 [pcmad16]
24 * Author: ds
25 * Status: untested
26 *
27 * This driver was written on a bet that I couldn't write a driver
28 * in less than 2 hours.  I won the bet, but never got paid.  =(
29 *
30 * Configuration options:
31 *   [0] - I/O port base
32 *   [1] - IRQ (unused)
33 *   [2] - Analog input reference (must match jumpers)
34 *	   0 = single-ended (16 channels)
35 *	   1 = differential (8 channels)
36 *   [3] - Analog input encoding (must match jumpers)
37 *	   0 = straight binary (0-5V input range)
38 *	   1 = two's complement (+-10V input range)
39 */
40
41#include <linux/module.h>
42#include "../comedidev.h"
43
44#define PCMAD_STATUS		0
45#define PCMAD_LSB		1
46#define PCMAD_MSB		2
47#define PCMAD_CONVERT		1
48
49struct pcmad_board_struct {
50	const char *name;
51	unsigned int ai_maxdata;
52};
53
54static const struct pcmad_board_struct pcmad_boards[] = {
55	{
56		.name		= "pcmad12",
57		.ai_maxdata	= 0x0fff,
58	}, {
59		.name		= "pcmad16",
60		.ai_maxdata	= 0xffff,
61	},
62};
63
64static int pcmad_ai_eoc(struct comedi_device *dev,
65			struct comedi_subdevice *s,
66			struct comedi_insn *insn,
67			unsigned long context)
68{
69	unsigned int status;
70
71	status = inb(dev->iobase + PCMAD_STATUS);
72	if ((status & 0x3) == 0x3)
73		return 0;
74	return -EBUSY;
75}
76
77static int pcmad_ai_insn_read(struct comedi_device *dev,
78			      struct comedi_subdevice *s,
79			      struct comedi_insn *insn,
80			      unsigned int *data)
81{
82	unsigned int chan = CR_CHAN(insn->chanspec);
83	unsigned int range = CR_RANGE(insn->chanspec);
84	unsigned int val;
85	int ret;
86	int i;
87
88	for (i = 0; i < insn->n; i++) {
89		outb(chan, dev->iobase + PCMAD_CONVERT);
90
91		ret = comedi_timeout(dev, s, insn, pcmad_ai_eoc, 0);
92		if (ret)
93			return ret;
94
95		val = inb(dev->iobase + PCMAD_LSB) |
96		      (inb(dev->iobase + PCMAD_MSB) << 8);
97
98		/* data is shifted on the pcmad12, fix it */
99		if (s->maxdata == 0x0fff)
100			val >>= 4;
101
102		if (comedi_range_is_bipolar(s, range)) {
103			/* munge the two's complement value */
104			val ^= ((s->maxdata + 1) >> 1);
105		}
106
107		data[i] = val;
108	}
109
110	return insn->n;
111}
112
113static int pcmad_attach(struct comedi_device *dev, struct comedi_devconfig *it)
114{
115	const struct pcmad_board_struct *board = dev->board_ptr;
116	struct comedi_subdevice *s;
117	int ret;
118
119	ret = comedi_request_region(dev, it->options[0], 0x04);
120	if (ret)
121		return ret;
122
123	ret = comedi_alloc_subdevices(dev, 1);
124	if (ret)
125		return ret;
126
127	s = &dev->subdevices[0];
128	s->type		= COMEDI_SUBD_AI;
129	if (it->options[1]) {
130		/* 8 differential channels */
131		s->subdev_flags	= SDF_READABLE | AREF_DIFF;
132		s->n_chan	= 8;
133	} else {
134		/* 16 single-ended channels */
135		s->subdev_flags	= SDF_READABLE | AREF_GROUND;
136		s->n_chan	= 16;
137	}
138	s->len_chanlist	= 1;
139	s->maxdata	= board->ai_maxdata;
140	s->range_table	= it->options[2] ? &range_bipolar10 : &range_unipolar5;
141	s->insn_read	= pcmad_ai_insn_read;
142
143	return 0;
144}
145
146static struct comedi_driver pcmad_driver = {
147	.driver_name	= "pcmad",
148	.module		= THIS_MODULE,
149	.attach		= pcmad_attach,
150	.detach		= comedi_legacy_detach,
151	.board_name	= &pcmad_boards[0].name,
152	.num_names	= ARRAY_SIZE(pcmad_boards),
153	.offset		= sizeof(pcmad_boards[0]),
154};
155module_comedi_driver(pcmad_driver);
156
157MODULE_AUTHOR("Comedi http://www.comedi.org");
158MODULE_DESCRIPTION("Comedi low-level driver");
159MODULE_LICENSE("GPL");
160