Re: [PATCH v3 5/6] iio: Add dummy counter driver

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

 



On Thu,  5 Oct 2017 14:14:38 -0400
William Breathitt Gray <vilhelm.gray@xxxxxxxxx> wrote:

> This patch introduces the dummy counter driver. The dummy counter driver
> serves as a reference implementation of a driver that utilizes the
> Generic Counter interface.

This is great - I was planning to write one of these to try out the
interface and you've already done it :)
> 
> Writing individual '1' and '0' characters to the Signal attributes
> allows a user to simulate a typical Counter Signal input stream for
> evaluation; the Counter will evaluate the Signal data based on the
> respective trigger mode for the associated Signal, and trigger the
> associated counter function specified by the respective function mode.
> The current Value value may be read, and the Value value preset by a
> write.
> 
> Signed-off-by: William Breathitt Gray <vilhelm.gray@xxxxxxxxx>

Comments are more generic suggestions for improving the example than
general comments on the ABI - that all seems to make reasonable sense
(other than when the documents contain the wonderful
Value value - not confusing at all ;)

> ---
>  drivers/iio/counter/Kconfig         |  15 ++
>  drivers/iio/counter/Makefile        |   1 +
>  drivers/iio/counter/dummy-counter.c | 293 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 309 insertions(+)
>  create mode 100644 drivers/iio/counter/dummy-counter.c
> 
> diff --git a/drivers/iio/counter/Kconfig b/drivers/iio/counter/Kconfig
> index c8becfe78e28..494aed40e9c9 100644
> --- a/drivers/iio/counter/Kconfig
> +++ b/drivers/iio/counter/Kconfig
> @@ -22,6 +22,21 @@ config 104_QUAD_8
>  	  The base port addresses for the devices may be configured via the base
>  	  array module parameter.
>  
> +config DUMMY_COUNTER
> +	tristate "Dummy counter driver"
> +	help
> +	  Select this option to enable the dummy counter driver. The dummy
> +	  counter driver serves as a reference implementation of a driver that
> +	  utilizes the Generic Counter interface.
> +
> +	  Writing individual '1' and '0' characters to the Signal attributes
> +	  allows a user to simulate a typical Counter Signal input stream for
> +	  evaluation; the Counter will evaluate the Signal data based on the
> +	  respective trigger mode for the associated Signal, and trigger the
> +	  associated counter function specified by the respective function mode.
> +	  The current Value value may be read, and the Value value preset by a
> +	  write.
> +
>  config STM32_LPTIMER_CNT
>  	tristate "STM32 LP Timer encoder counter driver"
>  	depends on MFD_STM32_LPTIMER || COMPILE_TEST
> diff --git a/drivers/iio/counter/Makefile b/drivers/iio/counter/Makefile
> index 1b9a896eb488..8c2ef0115426 100644
> --- a/drivers/iio/counter/Makefile
> +++ b/drivers/iio/counter/Makefile
> @@ -5,4 +5,5 @@
>  # When adding new entries keep the list in alphabetical order
>  
>  obj-$(CONFIG_104_QUAD_8)	+= 104-quad-8.o
> +obj-$(CONFIG_DUMMY_COUNTER)	+= dummy-counter.o
>  obj-$(CONFIG_STM32_LPTIMER_CNT)	+= stm32-lptimer-cnt.o
> diff --git a/drivers/iio/counter/dummy-counter.c b/drivers/iio/counter/dummy-counter.c
> new file mode 100644
> index 000000000000..6ecc9854894f
> --- /dev/null
> +++ b/drivers/iio/counter/dummy-counter.c
> @@ -0,0 +1,293 @@
> +/*
> + * Dummy counter driver
> + * Copyright (C) 2017 William Breathitt Gray
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License, version 2, as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that 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.
> + */
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/iio/counter.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +#define DUMCNT_NUM_COUNTERS 2
> +/**
> + * struct dumcnt - private data structure
> + * @counter:	instance of the iio_counter
> + * @counts:	array of accumulation values
> + * @states:	array of input line states
> + */
> +struct dumcnt {
> +	struct iio_counter counter;
> +	unsigned int counts[DUMCNT_NUM_COUNTERS];
> +	unsigned int states[DUMCNT_NUM_COUNTERS];
> +};
> +
> +static int dumcnt_signal_read(struct iio_counter *counter,
> +	struct iio_counter_signal *signal, int *val, int *val2)
> +{
> +	struct dumcnt *const priv = counter->driver_data;
> +	*val = priv->states[signal->id];
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static int dumcnt_signal_write(struct iio_counter *counter,
> +	struct iio_counter_signal *signal, int val, int val2)
> +{

This is an odd one to have in the generic interface.
What real hardware does writing the state make sense for?
If it is only fake drivers - figure out a way to do it without
having to add to the generic interfaces.

> +	struct dumcnt *const priv = counter->driver_data;
> +	const unsigned int id = signal->id;
> +	const unsigned int prev_state = priv->states[id];
> +	struct iio_counter_value *const value = counter->values + id;
> +	const unsigned int function_mode = value->mode;
> +	const unsigned int trigger_mode = value->triggers[0].mode;
> +	unsigned int triggered = 0;
> +
> +	if (val && val != 1)
> +		return -EINVAL;
> +
> +	/* If no state change then just exit */
> +	if (prev_state == val)
> +		return 0;
> +
> +	priv->states[id] = val;
> +
> +	switch (trigger_mode) {
> +	/* "none" case */
> +	case 0:
> +		return 0;
> +	/* "rising edge" case */
> +	case 1:
> +		if (!prev_state)
> +			triggered = 1;
> +		break;
> +	/* "falling edge" case */
> +	case 2:
> +		if (prev_state)
> +			triggered = 1;
> +		break;
> +	/* "both edges" case */
> +	case 3:
> +		triggered = 1;
> +		break;
> +	}
> +
> +	/* If counter function triggered */
> +	if (triggered)
> +		/* "increase" case */
> +		if (function_mode)
> +			priv->counts[id]++;
> +		/* "decrease" case */
> +		else
> +			priv->counts[id]--;
> +
> +	return 0;
> +}
> +
> +static int dumcnt_trigger_mode_set(struct iio_counter *counter,
> +	struct iio_counter_value *value, struct iio_counter_trigger *trigger,
> +	unsigned int mode)
> +{
> +	if (mode >= trigger->num_trigger_modes)
> +		return -EINVAL;
> +
> +	trigger->mode = mode;
> +
> +	return 0;
> +}
> +
> +static int dumcnt_trigger_mode_get(struct iio_counter *counter,
> +	struct iio_counter_value *value, struct iio_counter_trigger *trigger)
> +{
> +	return trigger->mode;
> +}
> +
> +static int dumcnt_value_read(struct iio_counter *counter,
> +	struct iio_counter_value *value, int *val, int *val2)
> +{
> +	struct dumcnt *const priv = counter->driver_data;
> +
> +	*val = priv->counts[value->id];
> +
> +	return IIO_VAL_INT;
> +}
> +
> +static int dumcnt_value_write(struct iio_counter *counter,
> +	struct iio_counter_value *value, int val, int val2)
> +{
> +	struct dumcnt *const priv = counter->driver_data;
> +
> +	priv->counts[value->id] = val;
> +
> +	return 0;
> +}
> +
> +static int dumcnt_value_function_set(struct iio_counter *counter,
> +	struct iio_counter_value *value, unsigned int mode)
> +{
> +	if (mode >= value->num_function_modes)
> +		return -EINVAL;
> +
> +	value->mode = mode;
> +
> +	return 0;
> +}
> +
> +static int dumcnt_value_function_get(struct iio_counter *counter,
> +	struct iio_counter_value *value)
> +{
> +	return value->mode;

If it's called function in the function name, call it function in 
the structure as well rather than mode.

> +}
> +
> +static const struct iio_counter_ops dumcnt_ops = {
> +	.signal_read = dumcnt_signal_read,
> +	.signal_write = dumcnt_signal_write,
> +	.trigger_mode_get = dumcnt_trigger_mode_get,
> +	.trigger_mode_set = dumcnt_trigger_mode_set,
> +	.value_read = dumcnt_value_read,
> +	.value_write = dumcnt_value_write,
> +	.value_function_set = dumcnt_value_function_set,
> +	.value_function_get = dumcnt_value_function_get
> +};
> +
> +static const char *const dumcnt_function_modes[] = {
> +	"decrease",

I think increment was used somewhere in the docs... It's
clearer, but you need to document this ABI to stop having
subtle variations of it like this (even if I imagined it ;)

> +	"increase"
> +};
> +
> +#define DUMCNT_SIGNAL(_id, _name) {	\
> +	.id = _id,			\
> +	.name = _name			\
> +}
> +
> +static const struct iio_counter_signal dumcnt_signals[] = {
> +	DUMCNT_SIGNAL(0, "Signal A"), DUMCNT_SIGNAL(1, "Signal B")
> +};
> +
> +#define DUMCNT_VALUE(_id, _name) {					\
> +	.id = _id,							\
> +	.name = _name,							\
> +	.mode = 0,							\
> +	.function_modes = dumcnt_function_modes,			\
> +	.num_function_modes = ARRAY_SIZE(dumcnt_function_modes)		\
> +}
> +
> +static const struct iio_counter_value dumcnt_values[] = {
> +	DUMCNT_VALUE(0, "Count A"), DUMCNT_VALUE(1, "Count B")
> +};
> +
> +static const char *const dumcnt_trigger_modes[] = {

As mentioned below, use an enum for the index as then you can make it obvious
what 0 means when you set the mode to it later.

> +	"none",
> +	"rising edge",
> +	"falling edge",
> +	"both edges"
> +};
> +
> +static int dumcnt_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct iio_counter_signal *signals;
> +	const size_t num_signals = ARRAY_SIZE(dumcnt_signals);

