162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * aio_aio12_8.c
462306a36Sopenharmony_ci * Driver for Access I/O Products PC-104 AIO12-8 Analog I/O Board
562306a36Sopenharmony_ci * Copyright (C) 2006 C&C Technologies, Inc.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci/*
962306a36Sopenharmony_ci * Driver: aio_aio12_8
1062306a36Sopenharmony_ci * Description: Access I/O Products PC-104 AIO12-8 Analog I/O Board
1162306a36Sopenharmony_ci * Author: Pablo Mejia <pablo.mejia@cctechnol.com>
1262306a36Sopenharmony_ci * Devices: [Access I/O] PC-104 AIO12-8 (aio_aio12_8),
1362306a36Sopenharmony_ci *   [Access I/O] PC-104 AI12-8 (aio_ai12_8),
1462306a36Sopenharmony_ci *   [Access I/O] PC-104 AO12-4 (aio_ao12_4)
1562306a36Sopenharmony_ci * Status: experimental
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci * Configuration Options:
1862306a36Sopenharmony_ci *   [0] - I/O port base address
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * Notes:
2162306a36Sopenharmony_ci * Only synchronous operations are supported.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <linux/module.h>
2562306a36Sopenharmony_ci#include <linux/comedi/comedidev.h>
2662306a36Sopenharmony_ci#include <linux/comedi/comedi_8255.h>
2762306a36Sopenharmony_ci#include <linux/comedi/comedi_8254.h>
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/*
3062306a36Sopenharmony_ci * Register map
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_ci#define AIO12_8_STATUS_REG		0x00
3362306a36Sopenharmony_ci#define AIO12_8_STATUS_ADC_EOC		BIT(7)
3462306a36Sopenharmony_ci#define AIO12_8_STATUS_PORT_C_COS	BIT(6)
3562306a36Sopenharmony_ci#define AIO12_8_STATUS_IRQ_ENA		BIT(2)
3662306a36Sopenharmony_ci#define AIO12_8_INTERRUPT_REG		0x01
3762306a36Sopenharmony_ci#define AIO12_8_INTERRUPT_ADC		BIT(7)
3862306a36Sopenharmony_ci#define AIO12_8_INTERRUPT_COS		BIT(6)
3962306a36Sopenharmony_ci#define AIO12_8_INTERRUPT_COUNTER1	BIT(5)
4062306a36Sopenharmony_ci#define AIO12_8_INTERRUPT_PORT_C3	BIT(4)
4162306a36Sopenharmony_ci#define AIO12_8_INTERRUPT_PORT_C0	BIT(3)
4262306a36Sopenharmony_ci#define AIO12_8_INTERRUPT_ENA		BIT(2)
4362306a36Sopenharmony_ci#define AIO12_8_ADC_REG			0x02
4462306a36Sopenharmony_ci#define AIO12_8_ADC_MODE(x)		(((x) & 0x3) << 6)
4562306a36Sopenharmony_ci#define AIO12_8_ADC_MODE_NORMAL		AIO12_8_ADC_MODE(0)
4662306a36Sopenharmony_ci#define AIO12_8_ADC_MODE_INT_CLK	AIO12_8_ADC_MODE(1)
4762306a36Sopenharmony_ci#define AIO12_8_ADC_MODE_STANDBY	AIO12_8_ADC_MODE(2)
4862306a36Sopenharmony_ci#define AIO12_8_ADC_MODE_POWERDOWN	AIO12_8_ADC_MODE(3)
4962306a36Sopenharmony_ci#define AIO12_8_ADC_ACQ(x)		(((x) & 0x1) << 5)
5062306a36Sopenharmony_ci#define AIO12_8_ADC_ACQ_3USEC		AIO12_8_ADC_ACQ(0)
5162306a36Sopenharmony_ci#define AIO12_8_ADC_ACQ_PROGRAM		AIO12_8_ADC_ACQ(1)
5262306a36Sopenharmony_ci#define AIO12_8_ADC_RANGE(x)		((x) << 3)
5362306a36Sopenharmony_ci#define AIO12_8_ADC_CHAN(x)		((x) << 0)
5462306a36Sopenharmony_ci#define AIO12_8_DAC_REG(x)		(0x04 + (x) * 2)
5562306a36Sopenharmony_ci#define AIO12_8_8254_BASE_REG		0x0c
5662306a36Sopenharmony_ci#define AIO12_8_8255_BASE_REG		0x10
5762306a36Sopenharmony_ci#define AIO12_8_DIO_CONTROL_REG		0x14
5862306a36Sopenharmony_ci#define AIO12_8_DIO_CONTROL_TST		BIT(0)
5962306a36Sopenharmony_ci#define AIO12_8_ADC_TRIGGER_REG		0x15
6062306a36Sopenharmony_ci#define AIO12_8_ADC_TRIGGER_RANGE(x)	((x) << 3)
6162306a36Sopenharmony_ci#define AIO12_8_ADC_TRIGGER_CHAN(x)	((x) << 0)
6262306a36Sopenharmony_ci#define AIO12_8_TRIGGER_REG		0x16
6362306a36Sopenharmony_ci#define AIO12_8_TRIGGER_ADTRIG		BIT(1)
6462306a36Sopenharmony_ci#define AIO12_8_TRIGGER_DACTRIG		BIT(0)
6562306a36Sopenharmony_ci#define AIO12_8_COS_REG			0x17
6662306a36Sopenharmony_ci#define AIO12_8_DAC_ENABLE_REG		0x18
6762306a36Sopenharmony_ci#define AIO12_8_DAC_ENABLE_REF_ENA	BIT(0)
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic const struct comedi_lrange aio_aio12_8_range = {
7062306a36Sopenharmony_ci	4, {
7162306a36Sopenharmony_ci		UNI_RANGE(5),
7262306a36Sopenharmony_ci		BIP_RANGE(5),
7362306a36Sopenharmony_ci		UNI_RANGE(10),
7462306a36Sopenharmony_ci		BIP_RANGE(10)
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci};
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistruct aio12_8_boardtype {
7962306a36Sopenharmony_ci	const char *name;
8062306a36Sopenharmony_ci	unsigned int has_ai:1;
8162306a36Sopenharmony_ci	unsigned int has_ao:1;
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic const struct aio12_8_boardtype board_types[] = {
8562306a36Sopenharmony_ci	{
8662306a36Sopenharmony_ci		.name		= "aio_aio12_8",
8762306a36Sopenharmony_ci		.has_ai		= 1,
8862306a36Sopenharmony_ci		.has_ao		= 1,
8962306a36Sopenharmony_ci	}, {
9062306a36Sopenharmony_ci		.name		= "aio_ai12_8",
9162306a36Sopenharmony_ci		.has_ai		= 1,
9262306a36Sopenharmony_ci	}, {
9362306a36Sopenharmony_ci		.name		= "aio_ao12_4",
9462306a36Sopenharmony_ci		.has_ao		= 1,
9562306a36Sopenharmony_ci	},
9662306a36Sopenharmony_ci};
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic int aio_aio12_8_ai_eoc(struct comedi_device *dev,
9962306a36Sopenharmony_ci			      struct comedi_subdevice *s,
10062306a36Sopenharmony_ci			      struct comedi_insn *insn,
10162306a36Sopenharmony_ci			      unsigned long context)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	unsigned int status;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	status = inb(dev->iobase + AIO12_8_STATUS_REG);
10662306a36Sopenharmony_ci	if (status & AIO12_8_STATUS_ADC_EOC)
10762306a36Sopenharmony_ci		return 0;
10862306a36Sopenharmony_ci	return -EBUSY;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic int aio_aio12_8_ai_read(struct comedi_device *dev,
11262306a36Sopenharmony_ci			       struct comedi_subdevice *s,
11362306a36Sopenharmony_ci			       struct comedi_insn *insn,
11462306a36Sopenharmony_ci			       unsigned int *data)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	unsigned int chan = CR_CHAN(insn->chanspec);
11762306a36Sopenharmony_ci	unsigned int range = CR_RANGE(insn->chanspec);
11862306a36Sopenharmony_ci	unsigned int val;
11962306a36Sopenharmony_ci	unsigned char control;
12062306a36Sopenharmony_ci	int ret;
12162306a36Sopenharmony_ci	int i;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/*
12462306a36Sopenharmony_ci	 * Setup the control byte for internal 2MHz clock, 3uS conversion,
12562306a36Sopenharmony_ci	 * at the desired range of the requested channel.
12662306a36Sopenharmony_ci	 */
12762306a36Sopenharmony_ci	control = AIO12_8_ADC_MODE_NORMAL | AIO12_8_ADC_ACQ_3USEC |
12862306a36Sopenharmony_ci		  AIO12_8_ADC_RANGE(range) | AIO12_8_ADC_CHAN(chan);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* Read status to clear EOC latch */
13162306a36Sopenharmony_ci	inb(dev->iobase + AIO12_8_STATUS_REG);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++) {
13462306a36Sopenharmony_ci		/*  Setup and start conversion */
13562306a36Sopenharmony_ci		outb(control, dev->iobase + AIO12_8_ADC_REG);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		/*  Wait for conversion to complete */
13862306a36Sopenharmony_ci		ret = comedi_timeout(dev, s, insn, aio_aio12_8_ai_eoc, 0);
13962306a36Sopenharmony_ci		if (ret)
14062306a36Sopenharmony_ci			return ret;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		val = inw(dev->iobase + AIO12_8_ADC_REG) & s->maxdata;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci		/* munge bipolar 2's complement data to offset binary */
14562306a36Sopenharmony_ci		if (comedi_range_is_bipolar(s, range))
14662306a36Sopenharmony_ci			val = comedi_offset_munge(s, val);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci		data[i] = val;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return insn->n;
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic int aio_aio12_8_ao_insn_write(struct comedi_device *dev,
15562306a36Sopenharmony_ci				     struct comedi_subdevice *s,
15662306a36Sopenharmony_ci				     struct comedi_insn *insn,
15762306a36Sopenharmony_ci				     unsigned int *data)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	unsigned int chan = CR_CHAN(insn->chanspec);
16062306a36Sopenharmony_ci	unsigned int val = s->readback[chan];
16162306a36Sopenharmony_ci	int i;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	/* enable DACs */
16462306a36Sopenharmony_ci	outb(AIO12_8_DAC_ENABLE_REF_ENA, dev->iobase + AIO12_8_DAC_ENABLE_REG);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++) {
16762306a36Sopenharmony_ci		val = data[i];
16862306a36Sopenharmony_ci		outw(val, dev->iobase + AIO12_8_DAC_REG(chan));
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci	s->readback[chan] = val;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	return insn->n;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic int aio_aio12_8_counter_insn_config(struct comedi_device *dev,
17662306a36Sopenharmony_ci					   struct comedi_subdevice *s,
17762306a36Sopenharmony_ci					   struct comedi_insn *insn,
17862306a36Sopenharmony_ci					   unsigned int *data)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	unsigned int chan = CR_CHAN(insn->chanspec);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	switch (data[0]) {
18362306a36Sopenharmony_ci	case INSN_CONFIG_GET_CLOCK_SRC:
18462306a36Sopenharmony_ci		/*
18562306a36Sopenharmony_ci		 * Channels 0 and 2 have external clock sources.
18662306a36Sopenharmony_ci		 * Channel 1 has a fixed 1 MHz clock source.
18762306a36Sopenharmony_ci		 */
18862306a36Sopenharmony_ci		data[0] = 0;
18962306a36Sopenharmony_ci		data[1] = (chan == 1) ? I8254_OSC_BASE_1MHZ : 0;
19062306a36Sopenharmony_ci		break;
19162306a36Sopenharmony_ci	default:
19262306a36Sopenharmony_ci		return -EINVAL;
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return insn->n;
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic int aio_aio12_8_attach(struct comedi_device *dev,
19962306a36Sopenharmony_ci			      struct comedi_devconfig *it)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	const struct aio12_8_boardtype *board = dev->board_ptr;
20262306a36Sopenharmony_ci	struct comedi_subdevice *s;
20362306a36Sopenharmony_ci	int ret;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	ret = comedi_request_region(dev, it->options[0], 32);
20662306a36Sopenharmony_ci	if (ret)
20762306a36Sopenharmony_ci		return ret;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	dev->pacer = comedi_8254_init(dev->iobase + AIO12_8_8254_BASE_REG,
21062306a36Sopenharmony_ci				      0, I8254_IO8, 0);
21162306a36Sopenharmony_ci	if (!dev->pacer)
21262306a36Sopenharmony_ci		return -ENOMEM;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	ret = comedi_alloc_subdevices(dev, 4);
21562306a36Sopenharmony_ci	if (ret)
21662306a36Sopenharmony_ci		return ret;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* Analog Input subdevice */
21962306a36Sopenharmony_ci	s = &dev->subdevices[0];
22062306a36Sopenharmony_ci	if (board->has_ai) {
22162306a36Sopenharmony_ci		s->type		= COMEDI_SUBD_AI;
22262306a36Sopenharmony_ci		s->subdev_flags	= SDF_READABLE | SDF_GROUND | SDF_DIFF;
22362306a36Sopenharmony_ci		s->n_chan	= 8;
22462306a36Sopenharmony_ci		s->maxdata	= 0x0fff;
22562306a36Sopenharmony_ci		s->range_table	= &aio_aio12_8_range;
22662306a36Sopenharmony_ci		s->insn_read	= aio_aio12_8_ai_read;
22762306a36Sopenharmony_ci	} else {
22862306a36Sopenharmony_ci		s->type = COMEDI_SUBD_UNUSED;
22962306a36Sopenharmony_ci	}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* Analog Output subdevice */
23262306a36Sopenharmony_ci	s = &dev->subdevices[1];
23362306a36Sopenharmony_ci	if (board->has_ao) {
23462306a36Sopenharmony_ci		s->type		= COMEDI_SUBD_AO;
23562306a36Sopenharmony_ci		s->subdev_flags	= SDF_WRITABLE | SDF_GROUND;
23662306a36Sopenharmony_ci		s->n_chan	= 4;
23762306a36Sopenharmony_ci		s->maxdata	= 0x0fff;
23862306a36Sopenharmony_ci		s->range_table	= &aio_aio12_8_range;
23962306a36Sopenharmony_ci		s->insn_write	= aio_aio12_8_ao_insn_write;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci		ret = comedi_alloc_subdev_readback(s);
24262306a36Sopenharmony_ci		if (ret)
24362306a36Sopenharmony_ci			return ret;
24462306a36Sopenharmony_ci	} else {
24562306a36Sopenharmony_ci		s->type = COMEDI_SUBD_UNUSED;
24662306a36Sopenharmony_ci	}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	/* Digital I/O subdevice (8255) */
24962306a36Sopenharmony_ci	s = &dev->subdevices[2];
25062306a36Sopenharmony_ci	ret = subdev_8255_init(dev, s, NULL, AIO12_8_8255_BASE_REG);
25162306a36Sopenharmony_ci	if (ret)
25262306a36Sopenharmony_ci		return ret;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	/* Counter subdevice (8254) */
25562306a36Sopenharmony_ci	s = &dev->subdevices[3];
25662306a36Sopenharmony_ci	comedi_8254_subdevice_init(s, dev->pacer);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	dev->pacer->insn_config = aio_aio12_8_counter_insn_config;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	return 0;
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic struct comedi_driver aio_aio12_8_driver = {
26462306a36Sopenharmony_ci	.driver_name	= "aio_aio12_8",
26562306a36Sopenharmony_ci	.module		= THIS_MODULE,
26662306a36Sopenharmony_ci	.attach		= aio_aio12_8_attach,
26762306a36Sopenharmony_ci	.detach		= comedi_legacy_detach,
26862306a36Sopenharmony_ci	.board_name	= &board_types[0].name,
26962306a36Sopenharmony_ci	.num_names	= ARRAY_SIZE(board_types),
27062306a36Sopenharmony_ci	.offset		= sizeof(struct aio12_8_boardtype),
27162306a36Sopenharmony_ci};
27262306a36Sopenharmony_cimodule_comedi_driver(aio_aio12_8_driver);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ciMODULE_AUTHOR("Comedi https://www.comedi.org");
27562306a36Sopenharmony_ciMODULE_DESCRIPTION("Comedi driver for Access I/O AIO12-8 Analog I/O Board");
27662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
277