Re: [[PATCH v2] 2/2] Altera Modular ADC driver support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




On 01/09/15 22:49, Chee Nouk Phoo wrote:
> From: Chee Nouk Phoon <cnphoon@xxxxxxxxxx>
> 
> Altera Modular ADC is soft IP that wraps the hardened ADC block in a Max10
> device. It can be configured to dual ADC mode that supports two channel
> synchronous sampling or independent single ADCs. ADC sampled values will be
> written into memory slots in sequence determined by a user configurable
> sequencer block.
> 
> This patch adds Altera Modular ADC driver support
> 
> Signed-off-by: Chee Nouk Phoon <cnphoon@xxxxxxxxxx>
Sorry, I wrote a review of version 1 but for some reason seem not have
sent it.  Anyhow, will comment on this version now.

I'm feeling rather guilty about this as there are a number of elements
in this driver that are major issues and I meant to get back to you on
them over a week ago!  Oops.

Anyhow, big ones in here are unusual uses of the ABI.  One point to note
is that if you ever add any ABI not documented in Documentation/ABI/testing/*
then you MUST document it as part of your patch.  It saves a lot
of time whilst the reviewer figures out what the ABI being presented
actually is.

Dual ADCs should not have their two elements passed out through a single
sysfs attribute.  If you need to maintain their relationship it must
be done by using the buffered interface not the sysfs one.  Doing it
any other way leads to inconsistent and confusing userspace ABI which
would be a disaster.

Extended naming (tied up with the above) has very limited use cases and
must be documented.  It confuses userspace applications by encoding
information in a freeform way within the attribute name. As such the
form must be clearly documented and justified.  The use case here is
not justified.

Anyhow, whilst these sound major, the driver is actually in pretty good
shape and the other stuff is minor tidy ups / formatting suggestions.

Thanks,

Jonathan
> ---
>  drivers/iio/adc/Kconfig           |   11 ++
>  drivers/iio/adc/Makefile          |    1 +
>  drivers/iio/adc/alt_modular_adc.c |  322 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 334 insertions(+), 0 deletions(-)
>  mode change 100644 => 100755 drivers/iio/adc/Kconfig
>  create mode 100755 drivers/iio/adc/alt_modular_adc.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> old mode 100644
> new mode 100755
> index 50c103d..e1a67cb
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -118,6 +118,17 @@ config AD799X
>  	  To compile this driver as a module, choose M here: the module will be
>  	  called ad799x.
>  
> +config ALT_MODULAR_ADC
> +	tristate "Altera Modular ADC driver"
> +	select ANON_INODES
> +
> +	help
> +	  Say yes here to have support for Altera Modular ADC. The driver
> +	  supports both Altera Modular ADC and Altera Modular Dual ADC.
> +
> +	  The driver can also be build as a module. If so the module will be
> +	  called alt_modular_adc.
> +
>  config AT91_ADC
>  	tristate "Atmel AT91 ADC"
>  	depends on ARCH_AT91
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index a096210..d7f10e0 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -38,3 +38,4 @@ obj-$(CONFIG_VF610_ADC) += vf610_adc.o
>  obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
>  xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o
>  obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o
> +obj-$(CONFIG_ALT_MODULAR_ADC) += alt_modular_adc.o
> diff --git a/drivers/iio/adc/alt_modular_adc.c b/drivers/iio/adc/alt_modular_adc.c
> new file mode 100755
> index 0000000..3d2d870
> --- /dev/null
> +++ b/drivers/iio/adc/alt_modular_adc.c
> @@ -0,0 +1,322 @@
> +/*
> + * Copyright (C) 2015 Altera Corporation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include <linux/iio/iio.h>
> +
> +/* Constant Definitions */
Please prefix all constants iwth ALT_MODULAR_ADC_ or similar
to avoid potential name clashes in the future.

