1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * ke_counter.c
4 * Comedi driver for Kolter-Electronic PCI Counter 1 Card
5 *
6 * COMEDI - Linux Control and Measurement Device Interface
7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
8 */
9
10/*
11 * Driver: ke_counter
12 * Description: Driver for Kolter Electronic Counter Card
13 * Devices: [Kolter Electronic] PCI Counter Card (ke_counter)
14 * Author: Michael Hillmann
15 * Updated: Mon, 14 Apr 2008 15:42:42 +0100
16 * Status: tested
17 *
18 * Configuration Options: not applicable, uses PCI auto config
19 */
20
21#include <linux/module.h>
22#include <linux/comedi/comedi_pci.h>
23
24/*
25 * PCI BAR 0 Register I/O map
26 */
27#define KE_RESET_REG(x)			(0x00 + ((x) * 0x20))
28#define KE_LATCH_REG(x)			(0x00 + ((x) * 0x20))
29#define KE_LSB_REG(x)			(0x04 + ((x) * 0x20))
30#define KE_MID_REG(x)			(0x08 + ((x) * 0x20))
31#define KE_MSB_REG(x)			(0x0c + ((x) * 0x20))
32#define KE_SIGN_REG(x)			(0x10 + ((x) * 0x20))
33#define KE_OSC_SEL_REG			0xf8
34#define KE_OSC_SEL_CLK(x)		(((x) & 0x3) << 0)
35#define KE_OSC_SEL_EXT			KE_OSC_SEL_CLK(1)
36#define KE_OSC_SEL_4MHZ			KE_OSC_SEL_CLK(2)
37#define KE_OSC_SEL_20MHZ		KE_OSC_SEL_CLK(3)
38#define KE_DO_REG			0xfc
39
40static int ke_counter_insn_write(struct comedi_device *dev,
41				 struct comedi_subdevice *s,
42				 struct comedi_insn *insn,
43				 unsigned int *data)
44{
45	unsigned int chan = CR_CHAN(insn->chanspec);
46	unsigned int val;
47	int i;
48
49	for (i = 0; i < insn->n; i++) {
50		val = data[0];
51
52		/* Order matters */
53		outb((val >> 24) & 0xff, dev->iobase + KE_SIGN_REG(chan));
54		outb((val >> 16) & 0xff, dev->iobase + KE_MSB_REG(chan));
55		outb((val >> 8) & 0xff, dev->iobase + KE_MID_REG(chan));
56		outb((val >> 0) & 0xff, dev->iobase + KE_LSB_REG(chan));
57	}
58
59	return insn->n;
60}
61
62static int ke_counter_insn_read(struct comedi_device *dev,
63				struct comedi_subdevice *s,
64				struct comedi_insn *insn,
65				unsigned int *data)
66{
67	unsigned int chan = CR_CHAN(insn->chanspec);
68	unsigned int val;
69	int i;
70
71	for (i = 0; i < insn->n; i++) {
72		/* Order matters */
73		inb(dev->iobase + KE_LATCH_REG(chan));
74
75		val = inb(dev->iobase + KE_LSB_REG(chan));
76		val |= (inb(dev->iobase + KE_MID_REG(chan)) << 8);
77		val |= (inb(dev->iobase + KE_MSB_REG(chan)) << 16);
78		val |= (inb(dev->iobase + KE_SIGN_REG(chan)) << 24);
79
80		data[i] = val;
81	}
82
83	return insn->n;
84}
85
86static void ke_counter_reset(struct comedi_device *dev)
87{
88	unsigned int chan;
89
90	for (chan = 0; chan < 3; chan++)
91		outb(0, dev->iobase + KE_RESET_REG(chan));
92}
93
94static int ke_counter_insn_config(struct comedi_device *dev,
95				  struct comedi_subdevice *s,
96				  struct comedi_insn *insn,
97				  unsigned int *data)
98{
99	unsigned char src;
100
101	switch (data[0]) {
102	case INSN_CONFIG_SET_CLOCK_SRC:
103		switch (data[1]) {
104		case KE_CLK_20MHZ:	/* default */
105			src = KE_OSC_SEL_20MHZ;
106			break;
107		case KE_CLK_4MHZ:	/* option */
108			src = KE_OSC_SEL_4MHZ;
109			break;
110		case KE_CLK_EXT:	/* Pin 21 on D-sub */
111			src = KE_OSC_SEL_EXT;
112			break;
113		default:
114			return -EINVAL;
115		}
116		outb(src, dev->iobase + KE_OSC_SEL_REG);
117		break;
118	case INSN_CONFIG_GET_CLOCK_SRC:
119		src = inb(dev->iobase + KE_OSC_SEL_REG);
120		switch (src) {
121		case KE_OSC_SEL_20MHZ:
122			data[1] = KE_CLK_20MHZ;
123			data[2] = 50;	/* 50ns */
124			break;
125		case KE_OSC_SEL_4MHZ:
126			data[1] = KE_CLK_4MHZ;
127			data[2] = 250;	/* 250ns */
128			break;
129		case KE_OSC_SEL_EXT:
130			data[1] = KE_CLK_EXT;
131			data[2] = 0;	/* Unknown */
132			break;
133		default:
134			return -EINVAL;
135		}
136		break;
137	case INSN_CONFIG_RESET:
138		ke_counter_reset(dev);
139		break;
140	default:
141		return -EINVAL;
142	}
143
144	return insn->n;
145}
146
147static int ke_counter_do_insn_bits(struct comedi_device *dev,
148				   struct comedi_subdevice *s,
149				   struct comedi_insn *insn,
150				   unsigned int *data)
151{
152	if (comedi_dio_update_state(s, data))
153		outb(s->state, dev->iobase + KE_DO_REG);
154
155	data[1] = s->state;
156
157	return insn->n;
158}
159
160static int ke_counter_auto_attach(struct comedi_device *dev,
161				  unsigned long context_unused)
162{
163	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
164	struct comedi_subdevice *s;
165	int ret;
166
167	ret = comedi_pci_enable(dev);
168	if (ret)
169		return ret;
170	dev->iobase = pci_resource_start(pcidev, 0);
171
172	ret = comedi_alloc_subdevices(dev, 2);
173	if (ret)
174		return ret;
175
176	s = &dev->subdevices[0];
177	s->type		= COMEDI_SUBD_COUNTER;
178	s->subdev_flags	= SDF_READABLE;
179	s->n_chan	= 3;
180	s->maxdata	= 0x01ffffff;
181	s->range_table	= &range_unknown;
182	s->insn_read	= ke_counter_insn_read;
183	s->insn_write	= ke_counter_insn_write;
184	s->insn_config	= ke_counter_insn_config;
185
186	s = &dev->subdevices[1];
187	s->type		= COMEDI_SUBD_DO;
188	s->subdev_flags	= SDF_WRITABLE;
189	s->n_chan	= 3;
190	s->maxdata	= 1;
191	s->range_table	= &range_digital;
192	s->insn_bits	= ke_counter_do_insn_bits;
193
194	outb(KE_OSC_SEL_20MHZ, dev->iobase + KE_OSC_SEL_REG);
195
196	ke_counter_reset(dev);
197
198	return 0;
199}
200
201static struct comedi_driver ke_counter_driver = {
202	.driver_name	= "ke_counter",
203	.module		= THIS_MODULE,
204	.auto_attach	= ke_counter_auto_attach,
205	.detach		= comedi_pci_detach,
206};
207
208static int ke_counter_pci_probe(struct pci_dev *dev,
209				const struct pci_device_id *id)
210{
211	return comedi_pci_auto_config(dev, &ke_counter_driver,
212				      id->driver_data);
213}
214
215static const struct pci_device_id ke_counter_pci_table[] = {
216	{ PCI_DEVICE(PCI_VENDOR_ID_KOLTER, 0x0014) },
217	{ 0 }
218};
219MODULE_DEVICE_TABLE(pci, ke_counter_pci_table);
220
221static struct pci_driver ke_counter_pci_driver = {
222	.name		= "ke_counter",
223	.id_table	= ke_counter_pci_table,
224	.probe		= ke_counter_pci_probe,
225	.remove		= comedi_pci_auto_unconfig,
226};
227module_comedi_pci_driver(ke_counter_driver, ke_counter_pci_driver);
228
229MODULE_AUTHOR("Comedi https://www.comedi.org");
230MODULE_DESCRIPTION("Comedi driver for Kolter Electronic Counter Card");
231MODULE_LICENSE("GPL");
232