1/*
2    comedi/drivers/das16cs.c
3    Driver for Computer Boards PC-CARD DAS16/16.
4
5    COMEDI - Linux Control and Measurement Device Interface
6    Copyright (C) 2000, 2001, 2002 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    PCMCIA support code for this driver is adapted from the dummy_cs.c
19    driver of the Linux PCMCIA Card Services package.
20
21    The initial developer of the original code is David A. Hinds
22    <dahinds@users.sourceforge.net>.  Portions created by David A. Hinds
23    are Copyright (C) 1999 David A. Hinds.  All Rights Reserved.
24
25*/
26/*
27Driver: cb_das16_cs
28Description: Computer Boards PC-CARD DAS16/16
29Devices: [ComputerBoards] PC-CARD DAS16/16 (cb_das16_cs), PC-CARD DAS16/16-AO
30Author: ds
31Updated: Mon, 04 Nov 2002 20:04:21 -0800
32Status: experimental
33
34
35*/
36
37#include <linux/module.h>
38#include <linux/interrupt.h>
39#include <linux/delay.h>
40
41#include "../comedidev.h"
42
43#include <pcmcia/cistpl.h>
44#include <pcmcia/ds.h>
45
46#include "comedi_fc.h"
47#include "8253.h"
48
49#define DAS16CS_ADC_DATA		0
50#define DAS16CS_DIO_MUX			2
51#define DAS16CS_MISC1			4
52#define DAS16CS_MISC2			6
53#define DAS16CS_CTR0			8
54#define DAS16CS_CTR1			10
55#define DAS16CS_CTR2			12
56#define DAS16CS_CTR_CONTROL		14
57#define DAS16CS_DIO			16
58
59struct das16cs_board {
60	const char *name;
61	int device_id;
62	int n_ao_chans;
63};
64
65static const struct das16cs_board das16cs_boards[] = {
66	{
67		.name		= "PC-CARD DAS16/16-AO",
68		.device_id	= 0x0039,
69		.n_ao_chans	= 2,
70	}, {
71		.name		= "PCM-DAS16s/16",
72		.device_id	= 0x4009,
73		.n_ao_chans	= 0,
74	}, {
75		.name		= "PC-CARD DAS16/16",
76		.device_id	= 0x0000,	/* unknown */
77		.n_ao_chans	= 0,
78	},
79};
80
81struct das16cs_private {
82	unsigned short status1;
83	unsigned short status2;
84};
85
86static const struct comedi_lrange das16cs_ai_range = {
87	4, {
88		BIP_RANGE(10),
89		BIP_RANGE(5),
90		BIP_RANGE(2.5),
91		BIP_RANGE(1.25),
92	}
93};
94
95static int das16cs_ai_eoc(struct comedi_device *dev,
96			  struct comedi_subdevice *s,
97			  struct comedi_insn *insn,
98			  unsigned long context)
99{
100	unsigned int status;
101
102	status = inw(dev->iobase + DAS16CS_MISC1);
103	if (status & 0x0080)
104		return 0;
105	return -EBUSY;
106}
107
108static int das16cs_ai_rinsn(struct comedi_device *dev,
109			    struct comedi_subdevice *s,
110			    struct comedi_insn *insn, unsigned int *data)
111{
112	struct das16cs_private *devpriv = dev->private;
113	int chan = CR_CHAN(insn->chanspec);
114	int range = CR_RANGE(insn->chanspec);
115	int aref = CR_AREF(insn->chanspec);
116	int ret;
117	int i;
118
119	outw(chan, dev->iobase + DAS16CS_DIO_MUX);
120
121	devpriv->status1 &= ~0xf320;
122	devpriv->status1 |= (aref == AREF_DIFF) ? 0 : 0x0020;
123	outw(devpriv->status1, dev->iobase + DAS16CS_MISC1);
124
125	devpriv->status2 &= ~0xff00;
126	switch (range) {
127	case 0:
128		devpriv->status2 |= 0x800;
129		break;
130	case 1:
131		devpriv->status2 |= 0x000;
132		break;
133	case 2:
134		devpriv->status2 |= 0x100;
135		break;
136	case 3:
137		devpriv->status2 |= 0x200;
138		break;
139	}
140	outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
141
142	for (i = 0; i < insn->n; i++) {
143		outw(0, dev->iobase + DAS16CS_ADC_DATA);
144
145		ret = comedi_timeout(dev, s, insn, das16cs_ai_eoc, 0);
146		if (ret)
147			return ret;
148
149		data[i] = inw(dev->iobase + DAS16CS_ADC_DATA);
150	}
151
152	return i;
153}
154
155static int das16cs_ao_insn_write(struct comedi_device *dev,
156				 struct comedi_subdevice *s,
157				 struct comedi_insn *insn,
158				 unsigned int *data)
159{
160	struct das16cs_private *devpriv = dev->private;
161	unsigned int chan = CR_CHAN(insn->chanspec);
162	unsigned int val = s->readback[chan];
163	unsigned short status1;
164	int bit;
165	int i;
166
167	for (i = 0; i < insn->n; i++) {
168		val = data[i];
169
170		outw(devpriv->status1, dev->iobase + DAS16CS_MISC1);
171		udelay(1);
172
173		status1 = devpriv->status1 & ~0xf;
174		if (chan)
175			status1 |= 0x0001;
176		else
177			status1 |= 0x0008;
178
179		outw(status1, dev->iobase + DAS16CS_MISC1);
180		udelay(1);
181
182		for (bit = 15; bit >= 0; bit--) {
183			int b = (val >> bit) & 0x1;
184
185			b <<= 1;
186			outw(status1 | b | 0x0000, dev->iobase + DAS16CS_MISC1);
187			udelay(1);
188			outw(status1 | b | 0x0004, dev->iobase + DAS16CS_MISC1);
189			udelay(1);
190		}
191		/*
192		 * Make both DAC0CS and DAC1CS high to load
193		 * the new data and update analog the output
194		 */
195		outw(status1 | 0x9, dev->iobase + DAS16CS_MISC1);
196	}
197	s->readback[chan] = val;
198
199	return insn->n;
200}
201
202static int das16cs_dio_insn_bits(struct comedi_device *dev,
203				 struct comedi_subdevice *s,
204				 struct comedi_insn *insn,
205				 unsigned int *data)
206{
207	if (comedi_dio_update_state(s, data))
208		outw(s->state, dev->iobase + DAS16CS_DIO);
209
210	data[1] = inw(dev->iobase + DAS16CS_DIO);
211
212	return insn->n;
213}
214
215static int das16cs_dio_insn_config(struct comedi_device *dev,
216				   struct comedi_subdevice *s,
217				   struct comedi_insn *insn,
218				   unsigned int *data)
219{
220	struct das16cs_private *devpriv = dev->private;
221	unsigned int chan = CR_CHAN(insn->chanspec);
222	unsigned int mask;
223	int ret;
224
225	if (chan < 4)
226		mask = 0x0f;
227	else
228		mask = 0xf0;
229
230	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
231	if (ret)
232		return ret;
233
234	devpriv->status2 &= ~0x00c0;
235	devpriv->status2 |= (s->io_bits & 0xf0) ? 0x0080 : 0;
236	devpriv->status2 |= (s->io_bits & 0x0f) ? 0x0040 : 0;
237
238	outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
239
240	return insn->n;
241}
242
243static const void *das16cs_find_boardinfo(struct comedi_device *dev,
244					  struct pcmcia_device *link)
245{
246	const struct das16cs_board *board;
247	int i;
248
249	for (i = 0; i < ARRAY_SIZE(das16cs_boards); i++) {
250		board = &das16cs_boards[i];
251		if (board->device_id == link->card_id)
252			return board;
253	}
254
255	return NULL;
256}
257
258static int das16cs_auto_attach(struct comedi_device *dev,
259			       unsigned long context)
260{
261	struct pcmcia_device *link = comedi_to_pcmcia_dev(dev);
262	const struct das16cs_board *board;
263	struct das16cs_private *devpriv;
264	struct comedi_subdevice *s;
265	int ret;
266
267	board = das16cs_find_boardinfo(dev, link);
268	if (!board)
269		return -ENODEV;
270	dev->board_ptr = board;
271	dev->board_name = board->name;
272
273	link->config_flags |= CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
274	ret = comedi_pcmcia_enable(dev, NULL);
275	if (ret)
276		return ret;
277	dev->iobase = link->resource[0]->start;
278
279	link->priv = dev;
280
281	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
282	if (!devpriv)
283		return -ENOMEM;
284
285	ret = comedi_alloc_subdevices(dev, 3);
286	if (ret)
287		return ret;
288
289	s = &dev->subdevices[0];
290	/* analog input subdevice */
291	s->type		= COMEDI_SUBD_AI;
292	s->subdev_flags	= SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ;
293	s->n_chan	= 16;
294	s->maxdata	= 0xffff;
295	s->range_table	= &das16cs_ai_range;
296	s->len_chanlist	= 16;
297	s->insn_read	= das16cs_ai_rinsn;
298
299	s = &dev->subdevices[1];
300	/* analog output subdevice */
301	if (board->n_ao_chans) {
302		s->type		= COMEDI_SUBD_AO;
303		s->subdev_flags	= SDF_WRITABLE;
304		s->n_chan	= board->n_ao_chans;
305		s->maxdata	= 0xffff;
306		s->range_table	= &range_bipolar10;
307		s->insn_write	= &das16cs_ao_insn_write;
308		s->insn_read	= comedi_readback_insn_read;
309
310		ret = comedi_alloc_subdev_readback(s);
311		if (ret)
312			return ret;
313	} else {
314		s->type		= COMEDI_SUBD_UNUSED;
315	}
316
317	s = &dev->subdevices[2];
318	/* digital i/o subdevice */
319	s->type		= COMEDI_SUBD_DIO;
320	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
321	s->n_chan	= 8;
322	s->maxdata	= 1;
323	s->range_table	= &range_digital;
324	s->insn_bits	= das16cs_dio_insn_bits;
325	s->insn_config	= das16cs_dio_insn_config;
326
327	return 0;
328}
329
330static struct comedi_driver driver_das16cs = {
331	.driver_name	= "cb_das16_cs",
332	.module		= THIS_MODULE,
333	.auto_attach	= das16cs_auto_attach,
334	.detach		= comedi_pcmcia_disable,
335};
336
337static int das16cs_pcmcia_attach(struct pcmcia_device *link)
338{
339	return comedi_pcmcia_auto_config(link, &driver_das16cs);
340}
341
342static const struct pcmcia_device_id das16cs_id_table[] = {
343	PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x0039),
344	PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4009),
345	PCMCIA_DEVICE_NULL
346};
347MODULE_DEVICE_TABLE(pcmcia, das16cs_id_table);
348
349static struct pcmcia_driver das16cs_driver = {
350	.name		= "cb_das16_cs",
351	.owner		= THIS_MODULE,
352	.id_table	= das16cs_id_table,
353	.probe		= das16cs_pcmcia_attach,
354	.remove		= comedi_pcmcia_auto_unconfig,
355};
356module_comedi_pcmcia_driver(driver_das16cs, das16cs_driver);
357
358MODULE_AUTHOR("David A. Schleef <ds@schleef.org>");
359MODULE_DESCRIPTION("Comedi driver for Computer Boards PC-CARD DAS16/16");
360MODULE_LICENSE("GPL");
361