xref: /kernel/linux/linux-6.6/drivers/comedi/range.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * comedi/range.c
462306a36Sopenharmony_ci * comedi routines for voltage ranges
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * COMEDI - Linux Control and Measurement Device Interface
762306a36Sopenharmony_ci * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/uaccess.h>
1162306a36Sopenharmony_ci#include <linux/comedi/comedidev.h>
1262306a36Sopenharmony_ci#include "comedi_internal.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ciconst struct comedi_lrange range_bipolar10 = { 1, {BIP_RANGE(10)} };
1562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_bipolar10);
1662306a36Sopenharmony_ciconst struct comedi_lrange range_bipolar5 = { 1, {BIP_RANGE(5)} };
1762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_bipolar5);
1862306a36Sopenharmony_ciconst struct comedi_lrange range_bipolar2_5 = { 1, {BIP_RANGE(2.5)} };
1962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_bipolar2_5);
2062306a36Sopenharmony_ciconst struct comedi_lrange range_unipolar10 = { 1, {UNI_RANGE(10)} };
2162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_unipolar10);
2262306a36Sopenharmony_ciconst struct comedi_lrange range_unipolar5 = { 1, {UNI_RANGE(5)} };
2362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_unipolar5);
2462306a36Sopenharmony_ciconst struct comedi_lrange range_unipolar2_5 = { 1, {UNI_RANGE(2.5)} };
2562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_unipolar2_5);
2662306a36Sopenharmony_ciconst struct comedi_lrange range_0_20mA = { 1, {RANGE_mA(0, 20)} };
2762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_0_20mA);
2862306a36Sopenharmony_ciconst struct comedi_lrange range_4_20mA = { 1, {RANGE_mA(4, 20)} };
2962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_4_20mA);
3062306a36Sopenharmony_ciconst struct comedi_lrange range_0_32mA = { 1, {RANGE_mA(0, 32)} };
3162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_0_32mA);
3262306a36Sopenharmony_ciconst struct comedi_lrange range_unknown = { 1, {{0, 1000000, UNIT_none} } };
3362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(range_unknown);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * COMEDI_RANGEINFO ioctl
3762306a36Sopenharmony_ci * range information
3862306a36Sopenharmony_ci *
3962306a36Sopenharmony_ci * arg:
4062306a36Sopenharmony_ci *	pointer to comedi_rangeinfo structure
4162306a36Sopenharmony_ci *
4262306a36Sopenharmony_ci * reads:
4362306a36Sopenharmony_ci *	comedi_rangeinfo structure
4462306a36Sopenharmony_ci *
4562306a36Sopenharmony_ci * writes:
4662306a36Sopenharmony_ci *	array of comedi_krange structures to rangeinfo->range_ptr pointer
4762306a36Sopenharmony_ci */
4862306a36Sopenharmony_ciint do_rangeinfo_ioctl(struct comedi_device *dev,
4962306a36Sopenharmony_ci		       struct comedi_rangeinfo *it)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	int subd, chan;
5262306a36Sopenharmony_ci	const struct comedi_lrange *lr;
5362306a36Sopenharmony_ci	struct comedi_subdevice *s;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	subd = (it->range_type >> 24) & 0xf;
5662306a36Sopenharmony_ci	chan = (it->range_type >> 16) & 0xff;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if (!dev->attached)
5962306a36Sopenharmony_ci		return -EINVAL;
6062306a36Sopenharmony_ci	if (subd >= dev->n_subdevices)
6162306a36Sopenharmony_ci		return -EINVAL;
6262306a36Sopenharmony_ci	s = &dev->subdevices[subd];
6362306a36Sopenharmony_ci	if (s->range_table) {
6462306a36Sopenharmony_ci		lr = s->range_table;
6562306a36Sopenharmony_ci	} else if (s->range_table_list) {
6662306a36Sopenharmony_ci		if (chan >= s->n_chan)
6762306a36Sopenharmony_ci			return -EINVAL;
6862306a36Sopenharmony_ci		lr = s->range_table_list[chan];
6962306a36Sopenharmony_ci	} else {
7062306a36Sopenharmony_ci		return -EINVAL;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (RANGE_LENGTH(it->range_type) != lr->length) {
7462306a36Sopenharmony_ci		dev_dbg(dev->class_dev,
7562306a36Sopenharmony_ci			"wrong length %d should be %d (0x%08x)\n",
7662306a36Sopenharmony_ci			RANGE_LENGTH(it->range_type),
7762306a36Sopenharmony_ci			lr->length, it->range_type);
7862306a36Sopenharmony_ci		return -EINVAL;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (copy_to_user(it->range_ptr, lr->range,
8262306a36Sopenharmony_ci			 sizeof(struct comedi_krange) * lr->length))
8362306a36Sopenharmony_ci		return -EFAULT;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	return 0;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/**
8962306a36Sopenharmony_ci * comedi_check_chanlist() - Validate each element in a chanlist.
9062306a36Sopenharmony_ci * @s: comedi_subdevice struct
9162306a36Sopenharmony_ci * @n: number of elements in the chanlist
9262306a36Sopenharmony_ci * @chanlist: the chanlist to validate
9362306a36Sopenharmony_ci *
9462306a36Sopenharmony_ci * Each element consists of a channel number, a range index, an analog
9562306a36Sopenharmony_ci * reference type and some flags, all packed into an unsigned int.
9662306a36Sopenharmony_ci *
9762306a36Sopenharmony_ci * This checks that the channel number and range index are supported by
9862306a36Sopenharmony_ci * the comedi subdevice.  It does not check whether the analog reference
9962306a36Sopenharmony_ci * type and the flags are supported.  Drivers that care should check those
10062306a36Sopenharmony_ci * themselves.
10162306a36Sopenharmony_ci *
10262306a36Sopenharmony_ci * Return: %0 if all @chanlist elements are valid (success),
10362306a36Sopenharmony_ci *         %-EINVAL if one or more elements are invalid.
10462306a36Sopenharmony_ci */
10562306a36Sopenharmony_ciint comedi_check_chanlist(struct comedi_subdevice *s, int n,
10662306a36Sopenharmony_ci			  unsigned int *chanlist)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct comedi_device *dev = s->device;
10962306a36Sopenharmony_ci	unsigned int chanspec;
11062306a36Sopenharmony_ci	int chan, range_len, i;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	for (i = 0; i < n; i++) {
11362306a36Sopenharmony_ci		chanspec = chanlist[i];
11462306a36Sopenharmony_ci		chan = CR_CHAN(chanspec);
11562306a36Sopenharmony_ci		if (s->range_table)
11662306a36Sopenharmony_ci			range_len = s->range_table->length;
11762306a36Sopenharmony_ci		else if (s->range_table_list && chan < s->n_chan)
11862306a36Sopenharmony_ci			range_len = s->range_table_list[chan]->length;
11962306a36Sopenharmony_ci		else
12062306a36Sopenharmony_ci			range_len = 0;
12162306a36Sopenharmony_ci		if (chan >= s->n_chan ||
12262306a36Sopenharmony_ci		    CR_RANGE(chanspec) >= range_len) {
12362306a36Sopenharmony_ci			dev_warn(dev->class_dev,
12462306a36Sopenharmony_ci				 "bad chanlist[%d]=0x%08x chan=%d range length=%d\n",
12562306a36Sopenharmony_ci				 i, chanspec, chan, range_len);
12662306a36Sopenharmony_ci			return -EINVAL;
12762306a36Sopenharmony_ci		}
12862306a36Sopenharmony_ci	}
12962306a36Sopenharmony_ci	return 0;
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(comedi_check_chanlist);
132