Don't bother with local variable - makes it less obvious what
is going on.

> +	struct iio_counter_value *values;
> +	const size_t num_values = ARRAY_SIZE(dumcnt_values);

Local variable doesn't add anything and if anything makes
it slightly harder to check what is going on.

> +	struct iio_counter_trigger *triggers;
> +	int i;
> +	struct dumcnt *dumcnt;
> +
> +	signals = devm_kmalloc(dev, sizeof(dumcnt_signals), GFP_KERNEL);
> +	if (!signals)
> +		return -ENOMEM;
> +
> +	memcpy(signals, dumcnt_signals, sizeof(dumcnt_signals));

devm_kmemdup?
> +
> +	values = devm_kmalloc(dev, sizeof(dumcnt_values), GFP_KERNEL);
> +	if (!values)
> +		return -ENOMEM;
> +
> +	memcpy(values, dumcnt_values, sizeof(dumcnt_values));

devm_kmemdup?

> +
> +	/* Associate values with their respective signals */
> +	for (i = 0; i < num_values; i++) {
> +		triggers = devm_kmalloc(dev, sizeof(*triggers), GFP_KERNEL);
> +		if (!triggers)
> +			return -ENOMEM;
> +
> +		triggers->mode = 0;

Use an enum for the dumcn_trigger_modes array index then specify by enum value
here.  Will make it more readable.

> +		triggers->trigger_modes = dumcnt_trigger_modes;
> +		triggers->num_trigger_modes = ARRAY_SIZE(dumcnt_trigger_modes);
> +		triggers->signal = &signals[i];
> +
> +		values[i].triggers = triggers;
> +		values[i].num_triggers = 1;
> +	}
> +
> +	dumcnt = devm_kzalloc(dev, sizeof(*dumcnt), GFP_KERNEL);
> +	if (!dumcnt)
> +		return -ENOMEM;
> +
> +	dumcnt->counter.name = dev_name(dev);
> +	dumcnt->counter.dev = dev;
> +	dumcnt->counter.ops = &dumcnt_ops;
> +	dumcnt->counter.signals = signals;
> +	dumcnt->counter.num_signals = num_signals;
> +	dumcnt->counter.values = values;
> +	dumcnt->counter.num_values = num_values;
> +	dumcnt->counter.driver_data = dumcnt;
> +
> +	return devm_iio_counter_register(dev, &dumcnt->counter);
> +}
> +
> +static struct platform_device *dumcnt_device;

