162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ke_counter.c
462306a36Sopenharmony_ci * Comedi driver for Kolter-Electronic PCI Counter 1 Card
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * COMEDI - Linux Control and Measurement Device Interface
762306a36Sopenharmony_ci * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/*
1162306a36Sopenharmony_ci * Driver: ke_counter
1262306a36Sopenharmony_ci * Description: Driver for Kolter Electronic Counter Card
1362306a36Sopenharmony_ci * Devices: [Kolter Electronic] PCI Counter Card (ke_counter)
1462306a36Sopenharmony_ci * Author: Michael Hillmann
1562306a36Sopenharmony_ci * Updated: Mon, 14 Apr 2008 15:42:42 +0100
1662306a36Sopenharmony_ci * Status: tested
1762306a36Sopenharmony_ci *
1862306a36Sopenharmony_ci * Configuration Options: not applicable, uses PCI auto config
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/module.h>
2262306a36Sopenharmony_ci#include <linux/comedi/comedi_pci.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/*
2562306a36Sopenharmony_ci * PCI BAR 0 Register I/O map
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_ci#define KE_RESET_REG(x)			(0x00 + ((x) * 0x20))
2862306a36Sopenharmony_ci#define KE_LATCH_REG(x)			(0x00 + ((x) * 0x20))
2962306a36Sopenharmony_ci#define KE_LSB_REG(x)			(0x04 + ((x) * 0x20))
3062306a36Sopenharmony_ci#define KE_MID_REG(x)			(0x08 + ((x) * 0x20))
3162306a36Sopenharmony_ci#define KE_MSB_REG(x)			(0x0c + ((x) * 0x20))
3262306a36Sopenharmony_ci#define KE_SIGN_REG(x)			(0x10 + ((x) * 0x20))
3362306a36Sopenharmony_ci#define KE_OSC_SEL_REG			0xf8
3462306a36Sopenharmony_ci#define KE_OSC_SEL_CLK(x)		(((x) & 0x3) << 0)
3562306a36Sopenharmony_ci#define KE_OSC_SEL_EXT			KE_OSC_SEL_CLK(1)
3662306a36Sopenharmony_ci#define KE_OSC_SEL_4MHZ			KE_OSC_SEL_CLK(2)
3762306a36Sopenharmony_ci#define KE_OSC_SEL_20MHZ		KE_OSC_SEL_CLK(3)
3862306a36Sopenharmony_ci#define KE_DO_REG			0xfc
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic int ke_counter_insn_write(struct comedi_device *dev,
4162306a36Sopenharmony_ci				 struct comedi_subdevice *s,
4262306a36Sopenharmony_ci				 struct comedi_insn *insn,
4362306a36Sopenharmony_ci				 unsigned int *data)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	unsigned int chan = CR_CHAN(insn->chanspec);
4662306a36Sopenharmony_ci	unsigned int val;
4762306a36Sopenharmony_ci	int i;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++) {
5062306a36Sopenharmony_ci		val = data[0];
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci		/* Order matters */
5362306a36Sopenharmony_ci		outb((val >> 24) & 0xff, dev->iobase + KE_SIGN_REG(chan));
5462306a36Sopenharmony_ci		outb((val >> 16) & 0xff, dev->iobase + KE_MSB_REG(chan));
5562306a36Sopenharmony_ci		outb((val >> 8) & 0xff, dev->iobase + KE_MID_REG(chan));
5662306a36Sopenharmony_ci		outb((val >> 0) & 0xff, dev->iobase + KE_LSB_REG(chan));
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	return insn->n;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic int ke_counter_insn_read(struct comedi_device *dev,
6362306a36Sopenharmony_ci				struct comedi_subdevice *s,
6462306a36Sopenharmony_ci				struct comedi_insn *insn,
6562306a36Sopenharmony_ci				unsigned int *data)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	unsigned int chan = CR_CHAN(insn->chanspec);
6862306a36Sopenharmony_ci	unsigned int val;
6962306a36Sopenharmony_ci	int i;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++) {
7262306a36Sopenharmony_ci		/* Order matters */
7362306a36Sopenharmony_ci		inb(dev->iobase + KE_LATCH_REG(chan));
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci		val = inb(dev->iobase + KE_LSB_REG(chan));
7662306a36Sopenharmony_ci		val |= (inb(dev->iobase + KE_MID_REG(chan)) << 8);
7762306a36Sopenharmony_ci		val |= (inb(dev->iobase + KE_MSB_REG(chan)) << 16);
7862306a36Sopenharmony_ci		val |= (inb(dev->iobase + KE_SIGN_REG(chan)) << 24);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci		data[i] = val;
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return insn->n;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic void ke_counter_reset(struct comedi_device *dev)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	unsigned int chan;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	for (chan = 0; chan < 3; chan++)
9162306a36Sopenharmony_ci		outb(0, dev->iobase + KE_RESET_REG(chan));
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic int ke_counter_insn_config(struct comedi_device *dev,
9562306a36Sopenharmony_ci				  struct comedi_subdevice *s,
9662306a36Sopenharmony_ci				  struct comedi_insn *insn,
9762306a36Sopenharmony_ci				  unsigned int *data)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	unsigned char src;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	switch (data[0]) {
10262306a36Sopenharmony_ci	case INSN_CONFIG_SET_CLOCK_SRC:
10362306a36Sopenharmony_ci		switch (data[1]) {
10462306a36Sopenharmony_ci		case KE_CLK_20MHZ:	/* default */
10562306a36Sopenharmony_ci			src = KE_OSC_SEL_20MHZ;
10662306a36Sopenharmony_ci			break;
10762306a36Sopenharmony_ci		case KE_CLK_4MHZ:	/* option */
10862306a36Sopenharmony_ci			src = KE_OSC_SEL_4MHZ;
10962306a36Sopenharmony_ci			break;
11062306a36Sopenharmony_ci		case KE_CLK_EXT:	/* Pin 21 on D-sub */
11162306a36Sopenharmony_ci			src = KE_OSC_SEL_EXT;
11262306a36Sopenharmony_ci			break;
11362306a36Sopenharmony_ci		default:
11462306a36Sopenharmony_ci			return -EINVAL;
11562306a36Sopenharmony_ci		}
11662306a36Sopenharmony_ci		outb(src, dev->iobase + KE_OSC_SEL_REG);
11762306a36Sopenharmony_ci		break;
11862306a36Sopenharmony_ci	case INSN_CONFIG_GET_CLOCK_SRC:
11962306a36Sopenharmony_ci		src = inb(dev->iobase + KE_OSC_SEL_REG);
12062306a36Sopenharmony_ci		switch (src) {
12162306a36Sopenharmony_ci		case KE_OSC_SEL_20MHZ:
12262306a36Sopenharmony_ci			data[1] = KE_CLK_20MHZ;
12362306a36Sopenharmony_ci			data[2] = 50;	/* 50ns */
12462306a36Sopenharmony_ci			break;
12562306a36Sopenharmony_ci		case KE_OSC_SEL_4MHZ:
12662306a36Sopenharmony_ci			data[1] = KE_CLK_4MHZ;
12762306a36Sopenharmony_ci			data[2] = 250;	/* 250ns */
12862306a36Sopenharmony_ci			break;
12962306a36Sopenharmony_ci		case KE_OSC_SEL_EXT:
13062306a36Sopenharmony_ci			data[1] = KE_CLK_EXT;
13162306a36Sopenharmony_ci			data[2] = 0;	/* Unknown */
13262306a36Sopenharmony_ci			break;
13362306a36Sopenharmony_ci		default:
13462306a36Sopenharmony_ci			return -EINVAL;
13562306a36Sopenharmony_ci		}
13662306a36Sopenharmony_ci		break;
13762306a36Sopenharmony_ci	case INSN_CONFIG_RESET:
13862306a36Sopenharmony_ci		ke_counter_reset(dev);
13962306a36Sopenharmony_ci		break;
14062306a36Sopenharmony_ci	default:
14162306a36Sopenharmony_ci		return -EINVAL;
14262306a36Sopenharmony_ci	}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	return insn->n;
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int ke_counter_do_insn_bits(struct comedi_device *dev,
14862306a36Sopenharmony_ci				   struct comedi_subdevice *s,
14962306a36Sopenharmony_ci				   struct comedi_insn *insn,
15062306a36Sopenharmony_ci				   unsigned int *data)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	if (comedi_dio_update_state(s, data))
15362306a36Sopenharmony_ci		outb(s->state, dev->iobase + KE_DO_REG);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	data[1] = s->state;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	return insn->n;
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic int ke_counter_auto_attach(struct comedi_device *dev,
16162306a36Sopenharmony_ci				  unsigned long context_unused)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
16462306a36Sopenharmony_ci	struct comedi_subdevice *s;
16562306a36Sopenharmony_ci	int ret;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	ret = comedi_pci_enable(dev);
16862306a36Sopenharmony_ci	if (ret)
16962306a36Sopenharmony_ci		return ret;
17062306a36Sopenharmony_ci	dev->iobase = pci_resource_start(pcidev, 0);
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	ret = comedi_alloc_subdevices(dev, 2);
17362306a36Sopenharmony_ci	if (ret)
17462306a36Sopenharmony_ci		return ret;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	s = &dev->subdevices[0];
17762306a36Sopenharmony_ci	s->type		= COMEDI_SUBD_COUNTER;
17862306a36Sopenharmony_ci	s->subdev_flags	= SDF_READABLE;
17962306a36Sopenharmony_ci	s->n_chan	= 3;
18062306a36Sopenharmony_ci	s->maxdata	= 0x01ffffff;
18162306a36Sopenharmony_ci	s->range_table	= &range_unknown;
18262306a36Sopenharmony_ci	s->insn_read	= ke_counter_insn_read;
18362306a36Sopenharmony_ci	s->insn_write	= ke_counter_insn_write;
18462306a36Sopenharmony_ci	s->insn_config	= ke_counter_insn_config;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	s = &dev->subdevices[1];
18762306a36Sopenharmony_ci	s->type		= COMEDI_SUBD_DO;
18862306a36Sopenharmony_ci	s->subdev_flags	= SDF_WRITABLE;
18962306a36Sopenharmony_ci	s->n_chan	= 3;
19062306a36Sopenharmony_ci	s->maxdata	= 1;
19162306a36Sopenharmony_ci	s->range_table	= &range_digital;
19262306a36Sopenharmony_ci	s->insn_bits	= ke_counter_do_insn_bits;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	outb(KE_OSC_SEL_20MHZ, dev->iobase + KE_OSC_SEL_REG);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	ke_counter_reset(dev);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	return 0;
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic struct comedi_driver ke_counter_driver = {
20262306a36Sopenharmony_ci	.driver_name	= "ke_counter",
20362306a36Sopenharmony_ci	.module		= THIS_MODULE,
20462306a36Sopenharmony_ci	.auto_attach	= ke_counter_auto_attach,
20562306a36Sopenharmony_ci	.detach		= comedi_pci_detach,
20662306a36Sopenharmony_ci};
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic int ke_counter_pci_probe(struct pci_dev *dev,
20962306a36Sopenharmony_ci				const struct pci_device_id *id)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	return comedi_pci_auto_config(dev, &ke_counter_driver,
21262306a36Sopenharmony_ci				      id->driver_data);
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic const struct pci_device_id ke_counter_pci_table[] = {
21662306a36Sopenharmony_ci	{ PCI_DEVICE(PCI_VENDOR_ID_KOLTER, 0x0014) },
21762306a36Sopenharmony_ci	{ 0 }
21862306a36Sopenharmony_ci};
21962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, ke_counter_pci_table);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic struct pci_driver ke_counter_pci_driver = {
22262306a36Sopenharmony_ci	.name		= "ke_counter",
22362306a36Sopenharmony_ci	.id_table	= ke_counter_pci_table,
22462306a36Sopenharmony_ci	.probe		= ke_counter_pci_probe,
22562306a36Sopenharmony_ci	.remove		= comedi_pci_auto_unconfig,
22662306a36Sopenharmony_ci};
22762306a36Sopenharmony_cimodule_comedi_pci_driver(ke_counter_driver, ke_counter_pci_driver);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ciMODULE_AUTHOR("Comedi https://www.comedi.org");
23062306a36Sopenharmony_ciMODULE_DESCRIPTION("Comedi driver for Kolter Electronic Counter Card");
23162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
232