diff options
Diffstat (limited to 'drivers/counter')
-rw-r--r-- | drivers/counter/104-quad-8.c | 1606 | ||||
-rw-r--r-- | drivers/counter/Kconfig | 84 | ||||
-rw-r--r-- | drivers/counter/Makefile | 13 | ||||
-rw-r--r-- | drivers/counter/counter.c | 1496 | ||||
-rw-r--r-- | drivers/counter/ftm-quaddec.c | 344 | ||||
-rw-r--r-- | drivers/counter/microchip-tcb-capture.c | 411 | ||||
-rw-r--r-- | drivers/counter/stm32-lptimer-cnt.c | 527 | ||||
-rw-r--r-- | drivers/counter/stm32-timer-cnt.c | 456 | ||||
-rw-r--r-- | drivers/counter/ti-eqep.c | 431 |
9 files changed, 5368 insertions, 0 deletions
diff --git a/drivers/counter/104-quad-8.c b/drivers/counter/104-quad-8.c new file mode 100644 index 000000000..89c9cb850 --- /dev/null +++ b/drivers/counter/104-quad-8.c @@ -0,0 +1,1606 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Counter driver for the ACCES 104-QUAD-8 + * Copyright (C) 2016 William Breathitt Gray + * + * This driver supports the ACCES 104-QUAD-8 and ACCES 104-QUAD-4. + */ +#include <linux/bitops.h> +#include <linux/counter.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/iio/iio.h> +#include <linux/iio/types.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/isa.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> + +#define QUAD8_EXTENT 32 + +static unsigned int base[max_num_isa_dev(QUAD8_EXTENT)]; +static unsigned int num_quad8; +module_param_array(base, uint, &num_quad8, 0); +MODULE_PARM_DESC(base, "ACCES 104-QUAD-8 base addresses"); + +#define QUAD8_NUM_COUNTERS 8 + +/** + * struct quad8_iio - IIO device private data structure + * @counter: instance of the counter_device + * @fck_prescaler: array of filter clock prescaler configurations + * @preset: array of preset values + * @count_mode: array of count mode configurations + * @quadrature_mode: array of quadrature mode configurations + * @quadrature_scale: array of quadrature mode scale configurations + * @ab_enable: array of A and B inputs enable configurations + * @preset_enable: array of set_to_preset_on_index attribute configurations + * @synchronous_mode: array of index function synchronous mode configurations + * @index_polarity: array of index function polarity configurations + * @cable_fault_enable: differential encoder cable status enable configurations + * @base: base port address of the IIO device + */ +struct quad8_iio { + struct mutex lock; + struct counter_device counter; + unsigned int fck_prescaler[QUAD8_NUM_COUNTERS]; + unsigned int preset[QUAD8_NUM_COUNTERS]; + unsigned int count_mode[QUAD8_NUM_COUNTERS]; + unsigned int quadrature_mode[QUAD8_NUM_COUNTERS]; + unsigned int quadrature_scale[QUAD8_NUM_COUNTERS]; + unsigned int ab_enable[QUAD8_NUM_COUNTERS]; + unsigned int preset_enable[QUAD8_NUM_COUNTERS]; + unsigned int synchronous_mode[QUAD8_NUM_COUNTERS]; + unsigned int index_polarity[QUAD8_NUM_COUNTERS]; + unsigned int cable_fault_enable; + unsigned int base; +}; + +#define QUAD8_REG_CHAN_OP 0x11 +#define QUAD8_REG_INDEX_INPUT_LEVELS 0x16 +#define QUAD8_DIFF_ENCODER_CABLE_STATUS 0x17 +/* Error flag */ +#define QUAD8_FLAG_E BIT(4) +/* Up/Down flag */ +#define QUAD8_FLAG_UD BIT(5) +/* Reset and Load Signal Decoders */ +#define QUAD8_CTR_RLD 0x00 +/* Counter Mode Register */ +#define QUAD8_CTR_CMR 0x20 +/* Input / Output Control Register */ +#define QUAD8_CTR_IOR 0x40 +/* Index Control Register */ +#define QUAD8_CTR_IDR 0x60 +/* Reset Byte Pointer (three byte data pointer) */ +#define QUAD8_RLD_RESET_BP 0x01 +/* Reset Counter */ +#define QUAD8_RLD_RESET_CNTR 0x02 +/* Reset Borrow Toggle, Carry Toggle, Compare Toggle, and Sign flags */ +#define QUAD8_RLD_RESET_FLAGS 0x04 +/* Reset Error flag */ +#define QUAD8_RLD_RESET_E 0x06 +/* Preset Register to Counter */ +#define QUAD8_RLD_PRESET_CNTR 0x08 +/* Transfer Counter to Output Latch */ +#define QUAD8_RLD_CNTR_OUT 0x10 +/* Transfer Preset Register LSB to FCK Prescaler */ +#define QUAD8_RLD_PRESET_PSC 0x18 +#define QUAD8_CHAN_OP_ENABLE_COUNTERS 0x00 +#define QUAD8_CHAN_OP_RESET_COUNTERS 0x01 +#define QUAD8_CMR_QUADRATURE_X1 0x08 +#define QUAD8_CMR_QUADRATURE_X2 0x10 +#define QUAD8_CMR_QUADRATURE_X4 0x18 + + +static int quad8_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel; + int i; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_INDEX) { + *val = !!(inb(priv->base + QUAD8_REG_INDEX_INPUT_LEVELS) + & BIT(chan->channel)); + return IIO_VAL_INT; + } + + *val = 0; + + mutex_lock(&priv->lock); + + /* Reset Byte Pointer; transfer Counter to Output Latch */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_CNTR_OUT, + base_offset + 1); + + for (i = 0; i < 3; i++) + *val |= (unsigned int)inb(base_offset) << (8 * i); + + mutex_unlock(&priv->lock); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_ENABLE: + *val = priv->ab_enable[chan->channel]; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 1; + *val2 = priv->quadrature_scale[chan->channel]; + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int quad8_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel; + int i; + unsigned int ior_cfg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_INDEX) + return -EINVAL; + + /* Only 24-bit values are supported */ + if ((unsigned int)val > 0xFFFFFF) + return -EINVAL; + + mutex_lock(&priv->lock); + + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + + /* Counter can only be set via Preset Register */ + for (i = 0; i < 3; i++) + outb(val >> (8 * i), base_offset); + + /* Transfer Preset Register to Counter */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_PRESET_CNTR, base_offset + 1); + + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + + /* Set Preset Register back to original value */ + val = priv->preset[chan->channel]; + for (i = 0; i < 3; i++) + outb(val >> (8 * i), base_offset); + + /* Reset Borrow, Carry, Compare, and Sign flags */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); + /* Reset Error flag */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); + + mutex_unlock(&priv->lock); + + return 0; + case IIO_CHAN_INFO_ENABLE: + /* only boolean values accepted */ + if (val < 0 || val > 1) + return -EINVAL; + + mutex_lock(&priv->lock); + + priv->ab_enable[chan->channel] = val; + + ior_cfg = val | priv->preset_enable[chan->channel] << 1; + + /* Load I/O control configuration */ + outb(QUAD8_CTR_IOR | ior_cfg, base_offset + 1); + + mutex_unlock(&priv->lock); + + return 0; + case IIO_CHAN_INFO_SCALE: + mutex_lock(&priv->lock); + + /* Quadrature scaling only available in quadrature mode */ + if (!priv->quadrature_mode[chan->channel] && + (val2 || val != 1)) { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + /* Only three gain states (1, 0.5, 0.25) */ + if (val == 1 && !val2) + priv->quadrature_scale[chan->channel] = 0; + else if (!val) + switch (val2) { + case 500000: + priv->quadrature_scale[chan->channel] = 1; + break; + case 250000: + priv->quadrature_scale[chan->channel] = 2; + break; + default: + mutex_unlock(&priv->lock); + return -EINVAL; + } + else { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + mutex_unlock(&priv->lock); + return 0; + } + + return -EINVAL; +} + +static const struct iio_info quad8_info = { + .read_raw = quad8_read_raw, + .write_raw = quad8_write_raw +}; + +static ssize_t quad8_read_preset(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, char *buf) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", priv->preset[chan->channel]); +} + +static ssize_t quad8_write_preset(struct iio_dev *indio_dev, uintptr_t private, + const struct iio_chan_spec *chan, const char *buf, size_t len) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel; + unsigned int preset; + int ret; + int i; + + ret = kstrtouint(buf, 0, &preset); + if (ret) + return ret; + + /* Only 24-bit values are supported */ + if (preset > 0xFFFFFF) + return -EINVAL; + + mutex_lock(&priv->lock); + + priv->preset[chan->channel] = preset; + + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + + /* Set Preset Register */ + for (i = 0; i < 3; i++) + outb(preset >> (8 * i), base_offset); + + mutex_unlock(&priv->lock); + + return len; +} + +static ssize_t quad8_read_set_to_preset_on_index(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, char *buf) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + !priv->preset_enable[chan->channel]); +} + +static ssize_t quad8_write_set_to_preset_on_index(struct iio_dev *indio_dev, + uintptr_t private, const struct iio_chan_spec *chan, const char *buf, + size_t len) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + bool preset_enable; + int ret; + unsigned int ior_cfg; + + ret = kstrtobool(buf, &preset_enable); + if (ret) + return ret; + + /* Preset enable is active low in Input/Output Control register */ + preset_enable = !preset_enable; + + mutex_lock(&priv->lock); + + priv->preset_enable[chan->channel] = preset_enable; + + ior_cfg = priv->ab_enable[chan->channel] | + (unsigned int)preset_enable << 1; + + /* Load I/O control configuration to Input / Output Control Register */ + outb(QUAD8_CTR_IOR | ior_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return len; +} + +static const char *const quad8_noise_error_states[] = { + "No excessive noise is present at the count inputs", + "Excessive noise is present at the count inputs" +}; + +static int quad8_get_noise_error(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + + return !!(inb(base_offset) & QUAD8_FLAG_E); +} + +static const struct iio_enum quad8_noise_error_enum = { + .items = quad8_noise_error_states, + .num_items = ARRAY_SIZE(quad8_noise_error_states), + .get = quad8_get_noise_error +}; + +static const char *const quad8_count_direction_states[] = { + "down", + "up" +}; + +static int quad8_get_count_direction(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + + return !!(inb(base_offset) & QUAD8_FLAG_UD); +} + +static const struct iio_enum quad8_count_direction_enum = { + .items = quad8_count_direction_states, + .num_items = ARRAY_SIZE(quad8_count_direction_states), + .get = quad8_get_count_direction +}; + +static const char *const quad8_count_modes[] = { + "normal", + "range limit", + "non-recycle", + "modulo-n" +}; + +static int quad8_set_count_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int cnt_mode) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + unsigned int mode_cfg = cnt_mode << 1; + const int base_offset = priv->base + 2 * chan->channel + 1; + + mutex_lock(&priv->lock); + + priv->count_mode[chan->channel] = cnt_mode; + + /* Add quadrature mode configuration */ + if (priv->quadrature_mode[chan->channel]) + mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3; + + /* Load mode configuration to Counter Mode Register */ + outb(QUAD8_CTR_CMR | mode_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int quad8_get_count_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->count_mode[chan->channel]; +} + +static const struct iio_enum quad8_count_mode_enum = { + .items = quad8_count_modes, + .num_items = ARRAY_SIZE(quad8_count_modes), + .set = quad8_set_count_mode, + .get = quad8_get_count_mode +}; + +static const char *const quad8_synchronous_modes[] = { + "non-synchronous", + "synchronous" +}; + +static int quad8_set_synchronous_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int synchronous_mode) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + unsigned int idr_cfg = synchronous_mode; + + mutex_lock(&priv->lock); + + idr_cfg |= priv->index_polarity[chan->channel] << 1; + + /* Index function must be non-synchronous in non-quadrature mode */ + if (synchronous_mode && !priv->quadrature_mode[chan->channel]) { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + priv->synchronous_mode[chan->channel] = synchronous_mode; + + /* Load Index Control configuration to Index Control Register */ + outb(QUAD8_CTR_IDR | idr_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int quad8_get_synchronous_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->synchronous_mode[chan->channel]; +} + +static const struct iio_enum quad8_synchronous_mode_enum = { + .items = quad8_synchronous_modes, + .num_items = ARRAY_SIZE(quad8_synchronous_modes), + .set = quad8_set_synchronous_mode, + .get = quad8_get_synchronous_mode +}; + +static const char *const quad8_quadrature_modes[] = { + "non-quadrature", + "quadrature" +}; + +static int quad8_set_quadrature_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int quadrature_mode) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + unsigned int mode_cfg; + + mutex_lock(&priv->lock); + + mode_cfg = priv->count_mode[chan->channel] << 1; + + if (quadrature_mode) + mode_cfg |= (priv->quadrature_scale[chan->channel] + 1) << 3; + else { + /* Quadrature scaling only available in quadrature mode */ + priv->quadrature_scale[chan->channel] = 0; + + /* Synchronous function not supported in non-quadrature mode */ + if (priv->synchronous_mode[chan->channel]) + quad8_set_synchronous_mode(indio_dev, chan, 0); + } + + priv->quadrature_mode[chan->channel] = quadrature_mode; + + /* Load mode configuration to Counter Mode Register */ + outb(QUAD8_CTR_CMR | mode_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int quad8_get_quadrature_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->quadrature_mode[chan->channel]; +} + +static const struct iio_enum quad8_quadrature_mode_enum = { + .items = quad8_quadrature_modes, + .num_items = ARRAY_SIZE(quad8_quadrature_modes), + .set = quad8_set_quadrature_mode, + .get = quad8_get_quadrature_mode +}; + +static const char *const quad8_index_polarity_modes[] = { + "negative", + "positive" +}; + +static int quad8_set_index_polarity(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, unsigned int index_polarity) +{ + struct quad8_iio *const priv = iio_priv(indio_dev); + const int base_offset = priv->base + 2 * chan->channel + 1; + unsigned int idr_cfg = index_polarity << 1; + + mutex_lock(&priv->lock); + + idr_cfg |= priv->synchronous_mode[chan->channel]; + + priv->index_polarity[chan->channel] = index_polarity; + + /* Load Index Control configuration to Index Control Register */ + outb(QUAD8_CTR_IDR | idr_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int quad8_get_index_polarity(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct quad8_iio *const priv = iio_priv(indio_dev); + + return priv->index_polarity[chan->channel]; +} + +static const struct iio_enum quad8_index_polarity_enum = { + .items = quad8_index_polarity_modes, + .num_items = ARRAY_SIZE(quad8_index_polarity_modes), + .set = quad8_set_index_polarity, + .get = quad8_get_index_polarity +}; + +static const struct iio_chan_spec_ext_info quad8_count_ext_info[] = { + { + .name = "preset", + .shared = IIO_SEPARATE, + .read = quad8_read_preset, + .write = quad8_write_preset + }, + { + .name = "set_to_preset_on_index", + .shared = IIO_SEPARATE, + .read = quad8_read_set_to_preset_on_index, + .write = quad8_write_set_to_preset_on_index + }, + IIO_ENUM("noise_error", IIO_SEPARATE, &quad8_noise_error_enum), + IIO_ENUM_AVAILABLE("noise_error", &quad8_noise_error_enum), + IIO_ENUM("count_direction", IIO_SEPARATE, &quad8_count_direction_enum), + IIO_ENUM_AVAILABLE("count_direction", &quad8_count_direction_enum), + IIO_ENUM("count_mode", IIO_SEPARATE, &quad8_count_mode_enum), + IIO_ENUM_AVAILABLE("count_mode", &quad8_count_mode_enum), + IIO_ENUM("quadrature_mode", IIO_SEPARATE, &quad8_quadrature_mode_enum), + IIO_ENUM_AVAILABLE("quadrature_mode", &quad8_quadrature_mode_enum), + {} +}; + +static const struct iio_chan_spec_ext_info quad8_index_ext_info[] = { + IIO_ENUM("synchronous_mode", IIO_SEPARATE, + &quad8_synchronous_mode_enum), + IIO_ENUM_AVAILABLE("synchronous_mode", &quad8_synchronous_mode_enum), + IIO_ENUM("index_polarity", IIO_SEPARATE, &quad8_index_polarity_enum), + IIO_ENUM_AVAILABLE("index_polarity", &quad8_index_polarity_enum), + {} +}; + +#define QUAD8_COUNT_CHAN(_chan) { \ + .type = IIO_COUNT, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_ENABLE) | BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = quad8_count_ext_info, \ + .indexed = 1 \ +} + +#define QUAD8_INDEX_CHAN(_chan) { \ + .type = IIO_INDEX, \ + .channel = (_chan), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .ext_info = quad8_index_ext_info, \ + .indexed = 1 \ +} + +static const struct iio_chan_spec quad8_channels[] = { + QUAD8_COUNT_CHAN(0), QUAD8_INDEX_CHAN(0), + QUAD8_COUNT_CHAN(1), QUAD8_INDEX_CHAN(1), + QUAD8_COUNT_CHAN(2), QUAD8_INDEX_CHAN(2), + QUAD8_COUNT_CHAN(3), QUAD8_INDEX_CHAN(3), + QUAD8_COUNT_CHAN(4), QUAD8_INDEX_CHAN(4), + QUAD8_COUNT_CHAN(5), QUAD8_INDEX_CHAN(5), + QUAD8_COUNT_CHAN(6), QUAD8_INDEX_CHAN(6), + QUAD8_COUNT_CHAN(7), QUAD8_INDEX_CHAN(7) +}; + +static int quad8_signal_read(struct counter_device *counter, + struct counter_signal *signal, enum counter_signal_value *val) +{ + const struct quad8_iio *const priv = counter->priv; + unsigned int state; + + /* Only Index signal levels can be read */ + if (signal->id < 16) + return -EINVAL; + + state = inb(priv->base + QUAD8_REG_INDEX_INPUT_LEVELS) + & BIT(signal->id - 16); + + *val = (state) ? COUNTER_SIGNAL_HIGH : COUNTER_SIGNAL_LOW; + + return 0; +} + +static int quad8_count_read(struct counter_device *counter, + struct counter_count *count, unsigned long *val) +{ + struct quad8_iio *const priv = counter->priv; + const int base_offset = priv->base + 2 * count->id; + int i; + + *val = 0; + + mutex_lock(&priv->lock); + + /* Reset Byte Pointer; transfer Counter to Output Latch */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_CNTR_OUT, + base_offset + 1); + + for (i = 0; i < 3; i++) + *val |= (unsigned long)inb(base_offset) << (8 * i); + + mutex_unlock(&priv->lock); + + return 0; +} + +static int quad8_count_write(struct counter_device *counter, + struct counter_count *count, unsigned long val) +{ + struct quad8_iio *const priv = counter->priv; + const int base_offset = priv->base + 2 * count->id; + int i; + + /* Only 24-bit values are supported */ + if (val > 0xFFFFFF) + return -EINVAL; + + mutex_lock(&priv->lock); + + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + + /* Counter can only be set via Preset Register */ + for (i = 0; i < 3; i++) + outb(val >> (8 * i), base_offset); + + /* Transfer Preset Register to Counter */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_PRESET_CNTR, base_offset + 1); + + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + + /* Set Preset Register back to original value */ + val = priv->preset[count->id]; + for (i = 0; i < 3; i++) + outb(val >> (8 * i), base_offset); + + /* Reset Borrow, Carry, Compare, and Sign flags */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); + /* Reset Error flag */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); + + mutex_unlock(&priv->lock); + + return 0; +} + +enum quad8_count_function { + QUAD8_COUNT_FUNCTION_PULSE_DIRECTION = 0, + QUAD8_COUNT_FUNCTION_QUADRATURE_X1, + QUAD8_COUNT_FUNCTION_QUADRATURE_X2, + QUAD8_COUNT_FUNCTION_QUADRATURE_X4 +}; + +static enum counter_count_function quad8_count_functions_list[] = { + [QUAD8_COUNT_FUNCTION_PULSE_DIRECTION] = COUNTER_COUNT_FUNCTION_PULSE_DIRECTION, + [QUAD8_COUNT_FUNCTION_QUADRATURE_X1] = COUNTER_COUNT_FUNCTION_QUADRATURE_X1_A, + [QUAD8_COUNT_FUNCTION_QUADRATURE_X2] = COUNTER_COUNT_FUNCTION_QUADRATURE_X2_A, + [QUAD8_COUNT_FUNCTION_QUADRATURE_X4] = COUNTER_COUNT_FUNCTION_QUADRATURE_X4 +}; + +static int quad8_function_get(struct counter_device *counter, + struct counter_count *count, size_t *function) +{ + struct quad8_iio *const priv = counter->priv; + const int id = count->id; + + mutex_lock(&priv->lock); + + if (priv->quadrature_mode[id]) + switch (priv->quadrature_scale[id]) { + case 0: + *function = QUAD8_COUNT_FUNCTION_QUADRATURE_X1; + break; + case 1: + *function = QUAD8_COUNT_FUNCTION_QUADRATURE_X2; + break; + case 2: + *function = QUAD8_COUNT_FUNCTION_QUADRATURE_X4; + break; + } + else + *function = QUAD8_COUNT_FUNCTION_PULSE_DIRECTION; + + mutex_unlock(&priv->lock); + + return 0; +} + +static int quad8_function_set(struct counter_device *counter, + struct counter_count *count, size_t function) +{ + struct quad8_iio *const priv = counter->priv; + const int id = count->id; + unsigned int *const quadrature_mode = priv->quadrature_mode + id; + unsigned int *const scale = priv->quadrature_scale + id; + unsigned int *const synchronous_mode = priv->synchronous_mode + id; + const int base_offset = priv->base + 2 * id + 1; + unsigned int mode_cfg; + unsigned int idr_cfg; + + mutex_lock(&priv->lock); + + mode_cfg = priv->count_mode[id] << 1; + idr_cfg = priv->index_polarity[id] << 1; + + if (function == QUAD8_COUNT_FUNCTION_PULSE_DIRECTION) { + *quadrature_mode = 0; + + /* Quadrature scaling only available in quadrature mode */ + *scale = 0; + + /* Synchronous function not supported in non-quadrature mode */ + if (*synchronous_mode) { + *synchronous_mode = 0; + /* Disable synchronous function mode */ + outb(QUAD8_CTR_IDR | idr_cfg, base_offset); + } + } else { + *quadrature_mode = 1; + + switch (function) { + case QUAD8_COUNT_FUNCTION_QUADRATURE_X1: + *scale = 0; + mode_cfg |= QUAD8_CMR_QUADRATURE_X1; + break; + case QUAD8_COUNT_FUNCTION_QUADRATURE_X2: + *scale = 1; + mode_cfg |= QUAD8_CMR_QUADRATURE_X2; + break; + case QUAD8_COUNT_FUNCTION_QUADRATURE_X4: + *scale = 2; + mode_cfg |= QUAD8_CMR_QUADRATURE_X4; + break; + } + } + + /* Load mode configuration to Counter Mode Register */ + outb(QUAD8_CTR_CMR | mode_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static void quad8_direction_get(struct counter_device *counter, + struct counter_count *count, enum counter_count_direction *direction) +{ + const struct quad8_iio *const priv = counter->priv; + unsigned int ud_flag; + const unsigned int flag_addr = priv->base + 2 * count->id + 1; + + /* U/D flag: nonzero = up, zero = down */ + ud_flag = inb(flag_addr) & QUAD8_FLAG_UD; + + *direction = (ud_flag) ? COUNTER_COUNT_DIRECTION_FORWARD : + COUNTER_COUNT_DIRECTION_BACKWARD; +} + +enum quad8_synapse_action { + QUAD8_SYNAPSE_ACTION_NONE = 0, + QUAD8_SYNAPSE_ACTION_RISING_EDGE, + QUAD8_SYNAPSE_ACTION_FALLING_EDGE, + QUAD8_SYNAPSE_ACTION_BOTH_EDGES +}; + +static enum counter_synapse_action quad8_index_actions_list[] = { + [QUAD8_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, + [QUAD8_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE +}; + +static enum counter_synapse_action quad8_synapse_actions_list[] = { + [QUAD8_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, + [QUAD8_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE, + [QUAD8_SYNAPSE_ACTION_FALLING_EDGE] = COUNTER_SYNAPSE_ACTION_FALLING_EDGE, + [QUAD8_SYNAPSE_ACTION_BOTH_EDGES] = COUNTER_SYNAPSE_ACTION_BOTH_EDGES +}; + +static int quad8_action_get(struct counter_device *counter, + struct counter_count *count, struct counter_synapse *synapse, + size_t *action) +{ + struct quad8_iio *const priv = counter->priv; + int err; + size_t function = 0; + const size_t signal_a_id = count->synapses[0].signal->id; + enum counter_count_direction direction; + + /* Handle Index signals */ + if (synapse->signal->id >= 16) { + if (priv->preset_enable[count->id]) + *action = QUAD8_SYNAPSE_ACTION_RISING_EDGE; + else + *action = QUAD8_SYNAPSE_ACTION_NONE; + + return 0; + } + + err = quad8_function_get(counter, count, &function); + if (err) + return err; + + /* Default action mode */ + *action = QUAD8_SYNAPSE_ACTION_NONE; + + /* Determine action mode based on current count function mode */ + switch (function) { + case QUAD8_COUNT_FUNCTION_PULSE_DIRECTION: + if (synapse->signal->id == signal_a_id) + *action = QUAD8_SYNAPSE_ACTION_RISING_EDGE; + break; + case QUAD8_COUNT_FUNCTION_QUADRATURE_X1: + if (synapse->signal->id == signal_a_id) { + quad8_direction_get(counter, count, &direction); + + if (direction == COUNTER_COUNT_DIRECTION_FORWARD) + *action = QUAD8_SYNAPSE_ACTION_RISING_EDGE; + else + *action = QUAD8_SYNAPSE_ACTION_FALLING_EDGE; + } + break; + case QUAD8_COUNT_FUNCTION_QUADRATURE_X2: + if (synapse->signal->id == signal_a_id) + *action = QUAD8_SYNAPSE_ACTION_BOTH_EDGES; + break; + case QUAD8_COUNT_FUNCTION_QUADRATURE_X4: + *action = QUAD8_SYNAPSE_ACTION_BOTH_EDGES; + break; + } + + return 0; +} + +static const struct counter_ops quad8_ops = { + .signal_read = quad8_signal_read, + .count_read = quad8_count_read, + .count_write = quad8_count_write, + .function_get = quad8_function_get, + .function_set = quad8_function_set, + .action_get = quad8_action_get +}; + +static int quad8_index_polarity_get(struct counter_device *counter, + struct counter_signal *signal, size_t *index_polarity) +{ + const struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id - 16; + + *index_polarity = priv->index_polarity[channel_id]; + + return 0; +} + +static int quad8_index_polarity_set(struct counter_device *counter, + struct counter_signal *signal, size_t index_polarity) +{ + struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id - 16; + const int base_offset = priv->base + 2 * channel_id + 1; + unsigned int idr_cfg = index_polarity << 1; + + mutex_lock(&priv->lock); + + idr_cfg |= priv->synchronous_mode[channel_id]; + + priv->index_polarity[channel_id] = index_polarity; + + /* Load Index Control configuration to Index Control Register */ + outb(QUAD8_CTR_IDR | idr_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static struct counter_signal_enum_ext quad8_index_pol_enum = { + .items = quad8_index_polarity_modes, + .num_items = ARRAY_SIZE(quad8_index_polarity_modes), + .get = quad8_index_polarity_get, + .set = quad8_index_polarity_set +}; + +static int quad8_synchronous_mode_get(struct counter_device *counter, + struct counter_signal *signal, size_t *synchronous_mode) +{ + const struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id - 16; + + *synchronous_mode = priv->synchronous_mode[channel_id]; + + return 0; +} + +static int quad8_synchronous_mode_set(struct counter_device *counter, + struct counter_signal *signal, size_t synchronous_mode) +{ + struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id - 16; + const int base_offset = priv->base + 2 * channel_id + 1; + unsigned int idr_cfg = synchronous_mode; + + mutex_lock(&priv->lock); + + idr_cfg |= priv->index_polarity[channel_id] << 1; + + /* Index function must be non-synchronous in non-quadrature mode */ + if (synchronous_mode && !priv->quadrature_mode[channel_id]) { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + priv->synchronous_mode[channel_id] = synchronous_mode; + + /* Load Index Control configuration to Index Control Register */ + outb(QUAD8_CTR_IDR | idr_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static struct counter_signal_enum_ext quad8_syn_mode_enum = { + .items = quad8_synchronous_modes, + .num_items = ARRAY_SIZE(quad8_synchronous_modes), + .get = quad8_synchronous_mode_get, + .set = quad8_synchronous_mode_set +}; + +static ssize_t quad8_count_floor_read(struct counter_device *counter, + struct counter_count *count, void *private, char *buf) +{ + /* Only a floor of 0 is supported */ + return sprintf(buf, "0\n"); +} + +static int quad8_count_mode_get(struct counter_device *counter, + struct counter_count *count, size_t *cnt_mode) +{ + const struct quad8_iio *const priv = counter->priv; + + /* Map 104-QUAD-8 count mode to Generic Counter count mode */ + switch (priv->count_mode[count->id]) { + case 0: + *cnt_mode = COUNTER_COUNT_MODE_NORMAL; + break; + case 1: + *cnt_mode = COUNTER_COUNT_MODE_RANGE_LIMIT; + break; + case 2: + *cnt_mode = COUNTER_COUNT_MODE_NON_RECYCLE; + break; + case 3: + *cnt_mode = COUNTER_COUNT_MODE_MODULO_N; + break; + } + + return 0; +} + +static int quad8_count_mode_set(struct counter_device *counter, + struct counter_count *count, size_t cnt_mode) +{ + struct quad8_iio *const priv = counter->priv; + unsigned int mode_cfg; + const int base_offset = priv->base + 2 * count->id + 1; + + /* Map Generic Counter count mode to 104-QUAD-8 count mode */ + switch (cnt_mode) { + case COUNTER_COUNT_MODE_NORMAL: + cnt_mode = 0; + break; + case COUNTER_COUNT_MODE_RANGE_LIMIT: + cnt_mode = 1; + break; + case COUNTER_COUNT_MODE_NON_RECYCLE: + cnt_mode = 2; + break; + case COUNTER_COUNT_MODE_MODULO_N: + cnt_mode = 3; + break; + } + + mutex_lock(&priv->lock); + + priv->count_mode[count->id] = cnt_mode; + + /* Set count mode configuration value */ + mode_cfg = cnt_mode << 1; + + /* Add quadrature mode configuration */ + if (priv->quadrature_mode[count->id]) + mode_cfg |= (priv->quadrature_scale[count->id] + 1) << 3; + + /* Load mode configuration to Counter Mode Register */ + outb(QUAD8_CTR_CMR | mode_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return 0; +} + +static struct counter_count_enum_ext quad8_cnt_mode_enum = { + .items = counter_count_mode_str, + .num_items = ARRAY_SIZE(counter_count_mode_str), + .get = quad8_count_mode_get, + .set = quad8_count_mode_set +}; + +static ssize_t quad8_count_direction_read(struct counter_device *counter, + struct counter_count *count, void *priv, char *buf) +{ + enum counter_count_direction dir; + + quad8_direction_get(counter, count, &dir); + + return sprintf(buf, "%s\n", counter_count_direction_str[dir]); +} + +static ssize_t quad8_count_enable_read(struct counter_device *counter, + struct counter_count *count, void *private, char *buf) +{ + const struct quad8_iio *const priv = counter->priv; + + return sprintf(buf, "%u\n", priv->ab_enable[count->id]); +} + +static ssize_t quad8_count_enable_write(struct counter_device *counter, + struct counter_count *count, void *private, const char *buf, size_t len) +{ + struct quad8_iio *const priv = counter->priv; + const int base_offset = priv->base + 2 * count->id; + int err; + bool ab_enable; + unsigned int ior_cfg; + + err = kstrtobool(buf, &ab_enable); + if (err) + return err; + + mutex_lock(&priv->lock); + + priv->ab_enable[count->id] = ab_enable; + + ior_cfg = ab_enable | priv->preset_enable[count->id] << 1; + + /* Load I/O control configuration */ + outb(QUAD8_CTR_IOR | ior_cfg, base_offset + 1); + + mutex_unlock(&priv->lock); + + return len; +} + +static int quad8_error_noise_get(struct counter_device *counter, + struct counter_count *count, size_t *noise_error) +{ + const struct quad8_iio *const priv = counter->priv; + const int base_offset = priv->base + 2 * count->id + 1; + + *noise_error = !!(inb(base_offset) & QUAD8_FLAG_E); + + return 0; +} + +static struct counter_count_enum_ext quad8_error_noise_enum = { + .items = quad8_noise_error_states, + .num_items = ARRAY_SIZE(quad8_noise_error_states), + .get = quad8_error_noise_get +}; + +static ssize_t quad8_count_preset_read(struct counter_device *counter, + struct counter_count *count, void *private, char *buf) +{ + const struct quad8_iio *const priv = counter->priv; + + return sprintf(buf, "%u\n", priv->preset[count->id]); +} + +static void quad8_preset_register_set(struct quad8_iio *quad8iio, int id, + unsigned int preset) +{ + const unsigned int base_offset = quad8iio->base + 2 * id; + int i; + + quad8iio->preset[id] = preset; + + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + + /* Set Preset Register */ + for (i = 0; i < 3; i++) + outb(preset >> (8 * i), base_offset); +} + +static ssize_t quad8_count_preset_write(struct counter_device *counter, + struct counter_count *count, void *private, const char *buf, size_t len) +{ + struct quad8_iio *const priv = counter->priv; + unsigned int preset; + int ret; + + ret = kstrtouint(buf, 0, &preset); + if (ret) + return ret; + + /* Only 24-bit values are supported */ + if (preset > 0xFFFFFF) + return -EINVAL; + + mutex_lock(&priv->lock); + + quad8_preset_register_set(priv, count->id, preset); + + mutex_unlock(&priv->lock); + + return len; +} + +static ssize_t quad8_count_ceiling_read(struct counter_device *counter, + struct counter_count *count, void *private, char *buf) +{ + struct quad8_iio *const priv = counter->priv; + + mutex_lock(&priv->lock); + + /* Range Limit and Modulo-N count modes use preset value as ceiling */ + switch (priv->count_mode[count->id]) { + case 1: + case 3: + mutex_unlock(&priv->lock); + return sprintf(buf, "%u\n", priv->preset[count->id]); + } + + mutex_unlock(&priv->lock); + + /* By default 0xFFFFFF (24 bits unsigned) is maximum count */ + return sprintf(buf, "16777215\n"); +} + +static ssize_t quad8_count_ceiling_write(struct counter_device *counter, + struct counter_count *count, void *private, const char *buf, size_t len) +{ + struct quad8_iio *const priv = counter->priv; + unsigned int ceiling; + int ret; + + ret = kstrtouint(buf, 0, &ceiling); + if (ret) + return ret; + + /* Only 24-bit values are supported */ + if (ceiling > 0xFFFFFF) + return -EINVAL; + + mutex_lock(&priv->lock); + + /* Range Limit and Modulo-N count modes use preset value as ceiling */ + switch (priv->count_mode[count->id]) { + case 1: + case 3: + quad8_preset_register_set(priv, count->id, ceiling); + mutex_unlock(&priv->lock); + return len; + } + + mutex_unlock(&priv->lock); + + return -EINVAL; +} + +static ssize_t quad8_count_preset_enable_read(struct counter_device *counter, + struct counter_count *count, void *private, char *buf) +{ + const struct quad8_iio *const priv = counter->priv; + + return sprintf(buf, "%u\n", !priv->preset_enable[count->id]); +} + +static ssize_t quad8_count_preset_enable_write(struct counter_device *counter, + struct counter_count *count, void *private, const char *buf, size_t len) +{ + struct quad8_iio *const priv = counter->priv; + const int base_offset = priv->base + 2 * count->id + 1; + bool preset_enable; + int ret; + unsigned int ior_cfg; + + ret = kstrtobool(buf, &preset_enable); + if (ret) + return ret; + + /* Preset enable is active low in Input/Output Control register */ + preset_enable = !preset_enable; + + mutex_lock(&priv->lock); + + priv->preset_enable[count->id] = preset_enable; + + ior_cfg = priv->ab_enable[count->id] | (unsigned int)preset_enable << 1; + + /* Load I/O control configuration to Input / Output Control Register */ + outb(QUAD8_CTR_IOR | ior_cfg, base_offset); + + mutex_unlock(&priv->lock); + + return len; +} + +static ssize_t quad8_signal_cable_fault_read(struct counter_device *counter, + struct counter_signal *signal, + void *private, char *buf) +{ + struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id / 2; + bool disabled; + unsigned int status; + unsigned int fault; + + mutex_lock(&priv->lock); + + disabled = !(priv->cable_fault_enable & BIT(channel_id)); + + if (disabled) { + mutex_unlock(&priv->lock); + return -EINVAL; + } + + /* Logic 0 = cable fault */ + status = inb(priv->base + QUAD8_DIFF_ENCODER_CABLE_STATUS); + + mutex_unlock(&priv->lock); + + /* Mask respective channel and invert logic */ + fault = !(status & BIT(channel_id)); + + return sprintf(buf, "%u\n", fault); +} + +static ssize_t quad8_signal_cable_fault_enable_read( + struct counter_device *counter, struct counter_signal *signal, + void *private, char *buf) +{ + const struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id / 2; + const unsigned int enb = !!(priv->cable_fault_enable & BIT(channel_id)); + + return sprintf(buf, "%u\n", enb); +} + +static ssize_t quad8_signal_cable_fault_enable_write( + struct counter_device *counter, struct counter_signal *signal, + void *private, const char *buf, size_t len) +{ + struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id / 2; + bool enable; + int ret; + unsigned int cable_fault_enable; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + mutex_lock(&priv->lock); + + if (enable) + priv->cable_fault_enable |= BIT(channel_id); + else + priv->cable_fault_enable &= ~BIT(channel_id); + + /* Enable is active low in Differential Encoder Cable Status register */ + cable_fault_enable = ~priv->cable_fault_enable; + + outb(cable_fault_enable, priv->base + QUAD8_DIFF_ENCODER_CABLE_STATUS); + + mutex_unlock(&priv->lock); + + return len; +} + +static ssize_t quad8_signal_fck_prescaler_read(struct counter_device *counter, + struct counter_signal *signal, void *private, char *buf) +{ + const struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id / 2; + + return sprintf(buf, "%u\n", priv->fck_prescaler[channel_id]); +} + +static ssize_t quad8_signal_fck_prescaler_write(struct counter_device *counter, + struct counter_signal *signal, void *private, const char *buf, + size_t len) +{ + struct quad8_iio *const priv = counter->priv; + const size_t channel_id = signal->id / 2; + const int base_offset = priv->base + 2 * channel_id; + u8 prescaler; + int ret; + + ret = kstrtou8(buf, 0, &prescaler); + if (ret) + return ret; + + mutex_lock(&priv->lock); + + priv->fck_prescaler[channel_id] = prescaler; + + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + + /* Set filter clock factor */ + outb(prescaler, base_offset); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_PRESET_PSC, + base_offset + 1); + + mutex_unlock(&priv->lock); + + return len; +} + +static const struct counter_signal_ext quad8_signal_ext[] = { + { + .name = "cable_fault", + .read = quad8_signal_cable_fault_read + }, + { + .name = "cable_fault_enable", + .read = quad8_signal_cable_fault_enable_read, + .write = quad8_signal_cable_fault_enable_write + }, + { + .name = "filter_clock_prescaler", + .read = quad8_signal_fck_prescaler_read, + .write = quad8_signal_fck_prescaler_write + } +}; + +static const struct counter_signal_ext quad8_index_ext[] = { + COUNTER_SIGNAL_ENUM("index_polarity", &quad8_index_pol_enum), + COUNTER_SIGNAL_ENUM_AVAILABLE("index_polarity", &quad8_index_pol_enum), + COUNTER_SIGNAL_ENUM("synchronous_mode", &quad8_syn_mode_enum), + COUNTER_SIGNAL_ENUM_AVAILABLE("synchronous_mode", &quad8_syn_mode_enum) +}; + +#define QUAD8_QUAD_SIGNAL(_id, _name) { \ + .id = (_id), \ + .name = (_name), \ + .ext = quad8_signal_ext, \ + .num_ext = ARRAY_SIZE(quad8_signal_ext) \ +} + +#define QUAD8_INDEX_SIGNAL(_id, _name) { \ + .id = (_id), \ + .name = (_name), \ + .ext = quad8_index_ext, \ + .num_ext = ARRAY_SIZE(quad8_index_ext) \ +} + +static struct counter_signal quad8_signals[] = { + QUAD8_QUAD_SIGNAL(0, "Channel 1 Quadrature A"), + QUAD8_QUAD_SIGNAL(1, "Channel 1 Quadrature B"), + QUAD8_QUAD_SIGNAL(2, "Channel 2 Quadrature A"), + QUAD8_QUAD_SIGNAL(3, "Channel 2 Quadrature B"), + QUAD8_QUAD_SIGNAL(4, "Channel 3 Quadrature A"), + QUAD8_QUAD_SIGNAL(5, "Channel 3 Quadrature B"), + QUAD8_QUAD_SIGNAL(6, "Channel 4 Quadrature A"), + QUAD8_QUAD_SIGNAL(7, "Channel 4 Quadrature B"), + QUAD8_QUAD_SIGNAL(8, "Channel 5 Quadrature A"), + QUAD8_QUAD_SIGNAL(9, "Channel 5 Quadrature B"), + QUAD8_QUAD_SIGNAL(10, "Channel 6 Quadrature A"), + QUAD8_QUAD_SIGNAL(11, "Channel 6 Quadrature B"), + QUAD8_QUAD_SIGNAL(12, "Channel 7 Quadrature A"), + QUAD8_QUAD_SIGNAL(13, "Channel 7 Quadrature B"), + QUAD8_QUAD_SIGNAL(14, "Channel 8 Quadrature A"), + QUAD8_QUAD_SIGNAL(15, "Channel 8 Quadrature B"), + QUAD8_INDEX_SIGNAL(16, "Channel 1 Index"), + QUAD8_INDEX_SIGNAL(17, "Channel 2 Index"), + QUAD8_INDEX_SIGNAL(18, "Channel 3 Index"), + QUAD8_INDEX_SIGNAL(19, "Channel 4 Index"), + QUAD8_INDEX_SIGNAL(20, "Channel 5 Index"), + QUAD8_INDEX_SIGNAL(21, "Channel 6 Index"), + QUAD8_INDEX_SIGNAL(22, "Channel 7 Index"), + QUAD8_INDEX_SIGNAL(23, "Channel 8 Index") +}; + +#define QUAD8_COUNT_SYNAPSES(_id) { \ + { \ + .actions_list = quad8_synapse_actions_list, \ + .num_actions = ARRAY_SIZE(quad8_synapse_actions_list), \ + .signal = quad8_signals + 2 * (_id) \ + }, \ + { \ + .actions_list = quad8_synapse_actions_list, \ + .num_actions = ARRAY_SIZE(quad8_synapse_actions_list), \ + .signal = quad8_signals + 2 * (_id) + 1 \ + }, \ + { \ + .actions_list = quad8_index_actions_list, \ + .num_actions = ARRAY_SIZE(quad8_index_actions_list), \ + .signal = quad8_signals + 2 * (_id) + 16 \ + } \ +} + +static struct counter_synapse quad8_count_synapses[][3] = { + QUAD8_COUNT_SYNAPSES(0), QUAD8_COUNT_SYNAPSES(1), + QUAD8_COUNT_SYNAPSES(2), QUAD8_COUNT_SYNAPSES(3), + QUAD8_COUNT_SYNAPSES(4), QUAD8_COUNT_SYNAPSES(5), + QUAD8_COUNT_SYNAPSES(6), QUAD8_COUNT_SYNAPSES(7) +}; + +static const struct counter_count_ext quad8_count_ext[] = { + { + .name = "ceiling", + .read = quad8_count_ceiling_read, + .write = quad8_count_ceiling_write + }, + { + .name = "floor", + .read = quad8_count_floor_read + }, + COUNTER_COUNT_ENUM("count_mode", &quad8_cnt_mode_enum), + COUNTER_COUNT_ENUM_AVAILABLE("count_mode", &quad8_cnt_mode_enum), + { + .name = "direction", + .read = quad8_count_direction_read + }, + { + .name = "enable", + .read = quad8_count_enable_read, + .write = quad8_count_enable_write + }, + COUNTER_COUNT_ENUM("error_noise", &quad8_error_noise_enum), + COUNTER_COUNT_ENUM_AVAILABLE("error_noise", &quad8_error_noise_enum), + { + .name = "preset", + .read = quad8_count_preset_read, + .write = quad8_count_preset_write + }, + { + .name = "preset_enable", + .read = quad8_count_preset_enable_read, + .write = quad8_count_preset_enable_write + } +}; + +#define QUAD8_COUNT(_id, _cntname) { \ + .id = (_id), \ + .name = (_cntname), \ + .functions_list = quad8_count_functions_list, \ + .num_functions = ARRAY_SIZE(quad8_count_functions_list), \ + .synapses = quad8_count_synapses[(_id)], \ + .num_synapses = 2, \ + .ext = quad8_count_ext, \ + .num_ext = ARRAY_SIZE(quad8_count_ext) \ +} + +static struct counter_count quad8_counts[] = { + QUAD8_COUNT(0, "Channel 1 Count"), + QUAD8_COUNT(1, "Channel 2 Count"), + QUAD8_COUNT(2, "Channel 3 Count"), + QUAD8_COUNT(3, "Channel 4 Count"), + QUAD8_COUNT(4, "Channel 5 Count"), + QUAD8_COUNT(5, "Channel 6 Count"), + QUAD8_COUNT(6, "Channel 7 Count"), + QUAD8_COUNT(7, "Channel 8 Count") +}; + +static int quad8_probe(struct device *dev, unsigned int id) +{ + struct iio_dev *indio_dev; + struct quad8_iio *quad8iio; + int i, j; + unsigned int base_offset; + int err; + + if (!devm_request_region(dev, base[id], QUAD8_EXTENT, dev_name(dev))) { + dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", + base[id], base[id] + QUAD8_EXTENT); + return -EBUSY; + } + + /* Allocate IIO device; this also allocates driver data structure */ + indio_dev = devm_iio_device_alloc(dev, sizeof(*quad8iio)); + if (!indio_dev) + return -ENOMEM; + + /* Initialize IIO device */ + indio_dev->info = &quad8_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = ARRAY_SIZE(quad8_channels); + indio_dev->channels = quad8_channels; + indio_dev->name = dev_name(dev); + + /* Initialize Counter device and driver data */ + quad8iio = iio_priv(indio_dev); + quad8iio->counter.name = dev_name(dev); + quad8iio->counter.parent = dev; + quad8iio->counter.ops = &quad8_ops; + quad8iio->counter.counts = quad8_counts; + quad8iio->counter.num_counts = ARRAY_SIZE(quad8_counts); + quad8iio->counter.signals = quad8_signals; + quad8iio->counter.num_signals = ARRAY_SIZE(quad8_signals); + quad8iio->counter.priv = quad8iio; + quad8iio->base = base[id]; + + /* Initialize mutex */ + mutex_init(&quad8iio->lock); + + /* Reset all counters and disable interrupt function */ + outb(QUAD8_CHAN_OP_RESET_COUNTERS, base[id] + QUAD8_REG_CHAN_OP); + /* Set initial configuration for all counters */ + for (i = 0; i < QUAD8_NUM_COUNTERS; i++) { + base_offset = base[id] + 2 * i; + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + /* Reset filter clock factor */ + outb(0, base_offset); + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_PRESET_PSC, + base_offset + 1); + /* Reset Byte Pointer */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, base_offset + 1); + /* Reset Preset Register */ + for (j = 0; j < 3; j++) + outb(0x00, base_offset); + /* Reset Borrow, Carry, Compare, and Sign flags */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, base_offset + 1); + /* Reset Error flag */ + outb(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, base_offset + 1); + /* Binary encoding; Normal count; non-quadrature mode */ + outb(QUAD8_CTR_CMR, base_offset + 1); + /* Disable A and B inputs; preset on index; FLG1 as Carry */ + outb(QUAD8_CTR_IOR, base_offset + 1); + /* Disable index function; negative index polarity */ + outb(QUAD8_CTR_IDR, base_offset + 1); + } + /* Disable Differential Encoder Cable Status for all channels */ + outb(0xFF, base[id] + QUAD8_DIFF_ENCODER_CABLE_STATUS); + /* Enable all counters */ + outb(QUAD8_CHAN_OP_ENABLE_COUNTERS, base[id] + QUAD8_REG_CHAN_OP); + + /* Register IIO device */ + err = devm_iio_device_register(dev, indio_dev); + if (err) + return err; + + /* Register Counter device */ + return devm_counter_register(dev, &quad8iio->counter); +} + +static struct isa_driver quad8_driver = { + .probe = quad8_probe, + .driver = { + .name = "104-quad-8" + } +}; + +module_isa_driver(quad8_driver, num_quad8); + +MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); +MODULE_DESCRIPTION("ACCES 104-QUAD-8 IIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig new file mode 100644 index 000000000..cbdf84200 --- /dev/null +++ b/drivers/counter/Kconfig @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Counter devices +# + +menuconfig COUNTER + tristate "Counter support" + help + This enables counter device support through the Generic Counter + interface. You only need to enable this, if you also want to enable + one or more of the counter device drivers below. + +if COUNTER + +config 104_QUAD_8 + tristate "ACCES 104-QUAD-8 driver" + depends on PC104 && X86 && IIO + select ISA_BUS_API + help + Say yes here to build support for the ACCES 104-QUAD-8 quadrature + encoder counter/interface device family (104-QUAD-8, 104-QUAD-4). + + A counter's respective error flag may be cleared by performing a write + operation on the respective count value attribute. Although the + 104-QUAD-8 counters have a 25-bit range, only the lower 24 bits may be + set, either directly or via the counter's preset attribute. Interrupts + are not supported by this driver. + + The base port addresses for the devices may be configured via the base + array module parameter. + +config STM32_TIMER_CNT + tristate "STM32 Timer encoder counter driver" + depends on MFD_STM32_TIMERS || COMPILE_TEST + help + Select this option to enable STM32 Timer quadrature encoder + and counter driver. + + To compile this driver as a module, choose M here: the + module will be called stm32-timer-cnt. + +config STM32_LPTIMER_CNT + tristate "STM32 LP Timer encoder counter driver" + depends on MFD_STM32_LPTIMER || COMPILE_TEST + help + Select this option to enable STM32 Low-Power Timer quadrature encoder + and counter driver. + + To compile this driver as a module, choose M here: the + module will be called stm32-lptimer-cnt. + +config TI_EQEP + tristate "TI eQEP counter driver" + depends on (SOC_AM33XX || COMPILE_TEST) + select REGMAP_MMIO + help + Select this option to enable the Texas Instruments Enhanced Quadrature + Encoder Pulse (eQEP) counter driver. + + To compile this driver as a module, choose M here: the module will be + called ti-eqep. + +config FTM_QUADDEC + tristate "Flex Timer Module Quadrature decoder driver" + depends on HAS_IOMEM && OF + help + Select this option to enable the Flex Timer Quadrature decoder + driver. + + To compile this driver as a module, choose M here: the + module will be called ftm-quaddec. + +config MICROCHIP_TCB_CAPTURE + tristate "Microchip Timer Counter Capture driver" + depends on HAS_IOMEM && OF + select REGMAP_MMIO + help + Select this option to enable the Microchip Timer Counter Block + capture driver. + + To compile this driver as a module, choose M here: the + module will be called microchip-tcb-capture. + +endif # COUNTER diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile new file mode 100644 index 000000000..0a393f71e --- /dev/null +++ b/drivers/counter/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for Counter devices +# + +obj-$(CONFIG_COUNTER) += counter.o + +obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o +obj-$(CONFIG_STM32_TIMER_CNT) += stm32-timer-cnt.o +obj-$(CONFIG_STM32_LPTIMER_CNT) += stm32-lptimer-cnt.o +obj-$(CONFIG_TI_EQEP) += ti-eqep.o +obj-$(CONFIG_FTM_QUADDEC) += ftm-quaddec.o +obj-$(CONFIG_MICROCHIP_TCB_CAPTURE) += microchip-tcb-capture.o diff --git a/drivers/counter/counter.c b/drivers/counter/counter.c new file mode 100644 index 000000000..6a683d086 --- /dev/null +++ b/drivers/counter/counter.c @@ -0,0 +1,1496 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic Counter interface + * Copyright (C) 2018 William Breathitt Gray + */ +#include <linux/counter.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/fs.h> +#include <linux/gfp.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/printk.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/sysfs.h> +#include <linux/types.h> + +const char *const counter_count_direction_str[2] = { + [COUNTER_COUNT_DIRECTION_FORWARD] = "forward", + [COUNTER_COUNT_DIRECTION_BACKWARD] = "backward" +}; +EXPORT_SYMBOL_GPL(counter_count_direction_str); + +const char *const counter_count_mode_str[4] = { + [COUNTER_COUNT_MODE_NORMAL] = "normal", + [COUNTER_COUNT_MODE_RANGE_LIMIT] = "range limit", + [COUNTER_COUNT_MODE_NON_RECYCLE] = "non-recycle", + [COUNTER_COUNT_MODE_MODULO_N] = "modulo-n" +}; +EXPORT_SYMBOL_GPL(counter_count_mode_str); + +ssize_t counter_signal_enum_read(struct counter_device *counter, + struct counter_signal *signal, void *priv, + char *buf) +{ + const struct counter_signal_enum_ext *const e = priv; + int err; + size_t index; + + if (!e->get) + return -EINVAL; + + err = e->get(counter, signal, &index); + if (err) + return err; + + if (index >= e->num_items) + return -EINVAL; + + return sprintf(buf, "%s\n", e->items[index]); +} +EXPORT_SYMBOL_GPL(counter_signal_enum_read); + +ssize_t counter_signal_enum_write(struct counter_device *counter, + struct counter_signal *signal, void *priv, + const char *buf, size_t len) +{ + const struct counter_signal_enum_ext *const e = priv; + ssize_t index; + int err; + + if (!e->set) + return -EINVAL; + + index = __sysfs_match_string(e->items, e->num_items, buf); + if (index < 0) + return index; + + err = e->set(counter, signal, index); + if (err) + return err; + + return len; +} +EXPORT_SYMBOL_GPL(counter_signal_enum_write); + +ssize_t counter_signal_enum_available_read(struct counter_device *counter, + struct counter_signal *signal, + void *priv, char *buf) +{ + const struct counter_signal_enum_ext *const e = priv; + size_t i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; i++) + len += sprintf(buf + len, "%s\n", e->items[i]); + + return len; +} +EXPORT_SYMBOL_GPL(counter_signal_enum_available_read); + +ssize_t counter_count_enum_read(struct counter_device *counter, + struct counter_count *count, void *priv, + char *buf) +{ + const struct counter_count_enum_ext *const e = priv; + int err; + size_t index; + + if (!e->get) + return -EINVAL; + + err = e->get(counter, count, &index); + if (err) + return err; + + if (index >= e->num_items) + return -EINVAL; + + return sprintf(buf, "%s\n", e->items[index]); +} +EXPORT_SYMBOL_GPL(counter_count_enum_read); + +ssize_t counter_count_enum_write(struct counter_device *counter, + struct counter_count *count, void *priv, + const char *buf, size_t len) +{ + const struct counter_count_enum_ext *const e = priv; + ssize_t index; + int err; + + if (!e->set) + return -EINVAL; + + index = __sysfs_match_string(e->items, e->num_items, buf); + if (index < 0) + return index; + + err = e->set(counter, count, index); + if (err) + return err; + + return len; +} +EXPORT_SYMBOL_GPL(counter_count_enum_write); + +ssize_t counter_count_enum_available_read(struct counter_device *counter, + struct counter_count *count, + void *priv, char *buf) +{ + const struct counter_count_enum_ext *const e = priv; + size_t i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; i++) + len += sprintf(buf + len, "%s\n", e->items[i]); + + return len; +} +EXPORT_SYMBOL_GPL(counter_count_enum_available_read); + +ssize_t counter_device_enum_read(struct counter_device *counter, void *priv, + char *buf) +{ + const struct counter_device_enum_ext *const e = priv; + int err; + size_t index; + + if (!e->get) + return -EINVAL; + + err = e->get(counter, &index); + if (err) + return err; + + if (index >= e->num_items) + return -EINVAL; + + return sprintf(buf, "%s\n", e->items[index]); +} +EXPORT_SYMBOL_GPL(counter_device_enum_read); + +ssize_t counter_device_enum_write(struct counter_device *counter, void *priv, + const char *buf, size_t len) +{ + const struct counter_device_enum_ext *const e = priv; + ssize_t index; + int err; + + if (!e->set) + return -EINVAL; + + index = __sysfs_match_string(e->items, e->num_items, buf); + if (index < 0) + return index; + + err = e->set(counter, index); + if (err) + return err; + + return len; +} +EXPORT_SYMBOL_GPL(counter_device_enum_write); + +ssize_t counter_device_enum_available_read(struct counter_device *counter, + void *priv, char *buf) +{ + const struct counter_device_enum_ext *const e = priv; + size_t i; + size_t len = 0; + + if (!e->num_items) + return 0; + + for (i = 0; i < e->num_items; i++) + len += sprintf(buf + len, "%s\n", e->items[i]); + + return len; +} +EXPORT_SYMBOL_GPL(counter_device_enum_available_read); + +struct counter_attr_parm { + struct counter_device_attr_group *group; + const char *prefix; + const char *name; + ssize_t (*show)(struct device *dev, struct device_attribute *attr, + char *buf); + ssize_t (*store)(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len); + void *component; +}; + +struct counter_device_attr { + struct device_attribute dev_attr; + struct list_head l; + void *component; +}; + +static int counter_attribute_create(const struct counter_attr_parm *const parm) +{ + struct counter_device_attr *counter_attr; + struct device_attribute *dev_attr; + int err; + struct list_head *const attr_list = &parm->group->attr_list; + + /* Allocate a Counter device attribute */ + counter_attr = kzalloc(sizeof(*counter_attr), GFP_KERNEL); + if (!counter_attr) + return -ENOMEM; + dev_attr = &counter_attr->dev_attr; + + sysfs_attr_init(&dev_attr->attr); + + /* Configure device attribute */ + dev_attr->attr.name = kasprintf(GFP_KERNEL, "%s%s", parm->prefix, + parm->name); + if (!dev_attr->attr.name) { + err = -ENOMEM; + goto err_free_counter_attr; + } + if (parm->show) { + dev_attr->attr.mode |= 0444; + dev_attr->show = parm->show; + } + if (parm->store) { + dev_attr->attr.mode |= 0200; + dev_attr->store = parm->store; + } + + /* Store associated Counter component with attribute */ + counter_attr->component = parm->component; + + /* Keep track of the attribute for later cleanup */ + list_add(&counter_attr->l, attr_list); + parm->group->num_attr++; + + return 0; + +err_free_counter_attr: + kfree(counter_attr); + return err; +} + +#define to_counter_attr(_dev_attr) \ + container_of(_dev_attr, struct counter_device_attr, dev_attr) + +struct counter_signal_unit { + struct counter_signal *signal; +}; + +static const char *const counter_signal_value_str[] = { + [COUNTER_SIGNAL_LOW] = "low", + [COUNTER_SIGNAL_HIGH] = "high" +}; + +static ssize_t counter_signal_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_signal_unit *const component = devattr->component; + struct counter_signal *const signal = component->signal; + int err; + enum counter_signal_value val; + + err = counter->ops->signal_read(counter, signal, &val); + if (err) + return err; + + return sprintf(buf, "%s\n", counter_signal_value_str[val]); +} + +struct counter_name_unit { + const char *name; +}; + +static ssize_t counter_device_attr_name_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct counter_name_unit *const comp = to_counter_attr(attr)->component; + + return sprintf(buf, "%s\n", comp->name); +} + +static int counter_name_attribute_create( + struct counter_device_attr_group *const group, + const char *const name) +{ + struct counter_name_unit *name_comp; + struct counter_attr_parm parm; + int err; + + /* Skip if no name */ + if (!name) + return 0; + + /* Allocate name attribute component */ + name_comp = kmalloc(sizeof(*name_comp), GFP_KERNEL); + if (!name_comp) + return -ENOMEM; + name_comp->name = name; + + /* Allocate Signal name attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = "name"; + parm.show = counter_device_attr_name_show; + parm.store = NULL; + parm.component = name_comp; + err = counter_attribute_create(&parm); + if (err) + goto err_free_name_comp; + + return 0; + +err_free_name_comp: + kfree(name_comp); + return err; +} + +struct counter_signal_ext_unit { + struct counter_signal *signal; + const struct counter_signal_ext *ext; +}; + +static ssize_t counter_signal_ext_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_signal_ext_unit *const comp = devattr->component; + const struct counter_signal_ext *const ext = comp->ext; + + return ext->read(dev_get_drvdata(dev), comp->signal, ext->priv, buf); +} + +static ssize_t counter_signal_ext_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_signal_ext_unit *const comp = devattr->component; + const struct counter_signal_ext *const ext = comp->ext; + + return ext->write(dev_get_drvdata(dev), comp->signal, ext->priv, buf, + len); +} + +static void counter_device_attr_list_free(struct list_head *attr_list) +{ + struct counter_device_attr *p, *n; + + list_for_each_entry_safe(p, n, attr_list, l) { + /* free attribute name and associated component memory */ + kfree(p->dev_attr.attr.name); + kfree(p->component); + list_del(&p->l); + kfree(p); + } +} + +static int counter_signal_ext_register( + struct counter_device_attr_group *const group, + struct counter_signal *const signal) +{ + const size_t num_ext = signal->num_ext; + size_t i; + const struct counter_signal_ext *ext; + struct counter_signal_ext_unit *signal_ext_comp; + struct counter_attr_parm parm; + int err; + + /* Create an attribute for each extension */ + for (i = 0 ; i < num_ext; i++) { + ext = signal->ext + i; + + /* Allocate signal_ext attribute component */ + signal_ext_comp = kmalloc(sizeof(*signal_ext_comp), GFP_KERNEL); + if (!signal_ext_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + signal_ext_comp->signal = signal; + signal_ext_comp->ext = ext; + + /* Allocate a Counter device attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = ext->name; + parm.show = (ext->read) ? counter_signal_ext_show : NULL; + parm.store = (ext->write) ? counter_signal_ext_store : NULL; + parm.component = signal_ext_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(signal_ext_comp); + goto err_free_attr_list; + } + } + + return 0; + +err_free_attr_list: + counter_device_attr_list_free(&group->attr_list); + return err; +} + +static int counter_signal_attributes_create( + struct counter_device_attr_group *const group, + const struct counter_device *const counter, + struct counter_signal *const signal) +{ + struct counter_signal_unit *signal_comp; + struct counter_attr_parm parm; + int err; + + /* Allocate Signal attribute component */ + signal_comp = kmalloc(sizeof(*signal_comp), GFP_KERNEL); + if (!signal_comp) + return -ENOMEM; + signal_comp->signal = signal; + + /* Create main Signal attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = "signal"; + parm.show = (counter->ops->signal_read) ? counter_signal_show : NULL; + parm.store = NULL; + parm.component = signal_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(signal_comp); + return err; + } + + /* Create Signal name attribute */ + err = counter_name_attribute_create(group, signal->name); + if (err) + goto err_free_attr_list; + + /* Register Signal extension attributes */ + err = counter_signal_ext_register(group, signal); + if (err) + goto err_free_attr_list; + + return 0; + +err_free_attr_list: + counter_device_attr_list_free(&group->attr_list); + return err; +} + +static int counter_signals_register( + struct counter_device_attr_group *const groups_list, + const struct counter_device *const counter) +{ + const size_t num_signals = counter->num_signals; + size_t i; + struct counter_signal *signal; + const char *name; + int err; + + /* Register each Signal */ + for (i = 0; i < num_signals; i++) { + signal = counter->signals + i; + + /* Generate Signal attribute directory name */ + name = kasprintf(GFP_KERNEL, "signal%d", signal->id); + if (!name) { + err = -ENOMEM; + goto err_free_attr_groups; + } + groups_list[i].attr_group.name = name; + + /* Create all attributes associated with Signal */ + err = counter_signal_attributes_create(groups_list + i, counter, + signal); + if (err) + goto err_free_attr_groups; + } + + return 0; + +err_free_attr_groups: + do { + kfree(groups_list[i].attr_group.name); + counter_device_attr_list_free(&groups_list[i].attr_list); + } while (i--); + return err; +} + +static const char *const counter_synapse_action_str[] = { + [COUNTER_SYNAPSE_ACTION_NONE] = "none", + [COUNTER_SYNAPSE_ACTION_RISING_EDGE] = "rising edge", + [COUNTER_SYNAPSE_ACTION_FALLING_EDGE] = "falling edge", + [COUNTER_SYNAPSE_ACTION_BOTH_EDGES] = "both edges" +}; + +struct counter_action_unit { + struct counter_synapse *synapse; + struct counter_count *count; +}; + +static ssize_t counter_action_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_action_unit *const component = devattr->component; + struct counter_count *const count = component->count; + struct counter_synapse *const synapse = component->synapse; + size_t action_index; + enum counter_synapse_action action; + + err = counter->ops->action_get(counter, count, synapse, &action_index); + if (err) + return err; + + synapse->action = action_index; + + action = synapse->actions_list[action_index]; + return sprintf(buf, "%s\n", counter_synapse_action_str[action]); +} + +static ssize_t counter_action_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_action_unit *const component = devattr->component; + struct counter_synapse *const synapse = component->synapse; + size_t action_index; + const size_t num_actions = synapse->num_actions; + enum counter_synapse_action action; + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + struct counter_count *const count = component->count; + + /* Find requested action mode */ + for (action_index = 0; action_index < num_actions; action_index++) { + action = synapse->actions_list[action_index]; + if (sysfs_streq(buf, counter_synapse_action_str[action])) + break; + } + /* If requested action mode not found */ + if (action_index >= num_actions) + return -EINVAL; + + err = counter->ops->action_set(counter, count, synapse, action_index); + if (err) + return err; + + synapse->action = action_index; + + return len; +} + +struct counter_action_avail_unit { + const enum counter_synapse_action *actions_list; + size_t num_actions; +}; + +static ssize_t counter_synapse_action_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_action_avail_unit *const component = devattr->component; + size_t i; + enum counter_synapse_action action; + ssize_t len = 0; + + for (i = 0; i < component->num_actions; i++) { + action = component->actions_list[i]; + len += sprintf(buf + len, "%s\n", + counter_synapse_action_str[action]); + } + + return len; +} + +static int counter_synapses_register( + struct counter_device_attr_group *const group, + const struct counter_device *const counter, + struct counter_count *const count, const char *const count_attr_name) +{ + size_t i; + struct counter_synapse *synapse; + const char *prefix; + struct counter_action_unit *action_comp; + struct counter_attr_parm parm; + int err; + struct counter_action_avail_unit *avail_comp; + + /* Register each Synapse */ + for (i = 0; i < count->num_synapses; i++) { + synapse = count->synapses + i; + + /* Generate attribute prefix */ + prefix = kasprintf(GFP_KERNEL, "signal%d_", + synapse->signal->id); + if (!prefix) { + err = -ENOMEM; + goto err_free_attr_list; + } + + /* Allocate action attribute component */ + action_comp = kmalloc(sizeof(*action_comp), GFP_KERNEL); + if (!action_comp) { + err = -ENOMEM; + goto err_free_prefix; + } + action_comp->synapse = synapse; + action_comp->count = count; + + /* Create action attribute */ + parm.group = group; + parm.prefix = prefix; + parm.name = "action"; + parm.show = (counter->ops->action_get) ? counter_action_show : NULL; + parm.store = (counter->ops->action_set) ? counter_action_store : NULL; + parm.component = action_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(action_comp); + goto err_free_prefix; + } + + /* Allocate action available attribute component */ + avail_comp = kmalloc(sizeof(*avail_comp), GFP_KERNEL); + if (!avail_comp) { + err = -ENOMEM; + goto err_free_prefix; + } + avail_comp->actions_list = synapse->actions_list; + avail_comp->num_actions = synapse->num_actions; + + /* Create action_available attribute */ + parm.group = group; + parm.prefix = prefix; + parm.name = "action_available"; + parm.show = counter_synapse_action_available_show; + parm.store = NULL; + parm.component = avail_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(avail_comp); + goto err_free_prefix; + } + + kfree(prefix); + } + + return 0; + +err_free_prefix: + kfree(prefix); +err_free_attr_list: + counter_device_attr_list_free(&group->attr_list); + return err; +} + +struct counter_count_unit { + struct counter_count *count; +}; + +static ssize_t counter_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_count_unit *const component = devattr->component; + struct counter_count *const count = component->count; + int err; + unsigned long val; + + err = counter->ops->count_read(counter, count, &val); + if (err) + return err; + + return sprintf(buf, "%lu\n", val); +} + +static ssize_t counter_count_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_count_unit *const component = devattr->component; + struct counter_count *const count = component->count; + int err; + unsigned long val; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + err = counter->ops->count_write(counter, count, val); + if (err) + return err; + + return len; +} + +static const char *const counter_count_function_str[] = { + [COUNTER_COUNT_FUNCTION_INCREASE] = "increase", + [COUNTER_COUNT_FUNCTION_DECREASE] = "decrease", + [COUNTER_COUNT_FUNCTION_PULSE_DIRECTION] = "pulse-direction", + [COUNTER_COUNT_FUNCTION_QUADRATURE_X1_A] = "quadrature x1 a", + [COUNTER_COUNT_FUNCTION_QUADRATURE_X1_B] = "quadrature x1 b", + [COUNTER_COUNT_FUNCTION_QUADRATURE_X2_A] = "quadrature x2 a", + [COUNTER_COUNT_FUNCTION_QUADRATURE_X2_B] = "quadrature x2 b", + [COUNTER_COUNT_FUNCTION_QUADRATURE_X4] = "quadrature x4" +}; + +static ssize_t counter_function_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_count_unit *const component = devattr->component; + struct counter_count *const count = component->count; + size_t func_index; + enum counter_count_function function; + + err = counter->ops->function_get(counter, count, &func_index); + if (err) + return err; + + count->function = func_index; + + function = count->functions_list[func_index]; + return sprintf(buf, "%s\n", counter_count_function_str[function]); +} + +static ssize_t counter_function_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_count_unit *const component = devattr->component; + struct counter_count *const count = component->count; + const size_t num_functions = count->num_functions; + size_t func_index; + enum counter_count_function function; + int err; + struct counter_device *const counter = dev_get_drvdata(dev); + + /* Find requested Count function mode */ + for (func_index = 0; func_index < num_functions; func_index++) { + function = count->functions_list[func_index]; + if (sysfs_streq(buf, counter_count_function_str[function])) + break; + } + /* Return error if requested Count function mode not found */ + if (func_index >= num_functions) + return -EINVAL; + + err = counter->ops->function_set(counter, count, func_index); + if (err) + return err; + + count->function = func_index; + + return len; +} + +struct counter_count_ext_unit { + struct counter_count *count; + const struct counter_count_ext *ext; +}; + +static ssize_t counter_count_ext_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_count_ext_unit *const comp = devattr->component; + const struct counter_count_ext *const ext = comp->ext; + + return ext->read(dev_get_drvdata(dev), comp->count, ext->priv, buf); +} + +static ssize_t counter_count_ext_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_count_ext_unit *const comp = devattr->component; + const struct counter_count_ext *const ext = comp->ext; + + return ext->write(dev_get_drvdata(dev), comp->count, ext->priv, buf, + len); +} + +static int counter_count_ext_register( + struct counter_device_attr_group *const group, + struct counter_count *const count) +{ + size_t i; + const struct counter_count_ext *ext; + struct counter_count_ext_unit *count_ext_comp; + struct counter_attr_parm parm; + int err; + + /* Create an attribute for each extension */ + for (i = 0 ; i < count->num_ext; i++) { + ext = count->ext + i; + + /* Allocate count_ext attribute component */ + count_ext_comp = kmalloc(sizeof(*count_ext_comp), GFP_KERNEL); + if (!count_ext_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + count_ext_comp->count = count; + count_ext_comp->ext = ext; + + /* Allocate count_ext attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = ext->name; + parm.show = (ext->read) ? counter_count_ext_show : NULL; + parm.store = (ext->write) ? counter_count_ext_store : NULL; + parm.component = count_ext_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(count_ext_comp); + goto err_free_attr_list; + } + } + + return 0; + +err_free_attr_list: + counter_device_attr_list_free(&group->attr_list); + return err; +} + +struct counter_func_avail_unit { + const enum counter_count_function *functions_list; + size_t num_functions; +}; + +static ssize_t counter_count_function_available_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_func_avail_unit *const component = devattr->component; + const enum counter_count_function *const func_list = component->functions_list; + const size_t num_functions = component->num_functions; + size_t i; + enum counter_count_function function; + ssize_t len = 0; + + for (i = 0; i < num_functions; i++) { + function = func_list[i]; + len += sprintf(buf + len, "%s\n", + counter_count_function_str[function]); + } + + return len; +} + +static int counter_count_attributes_create( + struct counter_device_attr_group *const group, + const struct counter_device *const counter, + struct counter_count *const count) +{ + struct counter_count_unit *count_comp; + struct counter_attr_parm parm; + int err; + struct counter_count_unit *func_comp; + struct counter_func_avail_unit *avail_comp; + + /* Allocate count attribute component */ + count_comp = kmalloc(sizeof(*count_comp), GFP_KERNEL); + if (!count_comp) + return -ENOMEM; + count_comp->count = count; + + /* Create main Count attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = "count"; + parm.show = (counter->ops->count_read) ? counter_count_show : NULL; + parm.store = (counter->ops->count_write) ? counter_count_store : NULL; + parm.component = count_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(count_comp); + return err; + } + + /* Allocate function attribute component */ + func_comp = kmalloc(sizeof(*func_comp), GFP_KERNEL); + if (!func_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + func_comp->count = count; + + /* Create Count function attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = "function"; + parm.show = (counter->ops->function_get) ? counter_function_show : NULL; + parm.store = (counter->ops->function_set) ? counter_function_store : NULL; + parm.component = func_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(func_comp); + goto err_free_attr_list; + } + + /* Allocate function available attribute component */ + avail_comp = kmalloc(sizeof(*avail_comp), GFP_KERNEL); + if (!avail_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + avail_comp->functions_list = count->functions_list; + avail_comp->num_functions = count->num_functions; + + /* Create Count function_available attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = "function_available"; + parm.show = counter_count_function_available_show; + parm.store = NULL; + parm.component = avail_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(avail_comp); + goto err_free_attr_list; + } + + /* Create Count name attribute */ + err = counter_name_attribute_create(group, count->name); + if (err) + goto err_free_attr_list; + + /* Register Count extension attributes */ + err = counter_count_ext_register(group, count); + if (err) + goto err_free_attr_list; + + return 0; + +err_free_attr_list: + counter_device_attr_list_free(&group->attr_list); + return err; +} + +static int counter_counts_register( + struct counter_device_attr_group *const groups_list, + const struct counter_device *const counter) +{ + size_t i; + struct counter_count *count; + const char *name; + int err; + + /* Register each Count */ + for (i = 0; i < counter->num_counts; i++) { + count = counter->counts + i; + + /* Generate Count attribute directory name */ + name = kasprintf(GFP_KERNEL, "count%d", count->id); + if (!name) { + err = -ENOMEM; + goto err_free_attr_groups; + } + groups_list[i].attr_group.name = name; + + /* Register the Synapses associated with each Count */ + err = counter_synapses_register(groups_list + i, counter, count, + name); + if (err) + goto err_free_attr_groups; + + /* Create all attributes associated with Count */ + err = counter_count_attributes_create(groups_list + i, counter, + count); + if (err) + goto err_free_attr_groups; + } + + return 0; + +err_free_attr_groups: + do { + kfree(groups_list[i].attr_group.name); + counter_device_attr_list_free(&groups_list[i].attr_list); + } while (i--); + return err; +} + +struct counter_size_unit { + size_t size; +}; + +static ssize_t counter_device_attr_size_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + const struct counter_size_unit *const comp = to_counter_attr(attr)->component; + + return sprintf(buf, "%zu\n", comp->size); +} + +static int counter_size_attribute_create( + struct counter_device_attr_group *const group, + const size_t size, const char *const name) +{ + struct counter_size_unit *size_comp; + struct counter_attr_parm parm; + int err; + + /* Allocate size attribute component */ + size_comp = kmalloc(sizeof(*size_comp), GFP_KERNEL); + if (!size_comp) + return -ENOMEM; + size_comp->size = size; + + parm.group = group; + parm.prefix = ""; + parm.name = name; + parm.show = counter_device_attr_size_show; + parm.store = NULL; + parm.component = size_comp; + err = counter_attribute_create(&parm); + if (err) + goto err_free_size_comp; + + return 0; + +err_free_size_comp: + kfree(size_comp); + return err; +} + +struct counter_ext_unit { + const struct counter_device_ext *ext; +}; + +static ssize_t counter_device_ext_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_ext_unit *const component = devattr->component; + const struct counter_device_ext *const ext = component->ext; + + return ext->read(dev_get_drvdata(dev), ext->priv, buf); +} + +static ssize_t counter_device_ext_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + const struct counter_device_attr *const devattr = to_counter_attr(attr); + const struct counter_ext_unit *const component = devattr->component; + const struct counter_device_ext *const ext = component->ext; + + return ext->write(dev_get_drvdata(dev), ext->priv, buf, len); +} + +static int counter_device_ext_register( + struct counter_device_attr_group *const group, + struct counter_device *const counter) +{ + size_t i; + struct counter_ext_unit *ext_comp; + struct counter_attr_parm parm; + int err; + + /* Create an attribute for each extension */ + for (i = 0 ; i < counter->num_ext; i++) { + /* Allocate extension attribute component */ + ext_comp = kmalloc(sizeof(*ext_comp), GFP_KERNEL); + if (!ext_comp) { + err = -ENOMEM; + goto err_free_attr_list; + } + + ext_comp->ext = counter->ext + i; + + /* Allocate extension attribute */ + parm.group = group; + parm.prefix = ""; + parm.name = counter->ext[i].name; + parm.show = (counter->ext[i].read) ? counter_device_ext_show : NULL; + parm.store = (counter->ext[i].write) ? counter_device_ext_store : NULL; + parm.component = ext_comp; + err = counter_attribute_create(&parm); + if (err) { + kfree(ext_comp); + goto err_free_attr_list; + } + } + + return 0; + +err_free_attr_list: + counter_device_attr_list_free(&group->attr_list); + return err; +} + +static int counter_global_attr_register( + struct counter_device_attr_group *const group, + struct counter_device *const counter) +{ + int err; + + /* Create name attribute */ + err = counter_name_attribute_create(group, counter->name); + if (err) + return err; + + /* Create num_counts attribute */ + err = counter_size_attribute_create(group, counter->num_counts, + "num_counts"); + if (err) + goto err_free_attr_list; + + /* Create num_signals attribute */ + err = counter_size_attribute_create(group, counter->num_signals, + "num_signals"); + if (err) + goto err_free_attr_list; + + /* Register Counter device extension attributes */ + err = counter_device_ext_register(group, counter); + if (err) + goto err_free_attr_list; + + return 0; + +err_free_attr_list: + counter_device_attr_list_free(&group->attr_list); + return err; +} + +static void counter_device_groups_list_free( + struct counter_device_attr_group *const groups_list, + const size_t num_groups) +{ + struct counter_device_attr_group *group; + size_t i; + + /* loop through all attribute groups (signals, counts, global, etc.) */ + for (i = 0; i < num_groups; i++) { + group = groups_list + i; + + /* free all attribute group and associated attributes memory */ + kfree(group->attr_group.name); + kfree(group->attr_group.attrs); + counter_device_attr_list_free(&group->attr_list); + } + + kfree(groups_list); +} + +static int counter_device_groups_list_prepare( + struct counter_device *const counter) +{ + const size_t total_num_groups = + counter->num_signals + counter->num_counts + 1; + struct counter_device_attr_group *groups_list; + size_t i; + int err; + size_t num_groups = 0; + + /* Allocate space for attribute groups (signals, counts, and ext) */ + groups_list = kcalloc(total_num_groups, sizeof(*groups_list), + GFP_KERNEL); + if (!groups_list) + return -ENOMEM; + + /* Initialize attribute lists */ + for (i = 0; i < total_num_groups; i++) + INIT_LIST_HEAD(&groups_list[i].attr_list); + + /* Register Signals */ + err = counter_signals_register(groups_list, counter); + if (err) + goto err_free_groups_list; + num_groups += counter->num_signals; + + /* Register Counts and respective Synapses */ + err = counter_counts_register(groups_list + num_groups, counter); + if (err) + goto err_free_groups_list; + num_groups += counter->num_counts; + + /* Register Counter global attributes */ + err = counter_global_attr_register(groups_list + num_groups, counter); + if (err) + goto err_free_groups_list; + num_groups++; + + /* Store groups_list in device_state */ + counter->device_state->groups_list = groups_list; + counter->device_state->num_groups = num_groups; + + return 0; + +err_free_groups_list: + counter_device_groups_list_free(groups_list, num_groups); + return err; +} + +static int counter_device_groups_prepare( + struct counter_device_state *const device_state) +{ + size_t i, j; + struct counter_device_attr_group *group; + int err; + struct counter_device_attr *p; + + /* Allocate attribute groups for association with device */ + device_state->groups = kcalloc(device_state->num_groups + 1, + sizeof(*device_state->groups), + GFP_KERNEL); + if (!device_state->groups) + return -ENOMEM; + + /* Prepare each group of attributes for association */ + for (i = 0; i < device_state->num_groups; i++) { + group = device_state->groups_list + i; + + /* Allocate space for attribute pointers in attribute group */ + group->attr_group.attrs = kcalloc(group->num_attr + 1, + sizeof(*group->attr_group.attrs), GFP_KERNEL); + if (!group->attr_group.attrs) { + err = -ENOMEM; + goto err_free_groups; + } + + /* Add attribute pointers to attribute group */ + j = 0; + list_for_each_entry(p, &group->attr_list, l) + group->attr_group.attrs[j++] = &p->dev_attr.attr; + + /* Group attributes in attribute group */ + device_state->groups[i] = &group->attr_group; + } + /* Associate attributes with device */ + device_state->dev.groups = device_state->groups; + + return 0; + +err_free_groups: + do { + group = device_state->groups_list + i; + kfree(group->attr_group.attrs); + group->attr_group.attrs = NULL; + } while (i--); + kfree(device_state->groups); + return err; +} + +/* Provides a unique ID for each counter device */ +static DEFINE_IDA(counter_ida); + +static void counter_device_release(struct device *dev) +{ + struct counter_device *const counter = dev_get_drvdata(dev); + struct counter_device_state *const device_state = counter->device_state; + + kfree(device_state->groups); + counter_device_groups_list_free(device_state->groups_list, + device_state->num_groups); + ida_simple_remove(&counter_ida, device_state->id); + kfree(device_state); +} + +static struct device_type counter_device_type = { + .name = "counter_device", + .release = counter_device_release +}; + +static struct bus_type counter_bus_type = { + .name = "counter" +}; + +/** + * counter_register - register Counter to the system + * @counter: pointer to Counter to register + * + * This function registers a Counter to the system. A sysfs "counter" directory + * will be created and populated with sysfs attributes correlating with the + * Counter Signals, Synapses, and Counts respectively. + */ +int counter_register(struct counter_device *const counter) +{ + struct counter_device_state *device_state; + int err; + + /* Allocate internal state container for Counter device */ + device_state = kzalloc(sizeof(*device_state), GFP_KERNEL); + if (!device_state) + return -ENOMEM; + counter->device_state = device_state; + + /* Acquire unique ID */ + device_state->id = ida_simple_get(&counter_ida, 0, 0, GFP_KERNEL); + if (device_state->id < 0) { + err = device_state->id; + goto err_free_device_state; + } + + /* Configure device structure for Counter */ + device_state->dev.type = &counter_device_type; + device_state->dev.bus = &counter_bus_type; + if (counter->parent) { + device_state->dev.parent = counter->parent; + device_state->dev.of_node = counter->parent->of_node; + } + dev_set_name(&device_state->dev, "counter%d", device_state->id); + device_initialize(&device_state->dev); + dev_set_drvdata(&device_state->dev, counter); + + /* Prepare device attributes */ + err = counter_device_groups_list_prepare(counter); + if (err) + goto err_free_id; + + /* Organize device attributes to groups and match to device */ + err = counter_device_groups_prepare(device_state); + if (err) + goto err_free_groups_list; + + /* Add device to system */ + err = device_add(&device_state->dev); + if (err) + goto err_free_groups; + + return 0; + +err_free_groups: + kfree(device_state->groups); +err_free_groups_list: + counter_device_groups_list_free(device_state->groups_list, + device_state->num_groups); +err_free_id: + ida_simple_remove(&counter_ida, device_state->id); +err_free_device_state: + kfree(device_state); + return err; +} +EXPORT_SYMBOL_GPL(counter_register); + +/** + * counter_unregister - unregister Counter from the system + * @counter: pointer to Counter to unregister + * + * The Counter is unregistered from the system; all allocated memory is freed. + */ +void counter_unregister(struct counter_device *const counter) +{ + if (counter) + device_del(&counter->device_state->dev); +} +EXPORT_SYMBOL_GPL(counter_unregister); + +static void devm_counter_unreg(struct device *dev, void *res) +{ + counter_unregister(*(struct counter_device **)res); +} + +/** + * devm_counter_register - Resource-managed counter_register + * @dev: device to allocate counter_device for + * @counter: pointer to Counter to register + * + * Managed counter_register. The Counter registered with this function is + * automatically unregistered on driver detach. This function calls + * counter_register internally. Refer to that function for more information. + * + * If an Counter registered with this function needs to be unregistered + * separately, devm_counter_unregister must be used. + * + * RETURNS: + * 0 on success, negative error number on failure. + */ +int devm_counter_register(struct device *dev, + struct counter_device *const counter) +{ + struct counter_device **ptr; + int ret; + + ptr = devres_alloc(devm_counter_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = counter_register(counter); + if (!ret) { + *ptr = counter; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_counter_register); + +static int devm_counter_match(struct device *dev, void *res, void *data) +{ + struct counter_device **r = res; + + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + + return *r == data; +} + +/** + * devm_counter_unregister - Resource-managed counter_unregister + * @dev: device this counter_device belongs to + * @counter: pointer to Counter associated with the device + * + * Unregister Counter registered with devm_counter_register. + */ +void devm_counter_unregister(struct device *dev, + struct counter_device *const counter) +{ + int rc; + + rc = devres_release(dev, devm_counter_unreg, devm_counter_match, + counter); + WARN_ON(rc); +} +EXPORT_SYMBOL_GPL(devm_counter_unregister); + +static int __init counter_init(void) +{ + return bus_register(&counter_bus_type); +} + +static void __exit counter_exit(void) +{ + bus_unregister(&counter_bus_type); +} + +subsys_initcall(counter_init); +module_exit(counter_exit); + +MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); +MODULE_DESCRIPTION("Generic Counter interface"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/counter/ftm-quaddec.c b/drivers/counter/ftm-quaddec.c new file mode 100644 index 000000000..c2b3fdfd8 --- /dev/null +++ b/drivers/counter/ftm-quaddec.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Flex Timer Module Quadrature decoder + * + * This module implements a driver for decoding the FTM quadrature + * of ex. a LS1021A + */ + +#include <linux/fsl/ftm.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/counter.h> +#include <linux/bitfield.h> + +#define FTM_FIELD_UPDATE(ftm, offset, mask, val) \ + ({ \ + uint32_t flags; \ + ftm_read(ftm, offset, &flags); \ + flags &= ~mask; \ + flags |= FIELD_PREP(mask, val); \ + ftm_write(ftm, offset, flags); \ + }) + +struct ftm_quaddec { + struct counter_device counter; + struct platform_device *pdev; + void __iomem *ftm_base; + bool big_endian; + struct mutex ftm_quaddec_mutex; +}; + +static void ftm_read(struct ftm_quaddec *ftm, uint32_t offset, uint32_t *data) +{ + if (ftm->big_endian) + *data = ioread32be(ftm->ftm_base + offset); + else + *data = ioread32(ftm->ftm_base + offset); +} + +static void ftm_write(struct ftm_quaddec *ftm, uint32_t offset, uint32_t data) +{ + if (ftm->big_endian) + iowrite32be(data, ftm->ftm_base + offset); + else + iowrite32(data, ftm->ftm_base + offset); +} + +/* Hold mutex before modifying write protection state */ +static void ftm_clear_write_protection(struct ftm_quaddec *ftm) +{ + uint32_t flag; + + /* First see if it is enabled */ + ftm_read(ftm, FTM_FMS, &flag); + + if (flag & FTM_FMS_WPEN) + FTM_FIELD_UPDATE(ftm, FTM_MODE, FTM_MODE_WPDIS, 1); +} + +static void ftm_set_write_protection(struct ftm_quaddec *ftm) +{ + FTM_FIELD_UPDATE(ftm, FTM_FMS, FTM_FMS_WPEN, 1); +} + +static void ftm_reset_counter(struct ftm_quaddec *ftm) +{ + /* Reset hardware counter to CNTIN */ + ftm_write(ftm, FTM_CNT, 0x0); +} + +static void ftm_quaddec_init(struct ftm_quaddec *ftm) +{ + ftm_clear_write_protection(ftm); + + /* + * Do not write in the region from the CNTIN register through the + * PWMLOAD register when FTMEN = 0. + * Also reset other fields to zero + */ + ftm_write(ftm, FTM_MODE, FTM_MODE_FTMEN); + ftm_write(ftm, FTM_CNTIN, 0x0000); + ftm_write(ftm, FTM_MOD, 0xffff); + ftm_write(ftm, FTM_CNT, 0x0); + /* Set prescaler, reset other fields to zero */ + ftm_write(ftm, FTM_SC, FTM_SC_PS_1); + + /* Select quad mode, reset other fields to zero */ + ftm_write(ftm, FTM_QDCTRL, FTM_QDCTRL_QUADEN); + + /* Unused features and reset to default section */ + ftm_write(ftm, FTM_POL, 0x0); + ftm_write(ftm, FTM_FLTCTRL, 0x0); + ftm_write(ftm, FTM_SYNCONF, 0x0); + ftm_write(ftm, FTM_SYNC, 0xffff); + + /* Lock the FTM */ + ftm_set_write_protection(ftm); +} + +static void ftm_quaddec_disable(void *ftm) +{ + struct ftm_quaddec *ftm_qua = ftm; + + ftm_clear_write_protection(ftm_qua); + ftm_write(ftm_qua, FTM_MODE, 0); + ftm_write(ftm_qua, FTM_QDCTRL, 0); + /* + * This is enough to disable the counter. No clock has been + * selected by writing to FTM_SC in init() + */ + ftm_set_write_protection(ftm_qua); +} + +static int ftm_quaddec_get_prescaler(struct counter_device *counter, + struct counter_count *count, + size_t *cnt_mode) +{ + struct ftm_quaddec *ftm = counter->priv; + uint32_t scflags; + + ftm_read(ftm, FTM_SC, &scflags); + + *cnt_mode = FIELD_GET(FTM_SC_PS_MASK, scflags); + + return 0; +} + +static int ftm_quaddec_set_prescaler(struct counter_device *counter, + struct counter_count *count, + size_t cnt_mode) +{ + struct ftm_quaddec *ftm = counter->priv; + + mutex_lock(&ftm->ftm_quaddec_mutex); + + ftm_clear_write_protection(ftm); + FTM_FIELD_UPDATE(ftm, FTM_SC, FTM_SC_PS_MASK, cnt_mode); + ftm_set_write_protection(ftm); + + /* Also resets the counter as it is undefined anyway now */ + ftm_reset_counter(ftm); + + mutex_unlock(&ftm->ftm_quaddec_mutex); + return 0; +} + +static const char * const ftm_quaddec_prescaler[] = { + "1", "2", "4", "8", "16", "32", "64", "128" +}; + +static struct counter_count_enum_ext ftm_quaddec_prescaler_enum = { + .items = ftm_quaddec_prescaler, + .num_items = ARRAY_SIZE(ftm_quaddec_prescaler), + .get = ftm_quaddec_get_prescaler, + .set = ftm_quaddec_set_prescaler +}; + +enum ftm_quaddec_synapse_action { + FTM_QUADDEC_SYNAPSE_ACTION_BOTH_EDGES, +}; + +static enum counter_synapse_action ftm_quaddec_synapse_actions[] = { + [FTM_QUADDEC_SYNAPSE_ACTION_BOTH_EDGES] = + COUNTER_SYNAPSE_ACTION_BOTH_EDGES +}; + +enum ftm_quaddec_count_function { + FTM_QUADDEC_COUNT_ENCODER_MODE_1, +}; + +static const enum counter_count_function ftm_quaddec_count_functions[] = { + [FTM_QUADDEC_COUNT_ENCODER_MODE_1] = + COUNTER_COUNT_FUNCTION_QUADRATURE_X4 +}; + +static int ftm_quaddec_count_read(struct counter_device *counter, + struct counter_count *count, + unsigned long *val) +{ + struct ftm_quaddec *const ftm = counter->priv; + uint32_t cntval; + + ftm_read(ftm, FTM_CNT, &cntval); + + *val = cntval; + + return 0; +} + +static int ftm_quaddec_count_write(struct counter_device *counter, + struct counter_count *count, + const unsigned long val) +{ + struct ftm_quaddec *const ftm = counter->priv; + + if (val != 0) { + dev_warn(&ftm->pdev->dev, "Can only accept '0' as new counter value\n"); + return -EINVAL; + } + + ftm_reset_counter(ftm); + + return 0; +} + +static int ftm_quaddec_count_function_get(struct counter_device *counter, + struct counter_count *count, + size_t *function) +{ + *function = FTM_QUADDEC_COUNT_ENCODER_MODE_1; + + return 0; +} + +static int ftm_quaddec_action_get(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t *action) +{ + *action = FTM_QUADDEC_SYNAPSE_ACTION_BOTH_EDGES; + + return 0; +} + +static const struct counter_ops ftm_quaddec_cnt_ops = { + .count_read = ftm_quaddec_count_read, + .count_write = ftm_quaddec_count_write, + .function_get = ftm_quaddec_count_function_get, + .action_get = ftm_quaddec_action_get, +}; + +static struct counter_signal ftm_quaddec_signals[] = { + { + .id = 0, + .name = "Channel 1 Phase A" + }, + { + .id = 1, + .name = "Channel 1 Phase B" + } +}; + +static struct counter_synapse ftm_quaddec_count_synapses[] = { + { + .actions_list = ftm_quaddec_synapse_actions, + .num_actions = ARRAY_SIZE(ftm_quaddec_synapse_actions), + .signal = &ftm_quaddec_signals[0] + }, + { + .actions_list = ftm_quaddec_synapse_actions, + .num_actions = ARRAY_SIZE(ftm_quaddec_synapse_actions), + .signal = &ftm_quaddec_signals[1] + } +}; + +static const struct counter_count_ext ftm_quaddec_count_ext[] = { + COUNTER_COUNT_ENUM("prescaler", &ftm_quaddec_prescaler_enum), + COUNTER_COUNT_ENUM_AVAILABLE("prescaler", &ftm_quaddec_prescaler_enum), +}; + +static struct counter_count ftm_quaddec_counts = { + .id = 0, + .name = "Channel 1 Count", + .functions_list = ftm_quaddec_count_functions, + .num_functions = ARRAY_SIZE(ftm_quaddec_count_functions), + .synapses = ftm_quaddec_count_synapses, + .num_synapses = ARRAY_SIZE(ftm_quaddec_count_synapses), + .ext = ftm_quaddec_count_ext, + .num_ext = ARRAY_SIZE(ftm_quaddec_count_ext) +}; + +static int ftm_quaddec_probe(struct platform_device *pdev) +{ + struct ftm_quaddec *ftm; + + struct device_node *node = pdev->dev.of_node; + struct resource *io; + int ret; + + ftm = devm_kzalloc(&pdev->dev, sizeof(*ftm), GFP_KERNEL); + if (!ftm) + return -ENOMEM; + + platform_set_drvdata(pdev, ftm); + + io = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!io) { + dev_err(&pdev->dev, "Failed to get memory region\n"); + return -ENODEV; + } + + ftm->pdev = pdev; + ftm->big_endian = of_property_read_bool(node, "big-endian"); + ftm->ftm_base = devm_ioremap(&pdev->dev, io->start, resource_size(io)); + + if (!ftm->ftm_base) { + dev_err(&pdev->dev, "Failed to map memory region\n"); + return -EINVAL; + } + ftm->counter.name = dev_name(&pdev->dev); + ftm->counter.parent = &pdev->dev; + ftm->counter.ops = &ftm_quaddec_cnt_ops; + ftm->counter.counts = &ftm_quaddec_counts; + ftm->counter.num_counts = 1; + ftm->counter.signals = ftm_quaddec_signals; + ftm->counter.num_signals = ARRAY_SIZE(ftm_quaddec_signals); + ftm->counter.priv = ftm; + + mutex_init(&ftm->ftm_quaddec_mutex); + + ftm_quaddec_init(ftm); + + ret = devm_add_action_or_reset(&pdev->dev, ftm_quaddec_disable, ftm); + if (ret) + return ret; + + ret = devm_counter_register(&pdev->dev, &ftm->counter); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id ftm_quaddec_match[] = { + { .compatible = "fsl,ftm-quaddec" }, + {}, +}; + +static struct platform_driver ftm_quaddec_driver = { + .driver = { + .name = "ftm-quaddec", + .of_match_table = ftm_quaddec_match, + }, + .probe = ftm_quaddec_probe, +}; + +module_platform_driver(ftm_quaddec_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kjeld Flarup <kfa@deif.com>"); +MODULE_AUTHOR("Patrick Havelange <patrick.havelange@essensium.com>"); diff --git a/drivers/counter/microchip-tcb-capture.c b/drivers/counter/microchip-tcb-capture.c new file mode 100644 index 000000000..6f17644b6 --- /dev/null +++ b/drivers/counter/microchip-tcb-capture.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * Copyright (C) 2020 Microchip + * + * Author: Kamel Bouhara <kamel.bouhara@bootlin.com> + */ +#include <linux/clk.h> +#include <linux/counter.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <soc/at91/atmel_tcb.h> + +#define ATMEL_TC_CMR_MASK (ATMEL_TC_LDRA_RISING | ATMEL_TC_LDRB_FALLING | \ + ATMEL_TC_ETRGEDG_RISING | ATMEL_TC_LDBDIS | \ + ATMEL_TC_LDBSTOP) + +#define ATMEL_TC_QDEN BIT(8) +#define ATMEL_TC_POSEN BIT(9) + +struct mchp_tc_data { + const struct atmel_tcb_config *tc_cfg; + struct counter_device counter; + struct regmap *regmap; + int qdec_mode; + int num_channels; + int channel[2]; +}; + +enum mchp_tc_count_function { + MCHP_TC_FUNCTION_INCREASE, + MCHP_TC_FUNCTION_QUADRATURE, +}; + +static enum counter_count_function mchp_tc_count_functions[] = { + [MCHP_TC_FUNCTION_INCREASE] = COUNTER_COUNT_FUNCTION_INCREASE, + [MCHP_TC_FUNCTION_QUADRATURE] = COUNTER_COUNT_FUNCTION_QUADRATURE_X4, +}; + +enum mchp_tc_synapse_action { + MCHP_TC_SYNAPSE_ACTION_NONE = 0, + MCHP_TC_SYNAPSE_ACTION_RISING_EDGE, + MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE, + MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE +}; + +static enum counter_synapse_action mchp_tc_synapse_actions[] = { + [MCHP_TC_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, + [MCHP_TC_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE, + [MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE] = COUNTER_SYNAPSE_ACTION_FALLING_EDGE, + [MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE] = COUNTER_SYNAPSE_ACTION_BOTH_EDGES, +}; + +static struct counter_signal mchp_tc_count_signals[] = { + { + .id = 0, + .name = "Channel A", + }, + { + .id = 1, + .name = "Channel B", + } +}; + +static struct counter_synapse mchp_tc_count_synapses[] = { + { + .actions_list = mchp_tc_synapse_actions, + .num_actions = ARRAY_SIZE(mchp_tc_synapse_actions), + .signal = &mchp_tc_count_signals[0] + }, + { + .actions_list = mchp_tc_synapse_actions, + .num_actions = ARRAY_SIZE(mchp_tc_synapse_actions), + .signal = &mchp_tc_count_signals[1] + } +}; + +static int mchp_tc_count_function_get(struct counter_device *counter, + struct counter_count *count, + size_t *function) +{ + struct mchp_tc_data *const priv = counter->priv; + + if (priv->qdec_mode) + *function = MCHP_TC_FUNCTION_QUADRATURE; + else + *function = MCHP_TC_FUNCTION_INCREASE; + + return 0; +} + +static int mchp_tc_count_function_set(struct counter_device *counter, + struct counter_count *count, + size_t function) +{ + struct mchp_tc_data *const priv = counter->priv; + u32 bmr, cmr; + + regmap_read(priv->regmap, ATMEL_TC_BMR, &bmr); + regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), &cmr); + + /* Set capture mode */ + cmr &= ~ATMEL_TC_WAVE; + + switch (function) { + case MCHP_TC_FUNCTION_INCREASE: + priv->qdec_mode = 0; + /* Set highest rate based on whether soc has gclk or not */ + bmr &= ~(ATMEL_TC_QDEN | ATMEL_TC_POSEN); + if (!priv->tc_cfg->has_gclk) + cmr |= ATMEL_TC_TIMER_CLOCK2; + else + cmr |= ATMEL_TC_TIMER_CLOCK1; + /* Setup the period capture mode */ + cmr |= ATMEL_TC_CMR_MASK; + cmr &= ~(ATMEL_TC_ABETRG | ATMEL_TC_XC0); + break; + case MCHP_TC_FUNCTION_QUADRATURE: + if (!priv->tc_cfg->has_qdec) + return -EINVAL; + /* In QDEC mode settings both channels 0 and 1 are required */ + if (priv->num_channels < 2 || priv->channel[0] != 0 || + priv->channel[1] != 1) { + pr_err("Invalid channels number or id for quadrature mode\n"); + return -EINVAL; + } + priv->qdec_mode = 1; + bmr |= ATMEL_TC_QDEN | ATMEL_TC_POSEN; + cmr |= ATMEL_TC_ETRGEDG_RISING | ATMEL_TC_ABETRG | ATMEL_TC_XC0; + break; + } + + regmap_write(priv->regmap, ATMEL_TC_BMR, bmr); + regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), cmr); + + /* Enable clock and trigger counter */ + regmap_write(priv->regmap, ATMEL_TC_REG(priv->channel[0], CCR), + ATMEL_TC_CLKEN | ATMEL_TC_SWTRG); + + if (priv->qdec_mode) { + regmap_write(priv->regmap, + ATMEL_TC_REG(priv->channel[1], CMR), cmr); + regmap_write(priv->regmap, + ATMEL_TC_REG(priv->channel[1], CCR), + ATMEL_TC_CLKEN | ATMEL_TC_SWTRG); + } + + return 0; +} + +static int mchp_tc_count_signal_read(struct counter_device *counter, + struct counter_signal *signal, + enum counter_signal_value *val) +{ + struct mchp_tc_data *const priv = counter->priv; + bool sigstatus; + u32 sr; + + regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], SR), &sr); + + if (signal->id == 1) + sigstatus = (sr & ATMEL_TC_MTIOB); + else + sigstatus = (sr & ATMEL_TC_MTIOA); + + *val = sigstatus ? COUNTER_SIGNAL_HIGH : COUNTER_SIGNAL_LOW; + + return 0; +} + +static int mchp_tc_count_action_get(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t *action) +{ + struct mchp_tc_data *const priv = counter->priv; + u32 cmr; + + if (priv->qdec_mode) { + *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES; + return 0; + } + + /* Only TIOA signal is evaluated in non-QDEC mode */ + if (synapse->signal->id != 0) { + *action = COUNTER_SYNAPSE_ACTION_NONE; + return 0; + } + + regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CMR), &cmr); + + switch (cmr & ATMEL_TC_ETRGEDG) { + default: + *action = MCHP_TC_SYNAPSE_ACTION_NONE; + break; + case ATMEL_TC_ETRGEDG_RISING: + *action = MCHP_TC_SYNAPSE_ACTION_RISING_EDGE; + break; + case ATMEL_TC_ETRGEDG_FALLING: + *action = MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE; + break; + case ATMEL_TC_ETRGEDG_BOTH: + *action = MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE; + break; + } + + return 0; +} + +static int mchp_tc_count_action_set(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t action) +{ + struct mchp_tc_data *const priv = counter->priv; + u32 edge = ATMEL_TC_ETRGEDG_NONE; + + /* QDEC mode is rising edge only; only TIOA handled in non-QDEC mode */ + if (priv->qdec_mode || synapse->signal->id != 0) + return -EINVAL; + + switch (action) { + case MCHP_TC_SYNAPSE_ACTION_NONE: + edge = ATMEL_TC_ETRGEDG_NONE; + break; + case MCHP_TC_SYNAPSE_ACTION_RISING_EDGE: + edge = ATMEL_TC_ETRGEDG_RISING; + break; + case MCHP_TC_SYNAPSE_ACTION_FALLING_EDGE: + edge = ATMEL_TC_ETRGEDG_FALLING; + break; + case MCHP_TC_SYNAPSE_ACTION_BOTH_EDGE: + edge = ATMEL_TC_ETRGEDG_BOTH; + break; + } + + return regmap_write_bits(priv->regmap, + ATMEL_TC_REG(priv->channel[0], CMR), + ATMEL_TC_ETRGEDG, edge); +} + +static int mchp_tc_count_read(struct counter_device *counter, + struct counter_count *count, + unsigned long *val) +{ + struct mchp_tc_data *const priv = counter->priv; + u32 cnt; + + regmap_read(priv->regmap, ATMEL_TC_REG(priv->channel[0], CV), &cnt); + *val = cnt; + + return 0; +} + +static struct counter_count mchp_tc_counts[] = { + { + .id = 0, + .name = "Timer Counter", + .functions_list = mchp_tc_count_functions, + .num_functions = ARRAY_SIZE(mchp_tc_count_functions), + .synapses = mchp_tc_count_synapses, + .num_synapses = ARRAY_SIZE(mchp_tc_count_synapses), + }, +}; + +static const struct counter_ops mchp_tc_ops = { + .signal_read = mchp_tc_count_signal_read, + .count_read = mchp_tc_count_read, + .function_get = mchp_tc_count_function_get, + .function_set = mchp_tc_count_function_set, + .action_get = mchp_tc_count_action_get, + .action_set = mchp_tc_count_action_set +}; + +static const struct atmel_tcb_config tcb_rm9200_config = { + .counter_width = 16, +}; + +static const struct atmel_tcb_config tcb_sam9x5_config = { + .counter_width = 32, +}; + +static const struct atmel_tcb_config tcb_sama5d2_config = { + .counter_width = 32, + .has_gclk = true, + .has_qdec = true, +}; + +static const struct atmel_tcb_config tcb_sama5d3_config = { + .counter_width = 32, + .has_qdec = true, +}; + +static const struct of_device_id atmel_tc_of_match[] = { + { .compatible = "atmel,at91rm9200-tcb", .data = &tcb_rm9200_config, }, + { .compatible = "atmel,at91sam9x5-tcb", .data = &tcb_sam9x5_config, }, + { .compatible = "atmel,sama5d2-tcb", .data = &tcb_sama5d2_config, }, + { .compatible = "atmel,sama5d3-tcb", .data = &tcb_sama5d3_config, }, + { /* sentinel */ } +}; + +static void mchp_tc_clk_remove(void *ptr) +{ + clk_disable_unprepare((struct clk *)ptr); +} + +static int mchp_tc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct atmel_tcb_config *tcb_config; + const struct of_device_id *match; + struct mchp_tc_data *priv; + char clk_name[7]; + struct regmap *regmap; + struct clk *clk[3]; + int channel; + int ret, i; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + match = of_match_node(atmel_tc_of_match, np->parent); + tcb_config = match->data; + if (!tcb_config) { + dev_err(&pdev->dev, "No matching parent node found\n"); + return -ENODEV; + } + + regmap = syscon_node_to_regmap(np->parent); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* max. channels number is 2 when in QDEC mode */ + priv->num_channels = of_property_count_u32_elems(np, "reg"); + if (priv->num_channels < 0) { + dev_err(&pdev->dev, "Invalid or missing channel\n"); + return -EINVAL; + } + + /* Register channels and initialize clocks */ + for (i = 0; i < priv->num_channels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &channel); + if (ret < 0 || channel > 2) + return -ENODEV; + + priv->channel[i] = channel; + + snprintf(clk_name, sizeof(clk_name), "t%d_clk", channel); + + clk[i] = of_clk_get_by_name(np->parent, clk_name); + if (IS_ERR(clk[i])) { + /* Fallback to t0_clk */ + clk[i] = of_clk_get_by_name(np->parent, "t0_clk"); + if (IS_ERR(clk[i])) + return PTR_ERR(clk[i]); + } + + ret = clk_prepare_enable(clk[i]); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, + mchp_tc_clk_remove, + clk[i]); + if (ret) + return ret; + + dev_dbg(&pdev->dev, + "Initialized capture mode on channel %d\n", + channel); + } + + priv->tc_cfg = tcb_config; + priv->regmap = regmap; + priv->counter.name = dev_name(&pdev->dev); + priv->counter.parent = &pdev->dev; + priv->counter.ops = &mchp_tc_ops; + priv->counter.num_counts = ARRAY_SIZE(mchp_tc_counts); + priv->counter.counts = mchp_tc_counts; + priv->counter.num_signals = ARRAY_SIZE(mchp_tc_count_signals); + priv->counter.signals = mchp_tc_count_signals; + priv->counter.priv = priv; + + return devm_counter_register(&pdev->dev, &priv->counter); +} + +static const struct of_device_id mchp_tc_dt_ids[] = { + { .compatible = "microchip,tcb-capture", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mchp_tc_dt_ids); + +static struct platform_driver mchp_tc_driver = { + .probe = mchp_tc_probe, + .driver = { + .name = "microchip-tcb-capture", + .of_match_table = mchp_tc_dt_ids, + }, +}; +module_platform_driver(mchp_tc_driver); + +MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>"); +MODULE_DESCRIPTION("Microchip TCB Capture driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/counter/stm32-lptimer-cnt.c b/drivers/counter/stm32-lptimer-cnt.c new file mode 100644 index 000000000..b084e971a --- /dev/null +++ b/drivers/counter/stm32-lptimer-cnt.c @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STM32 Low-Power Timer Encoder and Counter driver + * + * Copyright (C) STMicroelectronics 2017 + * + * Author: Fabrice Gasnier <fabrice.gasnier@st.com> + * + * Inspired by 104-quad-8 and stm32-timer-trigger drivers. + * + */ + +#include <linux/bitfield.h> +#include <linux/counter.h> +#include <linux/mfd/stm32-lptimer.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> + +struct stm32_lptim_cnt { + struct counter_device counter; + struct device *dev; + struct regmap *regmap; + struct clk *clk; + u32 ceiling; + u32 polarity; + u32 quadrature_mode; + bool enabled; +}; + +static int stm32_lptim_is_enabled(struct stm32_lptim_cnt *priv) +{ + u32 val; + int ret; + + ret = regmap_read(priv->regmap, STM32_LPTIM_CR, &val); + if (ret) + return ret; + + return FIELD_GET(STM32_LPTIM_ENABLE, val); +} + +static int stm32_lptim_set_enable_state(struct stm32_lptim_cnt *priv, + int enable) +{ + int ret; + u32 val; + + val = FIELD_PREP(STM32_LPTIM_ENABLE, enable); + ret = regmap_write(priv->regmap, STM32_LPTIM_CR, val); + if (ret) + return ret; + + if (!enable) { + clk_disable(priv->clk); + priv->enabled = false; + return 0; + } + + /* LP timer must be enabled before writing CMP & ARR */ + ret = regmap_write(priv->regmap, STM32_LPTIM_ARR, priv->ceiling); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, STM32_LPTIM_CMP, 0); + if (ret) + return ret; + + /* ensure CMP & ARR registers are properly written */ + ret = regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val, + (val & STM32_LPTIM_CMPOK_ARROK) == STM32_LPTIM_CMPOK_ARROK, + 100, 1000); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, STM32_LPTIM_ICR, + STM32_LPTIM_CMPOKCF_ARROKCF); + if (ret) + return ret; + + ret = clk_enable(priv->clk); + if (ret) { + regmap_write(priv->regmap, STM32_LPTIM_CR, 0); + return ret; + } + priv->enabled = true; + + /* Start LP timer in continuous mode */ + return regmap_update_bits(priv->regmap, STM32_LPTIM_CR, + STM32_LPTIM_CNTSTRT, STM32_LPTIM_CNTSTRT); +} + +static int stm32_lptim_setup(struct stm32_lptim_cnt *priv, int enable) +{ + u32 mask = STM32_LPTIM_ENC | STM32_LPTIM_COUNTMODE | + STM32_LPTIM_CKPOL | STM32_LPTIM_PRESC; + u32 val; + + /* Setup LP timer encoder/counter and polarity, without prescaler */ + if (priv->quadrature_mode) + val = enable ? STM32_LPTIM_ENC : 0; + else + val = enable ? STM32_LPTIM_COUNTMODE : 0; + val |= FIELD_PREP(STM32_LPTIM_CKPOL, enable ? priv->polarity : 0); + + return regmap_update_bits(priv->regmap, STM32_LPTIM_CFGR, mask, val); +} + +/** + * enum stm32_lptim_cnt_function - enumerates LPTimer counter & encoder modes + * @STM32_LPTIM_COUNTER_INCREASE: up count on IN1 rising, falling or both edges + * @STM32_LPTIM_ENCODER_BOTH_EDGE: count on both edges (IN1 & IN2 quadrature) + * + * In non-quadrature mode, device counts up on active edge. + * In quadrature mode, encoder counting scenarios are as follows: + * +---------+----------+--------------------+--------------------+ + * | Active | Level on | IN1 signal | IN2 signal | + * | edge | opposite +----------+---------+----------+---------+ + * | | signal | Rising | Falling | Rising | Falling | + * +---------+----------+----------+---------+----------+---------+ + * | Rising | High -> | Down | - | Up | - | + * | edge | Low -> | Up | - | Down | - | + * +---------+----------+----------+---------+----------+---------+ + * | Falling | High -> | - | Up | - | Down | + * | edge | Low -> | - | Down | - | Up | + * +---------+----------+----------+---------+----------+---------+ + * | Both | High -> | Down | Up | Up | Down | + * | edges | Low -> | Up | Down | Down | Up | + * +---------+----------+----------+---------+----------+---------+ + */ +enum stm32_lptim_cnt_function { + STM32_LPTIM_COUNTER_INCREASE, + STM32_LPTIM_ENCODER_BOTH_EDGE, +}; + +static enum counter_count_function stm32_lptim_cnt_functions[] = { + [STM32_LPTIM_COUNTER_INCREASE] = COUNTER_COUNT_FUNCTION_INCREASE, + [STM32_LPTIM_ENCODER_BOTH_EDGE] = COUNTER_COUNT_FUNCTION_QUADRATURE_X4, +}; + +enum stm32_lptim_synapse_action { + STM32_LPTIM_SYNAPSE_ACTION_RISING_EDGE, + STM32_LPTIM_SYNAPSE_ACTION_FALLING_EDGE, + STM32_LPTIM_SYNAPSE_ACTION_BOTH_EDGES, + STM32_LPTIM_SYNAPSE_ACTION_NONE, +}; + +static enum counter_synapse_action stm32_lptim_cnt_synapse_actions[] = { + /* Index must match with stm32_lptim_cnt_polarity[] (priv->polarity) */ + [STM32_LPTIM_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE, + [STM32_LPTIM_SYNAPSE_ACTION_FALLING_EDGE] = COUNTER_SYNAPSE_ACTION_FALLING_EDGE, + [STM32_LPTIM_SYNAPSE_ACTION_BOTH_EDGES] = COUNTER_SYNAPSE_ACTION_BOTH_EDGES, + [STM32_LPTIM_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, +}; + +static int stm32_lptim_cnt_read(struct counter_device *counter, + struct counter_count *count, unsigned long *val) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + u32 cnt; + int ret; + + ret = regmap_read(priv->regmap, STM32_LPTIM_CNT, &cnt); + if (ret) + return ret; + + *val = cnt; + + return 0; +} + +static int stm32_lptim_cnt_function_get(struct counter_device *counter, + struct counter_count *count, + size_t *function) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + + if (!priv->quadrature_mode) { + *function = STM32_LPTIM_COUNTER_INCREASE; + return 0; + } + + if (priv->polarity == STM32_LPTIM_SYNAPSE_ACTION_BOTH_EDGES) { + *function = STM32_LPTIM_ENCODER_BOTH_EDGE; + return 0; + } + + return -EINVAL; +} + +static int stm32_lptim_cnt_function_set(struct counter_device *counter, + struct counter_count *count, + size_t function) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + + if (stm32_lptim_is_enabled(priv)) + return -EBUSY; + + switch (function) { + case STM32_LPTIM_COUNTER_INCREASE: + priv->quadrature_mode = 0; + return 0; + case STM32_LPTIM_ENCODER_BOTH_EDGE: + priv->quadrature_mode = 1; + priv->polarity = STM32_LPTIM_SYNAPSE_ACTION_BOTH_EDGES; + return 0; + } + + return -EINVAL; +} + +static ssize_t stm32_lptim_cnt_enable_read(struct counter_device *counter, + struct counter_count *count, + void *private, char *buf) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + int ret; + + ret = stm32_lptim_is_enabled(priv); + if (ret < 0) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%u\n", ret); +} + +static ssize_t stm32_lptim_cnt_enable_write(struct counter_device *counter, + struct counter_count *count, + void *private, + const char *buf, size_t len) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret) + return ret; + + /* Check nobody uses the timer, or already disabled/enabled */ + ret = stm32_lptim_is_enabled(priv); + if ((ret < 0) || (!ret && !enable)) + return ret; + if (enable && ret) + return -EBUSY; + + ret = stm32_lptim_setup(priv, enable); + if (ret) + return ret; + + ret = stm32_lptim_set_enable_state(priv, enable); + if (ret) + return ret; + + return len; +} + +static ssize_t stm32_lptim_cnt_ceiling_read(struct counter_device *counter, + struct counter_count *count, + void *private, char *buf) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + + return snprintf(buf, PAGE_SIZE, "%u\n", priv->ceiling); +} + +static ssize_t stm32_lptim_cnt_ceiling_write(struct counter_device *counter, + struct counter_count *count, + void *private, + const char *buf, size_t len) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + unsigned int ceiling; + int ret; + + if (stm32_lptim_is_enabled(priv)) + return -EBUSY; + + ret = kstrtouint(buf, 0, &ceiling); + if (ret) + return ret; + + if (ceiling > STM32_LPTIM_MAX_ARR) + return -EINVAL; + + priv->ceiling = ceiling; + + return len; +} + +static const struct counter_count_ext stm32_lptim_cnt_ext[] = { + { + .name = "enable", + .read = stm32_lptim_cnt_enable_read, + .write = stm32_lptim_cnt_enable_write + }, + { + .name = "ceiling", + .read = stm32_lptim_cnt_ceiling_read, + .write = stm32_lptim_cnt_ceiling_write + }, +}; + +static int stm32_lptim_cnt_action_get(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t *action) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + size_t function; + int err; + + err = stm32_lptim_cnt_function_get(counter, count, &function); + if (err) + return err; + + switch (function) { + case STM32_LPTIM_COUNTER_INCREASE: + /* LP Timer acts as up-counter on input 1 */ + if (synapse->signal->id == count->synapses[0].signal->id) + *action = priv->polarity; + else + *action = STM32_LPTIM_SYNAPSE_ACTION_NONE; + return 0; + case STM32_LPTIM_ENCODER_BOTH_EDGE: + *action = priv->polarity; + return 0; + } + + return -EINVAL; +} + +static int stm32_lptim_cnt_action_set(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t action) +{ + struct stm32_lptim_cnt *const priv = counter->priv; + size_t function; + int err; + + if (stm32_lptim_is_enabled(priv)) + return -EBUSY; + + err = stm32_lptim_cnt_function_get(counter, count, &function); + if (err) + return err; + + /* only set polarity when in counter mode (on input 1) */ + if (function == STM32_LPTIM_COUNTER_INCREASE + && synapse->signal->id == count->synapses[0].signal->id) { + switch (action) { + case STM32_LPTIM_SYNAPSE_ACTION_RISING_EDGE: + case STM32_LPTIM_SYNAPSE_ACTION_FALLING_EDGE: + case STM32_LPTIM_SYNAPSE_ACTION_BOTH_EDGES: + priv->polarity = action; + return 0; + } + } + + return -EINVAL; +} + +static const struct counter_ops stm32_lptim_cnt_ops = { + .count_read = stm32_lptim_cnt_read, + .function_get = stm32_lptim_cnt_function_get, + .function_set = stm32_lptim_cnt_function_set, + .action_get = stm32_lptim_cnt_action_get, + .action_set = stm32_lptim_cnt_action_set, +}; + +static struct counter_signal stm32_lptim_cnt_signals[] = { + { + .id = 0, + .name = "Channel 1 Quadrature A" + }, + { + .id = 1, + .name = "Channel 1 Quadrature B" + } +}; + +static struct counter_synapse stm32_lptim_cnt_synapses[] = { + { + .actions_list = stm32_lptim_cnt_synapse_actions, + .num_actions = ARRAY_SIZE(stm32_lptim_cnt_synapse_actions), + .signal = &stm32_lptim_cnt_signals[0] + }, + { + .actions_list = stm32_lptim_cnt_synapse_actions, + .num_actions = ARRAY_SIZE(stm32_lptim_cnt_synapse_actions), + .signal = &stm32_lptim_cnt_signals[1] + } +}; + +/* LP timer with encoder */ +static struct counter_count stm32_lptim_enc_counts = { + .id = 0, + .name = "LPTimer Count", + .functions_list = stm32_lptim_cnt_functions, + .num_functions = ARRAY_SIZE(stm32_lptim_cnt_functions), + .synapses = stm32_lptim_cnt_synapses, + .num_synapses = ARRAY_SIZE(stm32_lptim_cnt_synapses), + .ext = stm32_lptim_cnt_ext, + .num_ext = ARRAY_SIZE(stm32_lptim_cnt_ext) +}; + +/* LP timer without encoder (counter only) */ +static struct counter_count stm32_lptim_in1_counts = { + .id = 0, + .name = "LPTimer Count", + .functions_list = stm32_lptim_cnt_functions, + .num_functions = 1, + .synapses = stm32_lptim_cnt_synapses, + .num_synapses = 1, + .ext = stm32_lptim_cnt_ext, + .num_ext = ARRAY_SIZE(stm32_lptim_cnt_ext) +}; + +static int stm32_lptim_cnt_probe(struct platform_device *pdev) +{ + struct stm32_lptimer *ddata = dev_get_drvdata(pdev->dev.parent); + struct stm32_lptim_cnt *priv; + + if (IS_ERR_OR_NULL(ddata)) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->regmap = ddata->regmap; + priv->clk = ddata->clk; + priv->ceiling = STM32_LPTIM_MAX_ARR; + + /* Initialize Counter device */ + priv->counter.name = dev_name(&pdev->dev); + priv->counter.parent = &pdev->dev; + priv->counter.ops = &stm32_lptim_cnt_ops; + if (ddata->has_encoder) { + priv->counter.counts = &stm32_lptim_enc_counts; + priv->counter.num_signals = ARRAY_SIZE(stm32_lptim_cnt_signals); + } else { + priv->counter.counts = &stm32_lptim_in1_counts; + priv->counter.num_signals = 1; + } + priv->counter.num_counts = 1; + priv->counter.signals = stm32_lptim_cnt_signals; + priv->counter.priv = priv; + + platform_set_drvdata(pdev, priv); + + return devm_counter_register(&pdev->dev, &priv->counter); +} + +#ifdef CONFIG_PM_SLEEP +static int stm32_lptim_cnt_suspend(struct device *dev) +{ + struct stm32_lptim_cnt *priv = dev_get_drvdata(dev); + int ret; + + /* Only take care of enabled counter: don't disturb other MFD child */ + if (priv->enabled) { + ret = stm32_lptim_setup(priv, 0); + if (ret) + return ret; + + ret = stm32_lptim_set_enable_state(priv, 0); + if (ret) + return ret; + + /* Force enable state for later resume */ + priv->enabled = true; + } + + return pinctrl_pm_select_sleep_state(dev); +} + +static int stm32_lptim_cnt_resume(struct device *dev) +{ + struct stm32_lptim_cnt *priv = dev_get_drvdata(dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret) + return ret; + + if (priv->enabled) { + priv->enabled = false; + ret = stm32_lptim_setup(priv, 1); + if (ret) + return ret; + + ret = stm32_lptim_set_enable_state(priv, 1); + if (ret) + return ret; + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(stm32_lptim_cnt_pm_ops, stm32_lptim_cnt_suspend, + stm32_lptim_cnt_resume); + +static const struct of_device_id stm32_lptim_cnt_of_match[] = { + { .compatible = "st,stm32-lptimer-counter", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_lptim_cnt_of_match); + +static struct platform_driver stm32_lptim_cnt_driver = { + .probe = stm32_lptim_cnt_probe, + .driver = { + .name = "stm32-lptimer-counter", + .of_match_table = stm32_lptim_cnt_of_match, + .pm = &stm32_lptim_cnt_pm_ops, + }, +}; +module_platform_driver(stm32_lptim_cnt_driver); + +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>"); +MODULE_ALIAS("platform:stm32-lptimer-counter"); +MODULE_DESCRIPTION("STMicroelectronics STM32 LPTIM counter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c new file mode 100644 index 000000000..75bc401fd --- /dev/null +++ b/drivers/counter/stm32-timer-cnt.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STM32 Timer Encoder and Counter driver + * + * Copyright (C) STMicroelectronics 2018 + * + * Author: Benjamin Gaignard <benjamin.gaignard@st.com> + * + */ +#include <linux/counter.h> +#include <linux/mfd/stm32-timers.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> + +#define TIM_CCMR_CCXS (BIT(8) | BIT(0)) +#define TIM_CCMR_MASK (TIM_CCMR_CC1S | TIM_CCMR_CC2S | \ + TIM_CCMR_IC1F | TIM_CCMR_IC2F) +#define TIM_CCER_MASK (TIM_CCER_CC1P | TIM_CCER_CC1NP | \ + TIM_CCER_CC2P | TIM_CCER_CC2NP) + +struct stm32_timer_regs { + u32 cr1; + u32 cnt; + u32 smcr; + u32 arr; +}; + +struct stm32_timer_cnt { + struct counter_device counter; + struct regmap *regmap; + struct clk *clk; + u32 max_arr; + bool enabled; + struct stm32_timer_regs bak; +}; + +/** + * enum stm32_count_function - enumerates stm32 timer counter encoder modes + * @STM32_COUNT_SLAVE_MODE_DISABLED: counts on internal clock when CEN=1 + * @STM32_COUNT_ENCODER_MODE_1: counts TI1FP1 edges, depending on TI2FP2 level + * @STM32_COUNT_ENCODER_MODE_2: counts TI2FP2 edges, depending on TI1FP1 level + * @STM32_COUNT_ENCODER_MODE_3: counts on both TI1FP1 and TI2FP2 edges + */ +enum stm32_count_function { + STM32_COUNT_SLAVE_MODE_DISABLED, + STM32_COUNT_ENCODER_MODE_1, + STM32_COUNT_ENCODER_MODE_2, + STM32_COUNT_ENCODER_MODE_3, +}; + +static enum counter_count_function stm32_count_functions[] = { + [STM32_COUNT_SLAVE_MODE_DISABLED] = COUNTER_COUNT_FUNCTION_INCREASE, + [STM32_COUNT_ENCODER_MODE_1] = COUNTER_COUNT_FUNCTION_QUADRATURE_X2_A, + [STM32_COUNT_ENCODER_MODE_2] = COUNTER_COUNT_FUNCTION_QUADRATURE_X2_B, + [STM32_COUNT_ENCODER_MODE_3] = COUNTER_COUNT_FUNCTION_QUADRATURE_X4, +}; + +static int stm32_count_read(struct counter_device *counter, + struct counter_count *count, unsigned long *val) +{ + struct stm32_timer_cnt *const priv = counter->priv; + u32 cnt; + + regmap_read(priv->regmap, TIM_CNT, &cnt); + *val = cnt; + + return 0; +} + +static int stm32_count_write(struct counter_device *counter, + struct counter_count *count, + const unsigned long val) +{ + struct stm32_timer_cnt *const priv = counter->priv; + u32 ceiling; + + regmap_read(priv->regmap, TIM_ARR, &ceiling); + if (val > ceiling) + return -EINVAL; + + return regmap_write(priv->regmap, TIM_CNT, val); +} + +static int stm32_count_function_get(struct counter_device *counter, + struct counter_count *count, + size_t *function) +{ + struct stm32_timer_cnt *const priv = counter->priv; + u32 smcr; + + regmap_read(priv->regmap, TIM_SMCR, &smcr); + + switch (smcr & TIM_SMCR_SMS) { + case 0: + *function = STM32_COUNT_SLAVE_MODE_DISABLED; + return 0; + case 1: + *function = STM32_COUNT_ENCODER_MODE_1; + return 0; + case 2: + *function = STM32_COUNT_ENCODER_MODE_2; + return 0; + case 3: + *function = STM32_COUNT_ENCODER_MODE_3; + return 0; + default: + return -EINVAL; + } +} + +static int stm32_count_function_set(struct counter_device *counter, + struct counter_count *count, + size_t function) +{ + struct stm32_timer_cnt *const priv = counter->priv; + u32 cr1, sms; + + switch (function) { + case STM32_COUNT_SLAVE_MODE_DISABLED: + sms = 0; + break; + case STM32_COUNT_ENCODER_MODE_1: + sms = 1; + break; + case STM32_COUNT_ENCODER_MODE_2: + sms = 2; + break; + case STM32_COUNT_ENCODER_MODE_3: + sms = 3; + break; + default: + return -EINVAL; + } + + /* Store enable status */ + regmap_read(priv->regmap, TIM_CR1, &cr1); + + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0); + + regmap_update_bits(priv->regmap, TIM_SMCR, TIM_SMCR_SMS, sms); + + /* Make sure that registers are updated */ + regmap_update_bits(priv->regmap, TIM_EGR, TIM_EGR_UG, TIM_EGR_UG); + + /* Restore the enable status */ + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, cr1); + + return 0; +} + +static ssize_t stm32_count_direction_read(struct counter_device *counter, + struct counter_count *count, + void *private, char *buf) +{ + struct stm32_timer_cnt *const priv = counter->priv; + const char *direction; + u32 cr1; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + direction = (cr1 & TIM_CR1_DIR) ? "backward" : "forward"; + + return scnprintf(buf, PAGE_SIZE, "%s\n", direction); +} + +static ssize_t stm32_count_ceiling_read(struct counter_device *counter, + struct counter_count *count, + void *private, char *buf) +{ + struct stm32_timer_cnt *const priv = counter->priv; + u32 arr; + + regmap_read(priv->regmap, TIM_ARR, &arr); + + return snprintf(buf, PAGE_SIZE, "%u\n", arr); +} + +static ssize_t stm32_count_ceiling_write(struct counter_device *counter, + struct counter_count *count, + void *private, + const char *buf, size_t len) +{ + struct stm32_timer_cnt *const priv = counter->priv; + unsigned int ceiling; + int ret; + + ret = kstrtouint(buf, 0, &ceiling); + if (ret) + return ret; + + if (ceiling > priv->max_arr) + return -ERANGE; + + /* TIMx_ARR register shouldn't be buffered (ARPE=0) */ + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE, 0); + regmap_write(priv->regmap, TIM_ARR, ceiling); + + return len; +} + +static ssize_t stm32_count_enable_read(struct counter_device *counter, + struct counter_count *count, + void *private, char *buf) +{ + struct stm32_timer_cnt *const priv = counter->priv; + u32 cr1; + + regmap_read(priv->regmap, TIM_CR1, &cr1); + + return scnprintf(buf, PAGE_SIZE, "%d\n", (bool)(cr1 & TIM_CR1_CEN)); +} + +static ssize_t stm32_count_enable_write(struct counter_device *counter, + struct counter_count *count, + void *private, + const char *buf, size_t len) +{ + struct stm32_timer_cnt *const priv = counter->priv; + int err; + u32 cr1; + bool enable; + + err = kstrtobool(buf, &enable); + if (err) + return err; + + if (enable) { + regmap_read(priv->regmap, TIM_CR1, &cr1); + if (!(cr1 & TIM_CR1_CEN)) + clk_enable(priv->clk); + + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, + TIM_CR1_CEN); + } else { + regmap_read(priv->regmap, TIM_CR1, &cr1); + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0); + if (cr1 & TIM_CR1_CEN) + clk_disable(priv->clk); + } + + /* Keep enabled state to properly handle low power states */ + priv->enabled = enable; + + return len; +} + +static const struct counter_count_ext stm32_count_ext[] = { + { + .name = "direction", + .read = stm32_count_direction_read, + }, + { + .name = "enable", + .read = stm32_count_enable_read, + .write = stm32_count_enable_write + }, + { + .name = "ceiling", + .read = stm32_count_ceiling_read, + .write = stm32_count_ceiling_write + }, +}; + +enum stm32_synapse_action { + STM32_SYNAPSE_ACTION_NONE, + STM32_SYNAPSE_ACTION_BOTH_EDGES +}; + +static enum counter_synapse_action stm32_synapse_actions[] = { + [STM32_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, + [STM32_SYNAPSE_ACTION_BOTH_EDGES] = COUNTER_SYNAPSE_ACTION_BOTH_EDGES +}; + +static int stm32_action_get(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, + size_t *action) +{ + size_t function; + int err; + + err = stm32_count_function_get(counter, count, &function); + if (err) + return err; + + switch (function) { + case STM32_COUNT_SLAVE_MODE_DISABLED: + /* counts on internal clock when CEN=1 */ + *action = STM32_SYNAPSE_ACTION_NONE; + return 0; + case STM32_COUNT_ENCODER_MODE_1: + /* counts up/down on TI1FP1 edge depending on TI2FP2 level */ + if (synapse->signal->id == count->synapses[0].signal->id) + *action = STM32_SYNAPSE_ACTION_BOTH_EDGES; + else + *action = STM32_SYNAPSE_ACTION_NONE; + return 0; + case STM32_COUNT_ENCODER_MODE_2: + /* counts up/down on TI2FP2 edge depending on TI1FP1 level */ + if (synapse->signal->id == count->synapses[1].signal->id) + *action = STM32_SYNAPSE_ACTION_BOTH_EDGES; + else + *action = STM32_SYNAPSE_ACTION_NONE; + return 0; + case STM32_COUNT_ENCODER_MODE_3: + /* counts up/down on both TI1FP1 and TI2FP2 edges */ + *action = STM32_SYNAPSE_ACTION_BOTH_EDGES; + return 0; + default: + return -EINVAL; + } +} + +static const struct counter_ops stm32_timer_cnt_ops = { + .count_read = stm32_count_read, + .count_write = stm32_count_write, + .function_get = stm32_count_function_get, + .function_set = stm32_count_function_set, + .action_get = stm32_action_get, +}; + +static struct counter_signal stm32_signals[] = { + { + .id = 0, + .name = "Channel 1 Quadrature A" + }, + { + .id = 1, + .name = "Channel 1 Quadrature B" + } +}; + +static struct counter_synapse stm32_count_synapses[] = { + { + .actions_list = stm32_synapse_actions, + .num_actions = ARRAY_SIZE(stm32_synapse_actions), + .signal = &stm32_signals[0] + }, + { + .actions_list = stm32_synapse_actions, + .num_actions = ARRAY_SIZE(stm32_synapse_actions), + .signal = &stm32_signals[1] + } +}; + +static struct counter_count stm32_counts = { + .id = 0, + .name = "Channel 1 Count", + .functions_list = stm32_count_functions, + .num_functions = ARRAY_SIZE(stm32_count_functions), + .synapses = stm32_count_synapses, + .num_synapses = ARRAY_SIZE(stm32_count_synapses), + .ext = stm32_count_ext, + .num_ext = ARRAY_SIZE(stm32_count_ext) +}; + +static int stm32_timer_cnt_probe(struct platform_device *pdev) +{ + struct stm32_timers *ddata = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct stm32_timer_cnt *priv; + + if (IS_ERR_OR_NULL(ddata)) + return -EINVAL; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = ddata->regmap; + priv->clk = ddata->clk; + priv->max_arr = ddata->max_arr; + + priv->counter.name = dev_name(dev); + priv->counter.parent = dev; + priv->counter.ops = &stm32_timer_cnt_ops; + priv->counter.counts = &stm32_counts; + priv->counter.num_counts = 1; + priv->counter.signals = stm32_signals; + priv->counter.num_signals = ARRAY_SIZE(stm32_signals); + priv->counter.priv = priv; + + platform_set_drvdata(pdev, priv); + + /* Register Counter device */ + return devm_counter_register(dev, &priv->counter); +} + +static int __maybe_unused stm32_timer_cnt_suspend(struct device *dev) +{ + struct stm32_timer_cnt *priv = dev_get_drvdata(dev); + + /* Only take care of enabled counter: don't disturb other MFD child */ + if (priv->enabled) { + /* Backup registers that may get lost in low power mode */ + regmap_read(priv->regmap, TIM_SMCR, &priv->bak.smcr); + regmap_read(priv->regmap, TIM_ARR, &priv->bak.arr); + regmap_read(priv->regmap, TIM_CNT, &priv->bak.cnt); + regmap_read(priv->regmap, TIM_CR1, &priv->bak.cr1); + + /* Disable the counter */ + regmap_update_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN, 0); + clk_disable(priv->clk); + } + + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused stm32_timer_cnt_resume(struct device *dev) +{ + struct stm32_timer_cnt *priv = dev_get_drvdata(dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret) + return ret; + + if (priv->enabled) { + clk_enable(priv->clk); + + /* Restore registers that may have been lost */ + regmap_write(priv->regmap, TIM_SMCR, priv->bak.smcr); + regmap_write(priv->regmap, TIM_ARR, priv->bak.arr); + regmap_write(priv->regmap, TIM_CNT, priv->bak.cnt); + + /* Also re-enables the counter */ + regmap_write(priv->regmap, TIM_CR1, priv->bak.cr1); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(stm32_timer_cnt_pm_ops, stm32_timer_cnt_suspend, + stm32_timer_cnt_resume); + +static const struct of_device_id stm32_timer_cnt_of_match[] = { + { .compatible = "st,stm32-timer-counter", }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_timer_cnt_of_match); + +static struct platform_driver stm32_timer_cnt_driver = { + .probe = stm32_timer_cnt_probe, + .driver = { + .name = "stm32-timer-counter", + .of_match_table = stm32_timer_cnt_of_match, + .pm = &stm32_timer_cnt_pm_ops, + }, +}; +module_platform_driver(stm32_timer_cnt_driver); + +MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>"); +MODULE_ALIAS("platform:stm32-timer-counter"); +MODULE_DESCRIPTION("STMicroelectronics STM32 TIMER counter driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/counter/ti-eqep.c b/drivers/counter/ti-eqep.c new file mode 100644 index 000000000..65df9ef5b --- /dev/null +++ b/drivers/counter/ti-eqep.c @@ -0,0 +1,431 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2019 David Lechner <david@lechnology.com> + * + * Counter driver for Texas Instruments Enhanced Quadrature Encoder Pulse (eQEP) + */ + +#include <linux/bitops.h> +#include <linux/counter.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> + +/* 32-bit registers */ +#define QPOSCNT 0x0 +#define QPOSINIT 0x4 +#define QPOSMAX 0x8 +#define QPOSCMP 0xc +#define QPOSILAT 0x10 +#define QPOSSLAT 0x14 +#define QPOSLAT 0x18 +#define QUTMR 0x1c +#define QUPRD 0x20 + +/* 16-bit registers */ +#define QWDTMR 0x0 /* 0x24 */ +#define QWDPRD 0x2 /* 0x26 */ +#define QDECCTL 0x4 /* 0x28 */ +#define QEPCTL 0x6 /* 0x2a */ +#define QCAPCTL 0x8 /* 0x2c */ +#define QPOSCTL 0xa /* 0x2e */ +#define QEINT 0xc /* 0x30 */ +#define QFLG 0xe /* 0x32 */ +#define QCLR 0x10 /* 0x34 */ +#define QFRC 0x12 /* 0x36 */ +#define QEPSTS 0x14 /* 0x38 */ +#define QCTMR 0x16 /* 0x3a */ +#define QCPRD 0x18 /* 0x3c */ +#define QCTMRLAT 0x1a /* 0x3e */ +#define QCPRDLAT 0x1c /* 0x40 */ + +#define QDECCTL_QSRC_SHIFT 14 +#define QDECCTL_QSRC GENMASK(15, 14) +#define QDECCTL_SOEN BIT(13) +#define QDECCTL_SPSEL BIT(12) +#define QDECCTL_XCR BIT(11) +#define QDECCTL_SWAP BIT(10) +#define QDECCTL_IGATE BIT(9) +#define QDECCTL_QAP BIT(8) +#define QDECCTL_QBP BIT(7) +#define QDECCTL_QIP BIT(6) +#define QDECCTL_QSP BIT(5) + +#define QEPCTL_FREE_SOFT GENMASK(15, 14) +#define QEPCTL_PCRM GENMASK(13, 12) +#define QEPCTL_SEI GENMASK(11, 10) +#define QEPCTL_IEI GENMASK(9, 8) +#define QEPCTL_SWI BIT(7) +#define QEPCTL_SEL BIT(6) +#define QEPCTL_IEL GENMASK(5, 4) +#define QEPCTL_PHEN BIT(3) +#define QEPCTL_QCLM BIT(2) +#define QEPCTL_UTE BIT(1) +#define QEPCTL_WDE BIT(0) + +/* EQEP Inputs */ +enum { + TI_EQEP_SIGNAL_QEPA, /* QEPA/XCLK */ + TI_EQEP_SIGNAL_QEPB, /* QEPB/XDIR */ +}; + +/* Position Counter Input Modes */ +enum { + TI_EQEP_COUNT_FUNC_QUAD_COUNT, + TI_EQEP_COUNT_FUNC_DIR_COUNT, + TI_EQEP_COUNT_FUNC_UP_COUNT, + TI_EQEP_COUNT_FUNC_DOWN_COUNT, +}; + +enum { + TI_EQEP_SYNAPSE_ACTION_BOTH_EDGES, + TI_EQEP_SYNAPSE_ACTION_RISING_EDGE, + TI_EQEP_SYNAPSE_ACTION_NONE, +}; + +struct ti_eqep_cnt { + struct counter_device counter; + struct regmap *regmap32; + struct regmap *regmap16; +}; + +static int ti_eqep_count_read(struct counter_device *counter, + struct counter_count *count, unsigned long *val) +{ + struct ti_eqep_cnt *priv = counter->priv; + u32 cnt; + + regmap_read(priv->regmap32, QPOSCNT, &cnt); + *val = cnt; + + return 0; +} + +static int ti_eqep_count_write(struct counter_device *counter, + struct counter_count *count, unsigned long val) +{ + struct ti_eqep_cnt *priv = counter->priv; + u32 max; + + regmap_read(priv->regmap32, QPOSMAX, &max); + if (val > max) + return -EINVAL; + + return regmap_write(priv->regmap32, QPOSCNT, val); +} + +static int ti_eqep_function_get(struct counter_device *counter, + struct counter_count *count, size_t *function) +{ + struct ti_eqep_cnt *priv = counter->priv; + u32 qdecctl; + + regmap_read(priv->regmap16, QDECCTL, &qdecctl); + *function = (qdecctl & QDECCTL_QSRC) >> QDECCTL_QSRC_SHIFT; + + return 0; +} + +static int ti_eqep_function_set(struct counter_device *counter, + struct counter_count *count, size_t function) +{ + struct ti_eqep_cnt *priv = counter->priv; + + return regmap_write_bits(priv->regmap16, QDECCTL, QDECCTL_QSRC, + function << QDECCTL_QSRC_SHIFT); +} + +static int ti_eqep_action_get(struct counter_device *counter, + struct counter_count *count, + struct counter_synapse *synapse, size_t *action) +{ + struct ti_eqep_cnt *priv = counter->priv; + size_t function; + u32 qdecctl; + int err; + + err = ti_eqep_function_get(counter, count, &function); + if (err) + return err; + + switch (function) { + case TI_EQEP_COUNT_FUNC_QUAD_COUNT: + /* In quadrature mode, the rising and falling edge of both + * QEPA and QEPB trigger QCLK. + */ + *action = TI_EQEP_SYNAPSE_ACTION_BOTH_EDGES; + break; + case TI_EQEP_COUNT_FUNC_DIR_COUNT: + /* In direction-count mode only rising edge of QEPA is counted + * and QEPB gives direction. + */ + switch (synapse->signal->id) { + case TI_EQEP_SIGNAL_QEPA: + *action = TI_EQEP_SYNAPSE_ACTION_RISING_EDGE; + break; + default: + *action = TI_EQEP_SYNAPSE_ACTION_NONE; + break; + } + break; + case TI_EQEP_COUNT_FUNC_UP_COUNT: + case TI_EQEP_COUNT_FUNC_DOWN_COUNT: + /* In up/down-count modes only QEPA is counted and QEPB is not + * used. + */ + switch (synapse->signal->id) { + case TI_EQEP_SIGNAL_QEPA: + err = regmap_read(priv->regmap16, QDECCTL, &qdecctl); + if (err) + return err; + + if (qdecctl & QDECCTL_XCR) + *action = TI_EQEP_SYNAPSE_ACTION_BOTH_EDGES; + else + *action = TI_EQEP_SYNAPSE_ACTION_RISING_EDGE; + break; + default: + *action = TI_EQEP_SYNAPSE_ACTION_NONE; + break; + } + break; + } + + return 0; +} + +static const struct counter_ops ti_eqep_counter_ops = { + .count_read = ti_eqep_count_read, + .count_write = ti_eqep_count_write, + .function_get = ti_eqep_function_get, + .function_set = ti_eqep_function_set, + .action_get = ti_eqep_action_get, +}; + +static ssize_t ti_eqep_position_ceiling_read(struct counter_device *counter, + struct counter_count *count, + void *ext_priv, char *buf) +{ + struct ti_eqep_cnt *priv = counter->priv; + u32 qposmax; + + regmap_read(priv->regmap32, QPOSMAX, &qposmax); + + return sprintf(buf, "%u\n", qposmax); +} + +static ssize_t ti_eqep_position_ceiling_write(struct counter_device *counter, + struct counter_count *count, + void *ext_priv, const char *buf, + size_t len) +{ + struct ti_eqep_cnt *priv = counter->priv; + int err; + u32 res; + + err = kstrtouint(buf, 0, &res); + if (err < 0) + return err; + + regmap_write(priv->regmap32, QPOSMAX, res); + + return len; +} + +static ssize_t ti_eqep_position_enable_read(struct counter_device *counter, + struct counter_count *count, + void *ext_priv, char *buf) +{ + struct ti_eqep_cnt *priv = counter->priv; + u32 qepctl; + + regmap_read(priv->regmap16, QEPCTL, &qepctl); + + return sprintf(buf, "%u\n", !!(qepctl & QEPCTL_PHEN)); +} + +static ssize_t ti_eqep_position_enable_write(struct counter_device *counter, + struct counter_count *count, + void *ext_priv, const char *buf, + size_t len) +{ + struct ti_eqep_cnt *priv = counter->priv; + int err; + bool res; + + err = kstrtobool(buf, &res); + if (err < 0) + return err; + + regmap_write_bits(priv->regmap16, QEPCTL, QEPCTL_PHEN, res ? -1 : 0); + + return len; +} + +static struct counter_count_ext ti_eqep_position_ext[] = { + { + .name = "ceiling", + .read = ti_eqep_position_ceiling_read, + .write = ti_eqep_position_ceiling_write, + }, + { + .name = "enable", + .read = ti_eqep_position_enable_read, + .write = ti_eqep_position_enable_write, + }, +}; + +static struct counter_signal ti_eqep_signals[] = { + [TI_EQEP_SIGNAL_QEPA] = { + .id = TI_EQEP_SIGNAL_QEPA, + .name = "QEPA" + }, + [TI_EQEP_SIGNAL_QEPB] = { + .id = TI_EQEP_SIGNAL_QEPB, + .name = "QEPB" + }, +}; + +static const enum counter_count_function ti_eqep_position_functions[] = { + [TI_EQEP_COUNT_FUNC_QUAD_COUNT] = COUNTER_COUNT_FUNCTION_QUADRATURE_X4, + [TI_EQEP_COUNT_FUNC_DIR_COUNT] = COUNTER_COUNT_FUNCTION_PULSE_DIRECTION, + [TI_EQEP_COUNT_FUNC_UP_COUNT] = COUNTER_COUNT_FUNCTION_INCREASE, + [TI_EQEP_COUNT_FUNC_DOWN_COUNT] = COUNTER_COUNT_FUNCTION_DECREASE, +}; + +static const enum counter_synapse_action ti_eqep_position_synapse_actions[] = { + [TI_EQEP_SYNAPSE_ACTION_BOTH_EDGES] = COUNTER_SYNAPSE_ACTION_BOTH_EDGES, + [TI_EQEP_SYNAPSE_ACTION_RISING_EDGE] = COUNTER_SYNAPSE_ACTION_RISING_EDGE, + [TI_EQEP_SYNAPSE_ACTION_NONE] = COUNTER_SYNAPSE_ACTION_NONE, +}; + +static struct counter_synapse ti_eqep_position_synapses[] = { + { + .actions_list = ti_eqep_position_synapse_actions, + .num_actions = ARRAY_SIZE(ti_eqep_position_synapse_actions), + .signal = &ti_eqep_signals[TI_EQEP_SIGNAL_QEPA], + }, + { + .actions_list = ti_eqep_position_synapse_actions, + .num_actions = ARRAY_SIZE(ti_eqep_position_synapse_actions), + .signal = &ti_eqep_signals[TI_EQEP_SIGNAL_QEPB], + }, +}; + +static struct counter_count ti_eqep_counts[] = { + { + .id = 0, + .name = "QPOSCNT", + .functions_list = ti_eqep_position_functions, + .num_functions = ARRAY_SIZE(ti_eqep_position_functions), + .synapses = ti_eqep_position_synapses, + .num_synapses = ARRAY_SIZE(ti_eqep_position_synapses), + .ext = ti_eqep_position_ext, + .num_ext = ARRAY_SIZE(ti_eqep_position_ext), + }, +}; + +static const struct regmap_config ti_eqep_regmap32_config = { + .name = "32-bit", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = QUPRD, +}; + +static const struct regmap_config ti_eqep_regmap16_config = { + .name = "16-bit", + .reg_bits = 16, + .val_bits = 16, + .reg_stride = 2, + .max_register = QCPRDLAT, +}; + +static int ti_eqep_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ti_eqep_cnt *priv; + void __iomem *base; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap32 = devm_regmap_init_mmio(dev, base, + &ti_eqep_regmap32_config); + if (IS_ERR(priv->regmap32)) + return PTR_ERR(priv->regmap32); + + priv->regmap16 = devm_regmap_init_mmio(dev, base + 0x24, + &ti_eqep_regmap16_config); + if (IS_ERR(priv->regmap16)) + return PTR_ERR(priv->regmap16); + + priv->counter.name = dev_name(dev); + priv->counter.parent = dev; + priv->counter.ops = &ti_eqep_counter_ops; + priv->counter.counts = ti_eqep_counts; + priv->counter.num_counts = ARRAY_SIZE(ti_eqep_counts); + priv->counter.signals = ti_eqep_signals; + priv->counter.num_signals = ARRAY_SIZE(ti_eqep_signals); + priv->counter.priv = priv; + + platform_set_drvdata(pdev, priv); + + /* + * Need to make sure power is turned on. On AM33xx, this comes from the + * parent PWMSS bus driver. On AM17xx, this comes from the PSC power + * domain. + */ + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + err = counter_register(&priv->counter); + if (err < 0) { + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + return err; + } + + return 0; +} + +static int ti_eqep_remove(struct platform_device *pdev) +{ + struct ti_eqep_cnt *priv = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + counter_unregister(&priv->counter); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return 0; +} + +static const struct of_device_id ti_eqep_of_match[] = { + { .compatible = "ti,am3352-eqep", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ti_eqep_of_match); + +static struct platform_driver ti_eqep_driver = { + .probe = ti_eqep_probe, + .remove = ti_eqep_remove, + .driver = { + .name = "ti-eqep-cnt", + .of_match_table = ti_eqep_of_match, + }, +}; +module_platform_driver(ti_eqep_driver); + +MODULE_AUTHOR("David Lechner <david@lechnology.com>"); +MODULE_DESCRIPTION("TI eQEP counter driver"); +MODULE_LICENSE("GPL v2"); |