Support multiple instances - nick this stuff from the
IIO dummy driver or more specifically the
industrialio-sw-device.c

> +
> +static struct platform_driver dumcnt_driver = {
> +	.driver = {
> +		.name = "104-quad-8"

Don't do that!  Give it it's own name.

> +	}
> +};
> +
> +static void __exit dumcnt_exit(void)
> +{
> +	platform_device_unregister(dumcnt_device);
> +	platform_driver_unregister(&dumcnt_driver);
> +}
> +
> +static int __init dumcnt_init(void)
> +{
> +	int err;
> +

General thing, but if we are going to upstream this with the subsystem,
make device instantiation happen via configfs.

> +	dumcnt_device = platform_device_alloc(dumcnt_driver.driver.name, -1);
> +	if (!dumcnt_device)
> +		return -ENOMEM;
> +
> +	err = platform_device_add(dumcnt_device);
> +	if (err)
> +		goto err_platform_device;
> +
> +	err = platform_driver_probe(&dumcnt_driver, dumcnt_probe);
> +	if (err)
> +		goto err_platform_driver;
> +
> +	return 0;
> +
> +err_platform_driver:
> +	platform_device_del(dumcnt_device);
> +err_platform_device:
> +	platform_device_put(dumcnt_device);
> +	return err;
> +}
> +
> +module_init(dumcnt_init);
> +module_exit(dumcnt_exit);
> +
> +MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@xxxxxxxxx>");
> +MODULE_DESCRIPTION("Dummy counter driver");
> +MODULE_LICENSE("GPL v2");

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



[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux