162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * kcomedilib/kcomedilib.c
462306a36Sopenharmony_ci * a comedlib interface for kernel modules
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * COMEDI - Linux Control and Measurement Device Interface
762306a36Sopenharmony_ci * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/errno.h>
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/sched.h>
1562306a36Sopenharmony_ci#include <linux/fcntl.h>
1662306a36Sopenharmony_ci#include <linux/mm.h>
1762306a36Sopenharmony_ci#include <linux/io.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/comedi.h>
2062306a36Sopenharmony_ci#include <linux/comedi/comedidev.h>
2162306a36Sopenharmony_ci#include <linux/comedi/comedilib.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ciMODULE_AUTHOR("David Schleef <ds@schleef.org>");
2462306a36Sopenharmony_ciMODULE_DESCRIPTION("Comedi kernel library");
2562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistruct comedi_device *comedi_open(const char *filename)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	struct comedi_device *dev, *retval = NULL;
3062306a36Sopenharmony_ci	unsigned int minor;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	if (strncmp(filename, "/dev/comedi", 11) != 0)
3362306a36Sopenharmony_ci		return NULL;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	if (kstrtouint(filename + 11, 0, &minor))
3662306a36Sopenharmony_ci		return NULL;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	if (minor >= COMEDI_NUM_BOARD_MINORS)
3962306a36Sopenharmony_ci		return NULL;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	dev = comedi_dev_get_from_minor(minor);
4262306a36Sopenharmony_ci	if (!dev)
4362306a36Sopenharmony_ci		return NULL;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	down_read(&dev->attach_lock);
4662306a36Sopenharmony_ci	if (dev->attached)
4762306a36Sopenharmony_ci		retval = dev;
4862306a36Sopenharmony_ci	else
4962306a36Sopenharmony_ci		retval = NULL;
5062306a36Sopenharmony_ci	up_read(&dev->attach_lock);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	if (!retval)
5362306a36Sopenharmony_ci		comedi_dev_put(dev);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	return retval;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_open);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciint comedi_close(struct comedi_device *dev)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	comedi_dev_put(dev);
6262306a36Sopenharmony_ci	return 0;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_close);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic int comedi_do_insn(struct comedi_device *dev,
6762306a36Sopenharmony_ci			  struct comedi_insn *insn,
6862306a36Sopenharmony_ci			  unsigned int *data)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	struct comedi_subdevice *s;
7162306a36Sopenharmony_ci	int ret;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	mutex_lock(&dev->mutex);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	if (!dev->attached) {
7662306a36Sopenharmony_ci		ret = -EINVAL;
7762306a36Sopenharmony_ci		goto error;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/* a subdevice instruction */
8162306a36Sopenharmony_ci	if (insn->subdev >= dev->n_subdevices) {
8262306a36Sopenharmony_ci		ret = -EINVAL;
8362306a36Sopenharmony_ci		goto error;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci	s = &dev->subdevices[insn->subdev];
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	if (s->type == COMEDI_SUBD_UNUSED) {
8862306a36Sopenharmony_ci		dev_err(dev->class_dev,
8962306a36Sopenharmony_ci			"%d not usable subdevice\n", insn->subdev);
9062306a36Sopenharmony_ci		ret = -EIO;
9162306a36Sopenharmony_ci		goto error;
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	/* XXX check lock */
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	ret = comedi_check_chanlist(s, 1, &insn->chanspec);
9762306a36Sopenharmony_ci	if (ret < 0) {
9862306a36Sopenharmony_ci		dev_err(dev->class_dev, "bad chanspec\n");
9962306a36Sopenharmony_ci		ret = -EINVAL;
10062306a36Sopenharmony_ci		goto error;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (s->busy) {
10462306a36Sopenharmony_ci		ret = -EBUSY;
10562306a36Sopenharmony_ci		goto error;
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci	s->busy = dev;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	switch (insn->insn) {
11062306a36Sopenharmony_ci	case INSN_BITS:
11162306a36Sopenharmony_ci		ret = s->insn_bits(dev, s, insn, data);
11262306a36Sopenharmony_ci		break;
11362306a36Sopenharmony_ci	case INSN_CONFIG:
11462306a36Sopenharmony_ci		/* XXX should check instruction length */
11562306a36Sopenharmony_ci		ret = s->insn_config(dev, s, insn, data);
11662306a36Sopenharmony_ci		break;
11762306a36Sopenharmony_ci	default:
11862306a36Sopenharmony_ci		ret = -EINVAL;
11962306a36Sopenharmony_ci		break;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	s->busy = NULL;
12362306a36Sopenharmony_cierror:
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	mutex_unlock(&dev->mutex);
12662306a36Sopenharmony_ci	return ret;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ciint comedi_dio_get_config(struct comedi_device *dev, unsigned int subdev,
13062306a36Sopenharmony_ci			  unsigned int chan, unsigned int *io)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct comedi_insn insn;
13362306a36Sopenharmony_ci	unsigned int data[2];
13462306a36Sopenharmony_ci	int ret;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	memset(&insn, 0, sizeof(insn));
13762306a36Sopenharmony_ci	insn.insn = INSN_CONFIG;
13862306a36Sopenharmony_ci	insn.n = 2;
13962306a36Sopenharmony_ci	insn.subdev = subdev;
14062306a36Sopenharmony_ci	insn.chanspec = CR_PACK(chan, 0, 0);
14162306a36Sopenharmony_ci	data[0] = INSN_CONFIG_DIO_QUERY;
14262306a36Sopenharmony_ci	data[1] = 0;
14362306a36Sopenharmony_ci	ret = comedi_do_insn(dev, &insn, data);
14462306a36Sopenharmony_ci	if (ret >= 0)
14562306a36Sopenharmony_ci		*io = data[1];
14662306a36Sopenharmony_ci	return ret;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_dio_get_config);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ciint comedi_dio_config(struct comedi_device *dev, unsigned int subdev,
15162306a36Sopenharmony_ci		      unsigned int chan, unsigned int io)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct comedi_insn insn;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	memset(&insn, 0, sizeof(insn));
15662306a36Sopenharmony_ci	insn.insn = INSN_CONFIG;
15762306a36Sopenharmony_ci	insn.n = 1;
15862306a36Sopenharmony_ci	insn.subdev = subdev;
15962306a36Sopenharmony_ci	insn.chanspec = CR_PACK(chan, 0, 0);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return comedi_do_insn(dev, &insn, &io);
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_dio_config);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ciint comedi_dio_bitfield2(struct comedi_device *dev, unsigned int subdev,
16662306a36Sopenharmony_ci			 unsigned int mask, unsigned int *bits,
16762306a36Sopenharmony_ci			 unsigned int base_channel)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	struct comedi_insn insn;
17062306a36Sopenharmony_ci	unsigned int data[2];
17162306a36Sopenharmony_ci	unsigned int n_chan;
17262306a36Sopenharmony_ci	unsigned int shift;
17362306a36Sopenharmony_ci	int ret;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	base_channel = CR_CHAN(base_channel);
17662306a36Sopenharmony_ci	n_chan = comedi_get_n_channels(dev, subdev);
17762306a36Sopenharmony_ci	if (base_channel >= n_chan)
17862306a36Sopenharmony_ci		return -EINVAL;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	memset(&insn, 0, sizeof(insn));
18162306a36Sopenharmony_ci	insn.insn = INSN_BITS;
18262306a36Sopenharmony_ci	insn.chanspec = base_channel;
18362306a36Sopenharmony_ci	insn.n = 2;
18462306a36Sopenharmony_ci	insn.subdev = subdev;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	data[0] = mask;
18762306a36Sopenharmony_ci	data[1] = *bits;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	/*
19062306a36Sopenharmony_ci	 * Most drivers ignore the base channel in insn->chanspec.
19162306a36Sopenharmony_ci	 * Fix this here if the subdevice has <= 32 channels.
19262306a36Sopenharmony_ci	 */
19362306a36Sopenharmony_ci	if (n_chan <= 32) {
19462306a36Sopenharmony_ci		shift = base_channel;
19562306a36Sopenharmony_ci		if (shift) {
19662306a36Sopenharmony_ci			insn.chanspec = 0;
19762306a36Sopenharmony_ci			data[0] <<= shift;
19862306a36Sopenharmony_ci			data[1] <<= shift;
19962306a36Sopenharmony_ci		}
20062306a36Sopenharmony_ci	} else {
20162306a36Sopenharmony_ci		shift = 0;
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	ret = comedi_do_insn(dev, &insn, data);
20562306a36Sopenharmony_ci	*bits = data[1] >> shift;
20662306a36Sopenharmony_ci	return ret;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_dio_bitfield2);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ciint comedi_find_subdevice_by_type(struct comedi_device *dev, int type,
21162306a36Sopenharmony_ci				  unsigned int subd)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	struct comedi_subdevice *s;
21462306a36Sopenharmony_ci	int ret = -ENODEV;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	down_read(&dev->attach_lock);
21762306a36Sopenharmony_ci	if (dev->attached)
21862306a36Sopenharmony_ci		for (; subd < dev->n_subdevices; subd++) {
21962306a36Sopenharmony_ci			s = &dev->subdevices[subd];
22062306a36Sopenharmony_ci			if (s->type == type) {
22162306a36Sopenharmony_ci				ret = subd;
22262306a36Sopenharmony_ci				break;
22362306a36Sopenharmony_ci			}
22462306a36Sopenharmony_ci		}
22562306a36Sopenharmony_ci	up_read(&dev->attach_lock);
22662306a36Sopenharmony_ci	return ret;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_find_subdevice_by_type);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ciint comedi_get_n_channels(struct comedi_device *dev, unsigned int subdevice)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	int n;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	down_read(&dev->attach_lock);
23562306a36Sopenharmony_ci	if (!dev->attached || subdevice >= dev->n_subdevices)
23662306a36Sopenharmony_ci		n = 0;
23762306a36Sopenharmony_ci	else
23862306a36Sopenharmony_ci		n = dev->subdevices[subdevice].n_chan;
23962306a36Sopenharmony_ci	up_read(&dev->attach_lock);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	return n;
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_get_n_channels);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic int __init kcomedilib_module_init(void)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	return 0;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic void __exit kcomedilib_module_exit(void)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cimodule_init(kcomedilib_module_init);
25562306a36Sopenharmony_cimodule_exit(kcomedilib_module_exit);
256