> +#define MAX_SLOT		64
> +#define MAX_ADC			2
> +#define MAX_CHANNEL		18
> +#define MODE_SINGLE_ADC		1
> +#define MODE_DUAL_ADC		2
> +#define ADC_BITS		12
> +#define ADC_STORE_BITS		16
> +#define ADC_MAX_STR_SIZE        20
> +
> +/* Register Definitions */
> +#define ADC_CMD_REG		0x0
> +#define ADC_IER_REG		0x100
> +#define ADC_ISR_REG		0x104
> +
> +#define ADC_RUN_MSK		0x1
> +#define ADC_SINGLE_MKS		0x2
> +#define ADC_STOP_MSK		0x0
> +#define ADC_LOW_MSK		0xFFF
> +#define ADC_HIGH_MSK		0xFFF0000
> +
I'd like to see kernel-doc description fo this structure.
It's not immediately obvious what all the parts are!

> +struct altera_adc {
> +	void __iomem		*seq_regs;
> +	void __iomem		*sample_regs;
> +
> +	unsigned int		mode;
> +	unsigned int		slot_count;
> +	unsigned int		slot_sequence[MAX_ADC][MAX_SLOT];
Without looking at the bindings doc, it's not obvious what adc_number.
I'd prefer enough local docs to make that clear!
> +	unsigned int		adc_number;
> +};
> +
> +static int alt_modular_adc_read_raw(struct iio_dev *indio_dev,
> +				struct iio_chan_spec const *chan,
> +				int *val,
> +				int *val2,
> +				long mask)
> +{
> +	struct altera_adc *adc = iio_priv(indio_dev);
> +	int ret = -EINVAL;
> +	u32 value;
> +
> +	if (mask != IIO_CHAN_INFO_RAW)
> +		return -EINVAL;
> +
> +	value = readl_relaxed(adc->sample_regs + (chan->address * 4));
> +
> +	if (adc->mode == MODE_SINGLE_ADC) {
> +		*val = (value & ADC_LOW_MSK);
> +		ret = IIO_VAL_INT;
> +	} else if (adc->mode == MODE_DUAL_ADC) {
> +		*val = (value & ADC_LOW_MSK);
> +		*val2 = ((value & ADC_HIGH_MSK) >> 16);

No.  This is not part of the ABI and is absolutely not the intent.
You can't pass two separate ADC readings from different channels through
the sysfs interface like this.  It's been requested a number of times
but the write way to do this is to implement the buffered interface
and push the out that way.  One sysfs attribute, one value is the rule!
The exceptions are for element that have no meaning whatsoever on their own
(such as quaternions) where any given element has no scale without the rest.
That is not true here.

> +		ret = IIO_VAL_INT_MULTIPLE;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct iio_info adc_iio_info = {
> +	.read_raw = &alt_modular_adc_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static int alt_modular_adc_channel_init(struct iio_dev *indio_dev)
> +{
> +	struct altera_adc *adc = iio_priv(indio_dev);
> +	struct iio_chan_spec *chan_array;
> +	struct iio_chan_spec *chan;
> +	char **channel_name;
> +	int i;
> +
> +	chan_array = devm_kzalloc(&indio_dev->dev, (adc->slot_count *
> +		sizeof(*chan_array)), GFP_KERNEL);
> +	if (chan_array == NULL)
> +		return -ENOMEM;
> +
> +	channel_name = devm_kzalloc(&indio_dev->dev, adc->slot_count,
> +		GFP_KERNEL);
> +	if (channel_name == NULL)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < adc->slot_count; i++) {
> +		channel_name[i] = devm_kzalloc(&indio_dev->dev,
> +				ADC_MAX_STR_SIZE, GFP_KERNEL);
No need to do this here, use devm_kasprintf as suggested below.
If not I think you could easily have combined this loop with the one
below simplifying the code.

> +		if (channel_name == NULL)
> +			return -ENOMEM;
> +	}
> +
> +	chan = chan_array;
> +	for (i = 0; i < adc->slot_count; i++, chan++) {
> +		chan->type = IIO_VOLTAGE;
> +		chan->indexed = 1;
> +		chan->channel = i;
> +		chan->address = i;
No need to set address if it is always being set to the same
value as channel.  Channel is available everywhere address is so
just use that value.

> +
> +		/* Construct iio sysfs name*/
> +		if (adc->mode == MODE_SINGLE_ADC) {
Use kasprintf and you can skip the earlier allocation of channel_name[i]
plus allocate exactly what is needed.
> +			snprintf(channel_name[i], ADC_MAX_STR_SIZE,
> +				"adc%d-ch%d",
> +				adc->adc_number,
> +				adc->slot_sequence[0][i]);
> +		} else if (adc->mode == MODE_DUAL_ADC) {
> +			snprintf(channel_name[i], ADC_MAX_STR_SIZE,
> +				"adc1-ch%d_adc2-ch%d",
> +				adc->slot_sequence[0][i],
> +				adc->slot_sequence[1][i]);
> +		} else {
> +			return -EINVAL;
> +		}
> +
> +		chan->datasheet_name = channel_name[i];
> +		chan->extend_name = channel_name[i];
You are making use of extend_name in an unusual way.
1) This results in non standard ABI so should be documented under
Documentation/ABI/testing/sysfs-bus-iio*
2) The naming here makes me think you are abusing the abi and pushing two
channels through the same sysfs attribute.  Don't do that.  If you need
to clearly capture two channels together then implement the buffered
interface (chardev reading of 'scans' - groups of channels captured at
a similar time) rather than abusing the simpler sysfs route.
> +		chan->scan_index = i;
You aren't providing buffered access yet so don't bother filling in
scan_index or scan_type fo rhte channels (they are unusued).
> +		chan->scan_type.sign = 'u';
> +		chan->scan_type.realbits = ADC_BITS;
> +		chan->scan_type.storagebits = ADC_STORE_BITS;
> +		chan->scan_type.endianness = IIO_CPU;
> +		chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> +	}
> +
> +	indio_dev->channels = chan_array;
Might as well just use indio_dev->channels directly rather than
having the local variable.

> +
> +	return 0;
> +}
> +
> +static const struct of_device_id alt_modular_adc_match[] = {
> +	{ .compatible = "altr,modular-adc-1.0" },
> +	{ .compatible = "altr,modular-dual-adc-1.0" },
> +	{ /* Sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, alt_modular_adc_match);
> +
> +static int alt_modular_adc_parse_dt(struct iio_dev *indio_dev,
> +				struct device *dev)
> +{
> +	struct altera_adc *adc = iio_priv(indio_dev);
> +	struct device_node *np = dev->of_node;
> +	u32 value;
> +	int ret, i, j;
> +	char str[50];
> +
> +	ret = of_property_read_u32(np, "altr,adc-mode", &value);
> +	if (ret < 0)
> +		return ret;
> +	if (value == 0 || value > MAX_ADC) {
> +		dev_err(dev, "Invalid ADC mode value");
> +		return -EINVAL;
> +	}
> +	adc->mode = value;
> +
> +	ret = of_property_read_u32(np, "altr,adc-slot-count", &value);
> +	if (ret < 0)
> +		return ret;
> +	if (value == 0 || value > MAX_SLOT) {
> +		dev_err(dev, "Invalid ADC slot count value");
> +		return -EINVAL;
> +	}
> +	adc->slot_count = value;
> +
> +	ret = of_property_read_u32(np, "altr,adc-number", &value);
> +	if (ret < 0)
> +		return ret;
> +	if (value == 0 || value > MAX_ADC) {
> +		dev_err(dev, "Invalid ADC number value");
> +		return -EINVAL;
> +	}
> +	adc->adc_number = value;
> +
> +	/* Device tree lookup for channels for each memory slots */
> +	for (j = 0; j < adc->mode; j++) {
> +		for (i = 0; i < adc->slot_count; i++) {
> +			str[0] = '\0';
> +
> +			snprintf(str, sizeof(str),
> +				"altr,adc%d-slot-sequence-%d",
> +				(j + 1), (i + 1));
> +
> +			ret = of_property_read_u32(np, str, &value);
> +			if (ret < 0)
> +				return ret;
> +			if (value > MAX_CHANNEL) {
> +				dev_err(dev, "Invalid ADC channel value");
> +				return -EINVAL;
> +			}
> +			adc->slot_sequence[j][i] = value;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int alt_modular_adc_probe(struct platform_device *pdev)
> +{
> +	struct altera_adc *adc;
> +	struct device_node *np = pdev->dev.of_node;
> +	struct iio_dev *indio_dev;
> +	struct resource *mem;
> +	int ret;
> +
> +	if (!np)
> +		return -ENODEV;
> +
> +	indio_dev = iio_device_alloc(sizeof(struct altera_adc));
Convention has become sizeof(*adc) as it makes the connection slightly
more explicit.

> +	if (!indio_dev) {
> +		dev_err(&pdev->dev, "failed allocating iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	adc = iio_priv(indio_dev);
> +
> +	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +		"sequencer_csr");
> +	adc->seq_regs = devm_ioremap_resource(&pdev->dev, mem);
> +	if (IS_ERR(adc->seq_regs)) {
> +		ret = PTR_ERR(adc->seq_regs);
> +		goto err_iio;
> +	}
> +
> +	mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +		"sample_store_csr");
> +	adc->sample_regs = devm_ioremap_resource(&pdev->dev, mem);
> +	if (IS_ERR(adc->sample_regs)) {
> +		ret = PTR_ERR(adc->sample_regs);
> +		goto err_iio;
> +	}
> +
> +	ret = alt_modular_adc_parse_dt(indio_dev, &pdev->dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed to parse device tree\n");
> +		goto err_iio;
> +	}
> +
> +	ret = alt_modular_adc_channel_init(indio_dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "failed initialize ADC channels\n");
> +		goto err_iio;
> +	}
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +
> +	indio_dev->name = dev_name(&pdev->dev);
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->dev.of_node = pdev->dev.of_node;
> +	indio_dev->info = &adc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->num_channels = adc->slot_count;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto err_iio;
> +
> +	/* Disable Interrupt */
> +	writel_relaxed(0, (adc->sample_regs + ADC_IER_REG));
Given we don't seem to handle interrupts anywhere I'd expect the device
to have come up with this disabled, but if it doesn't, this definitely
belongs a lot earlier than here.
> +
> +	/* Start Continuous Sampling */
> +	writel_relaxed((ADC_RUN_MSK), (adc->seq_regs + ADC_CMD_REG));

I'm not sure it makes sense to do either of these last two steps
after the userspace interface is started by the iio_device_register
call.

> +
> +	return 0;
> +
Only 1 blank line here please (pretty much always considered enough
in kernel coding style).
> +
> +err_iio:
> +	iio_device_free(indio_dev);
> +	return ret;
> +}
> +
> +static int alt_modular_adc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +	struct altera_adc *adc = iio_priv(indio_dev);
> +

If you don't unregister the iio_device first there is a clear
race condition with userspace requests hitting after you've
stopped the ADC but before the interfaces have been removed / marked
as going away by that unregister call.

> +	/* Stop ADC */
> +	writel((ADC_STOP_MSK), (adc->seq_regs + ADC_CMD_REG));

Still  an excess of brackets.  You don't need them around a lot
of these arguments.  We can reasonably assume they don't contain
any malicious elements that could lead to it doing other than the
obvious.
	writel(ADC_STOP_MSK, adc->seq_regs + ADC_CMD_REG);
will be fine (similar cases elsewhere in the driver).

> +
> +	/* Unregister ADC */
> +	iio_device_unregister(indio_dev);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver altr_modular_adc_driver = {
> +	.probe		= alt_modular_adc_probe,
> +	.remove		= alt_modular_adc_remove,
> +	.driver		= {
> +		.name	= "alt-modular-adc",
> +		.owner	= THIS_MODULE,
> +		.of_match_table = alt_modular_adc_match,
> +	},
> +};
> +
> +
> +module_platform_driver(altr_modular_adc_driver);
> +
> +MODULE_DESCRIPTION("Altera Modular ADC Driver");
> +MODULE_AUTHOR("Chee Nouk Phoon <cnphoon@xxxxxxxxxx>");
> +MODULE_LICENSE("GPL v2");
> 

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux