2017-10-08 15:41 GMT+02:00 Jonathan Cameron <jic23@xxxxxxxxxx>: > 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 :) May I suggest you to write a gpio-counter driver instead ? (that was in my todo list) It is more or less the same than this driver but instead of simulate the signal it could use gpio to them provide a software quadratic counter. Benjamin >> >> 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