summaryrefslogtreecommitdiffstats
path: root/drivers/counter
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/counter
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/counter')
-rw-r--r--drivers/counter/104-quad-8.c1287
-rw-r--r--drivers/counter/Kconfig119
-rw-r--r--drivers/counter/Makefile17
-rw-r--r--drivers/counter/counter-chrdev.c676
-rw-r--r--drivers/counter/counter-chrdev.h14
-rw-r--r--drivers/counter/counter-core.c282
-rw-r--r--drivers/counter/counter-sysfs.c1170
-rw-r--r--drivers/counter/counter-sysfs.h13
-rw-r--r--drivers/counter/ftm-quaddec.c328
-rw-r--r--drivers/counter/intel-qep.c526
-rw-r--r--drivers/counter/interrupt-cnt.c256
-rw-r--r--drivers/counter/microchip-tcb-capture.c407
-rw-r--r--drivers/counter/stm32-lptimer-cnt.c523
-rw-r--r--drivers/counter/stm32-timer-cnt.c420
-rw-r--r--drivers/counter/ti-ecap-capture.c615
-rw-r--r--drivers/counter/ti-eqep.c459
16 files changed, 7112 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..d9cb93766
--- /dev/null
+++ b/drivers/counter/104-quad-8.c
@@ -0,0 +1,1287 @@
+// 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/io.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+
+#define QUAD8_EXTENT 32
+
+static unsigned int base[max_num_isa_dev(QUAD8_EXTENT)];
+static unsigned int num_quad8;
+module_param_hw_array(base, uint, ioport, &num_quad8, 0);
+MODULE_PARM_DESC(base, "ACCES 104-QUAD-8 base addresses");
+
+static unsigned int irq[max_num_isa_dev(QUAD8_EXTENT)];
+static unsigned int num_irq;
+module_param_hw_array(irq, uint, irq, &num_irq, 0);
+MODULE_PARM_DESC(irq, "ACCES 104-QUAD-8 interrupt line numbers");
+
+#define QUAD8_NUM_COUNTERS 8
+
+/**
+ * struct channel_reg - channel register structure
+ * @data: Count data
+ * @control: Channel flags and control
+ */
+struct channel_reg {
+ u8 data;
+ u8 control;
+};
+
+/**
+ * struct quad8_reg - device register structure
+ * @channel: quadrature counter data and control
+ * @interrupt_status: channel interrupt status
+ * @channel_oper: enable/reset counters and interrupt functions
+ * @index_interrupt: enable channel interrupts
+ * @reserved: reserved for Factory Use
+ * @index_input_levels: index signal logical input level
+ * @cable_status: differential encoder cable status
+ */
+struct quad8_reg {
+ struct channel_reg channel[QUAD8_NUM_COUNTERS];
+ u8 interrupt_status;
+ u8 channel_oper;
+ u8 index_interrupt;
+ u8 reserved[3];
+ u8 index_input_levels;
+ u8 cable_status;
+};
+
+/**
+ * struct quad8 - device private data structure
+ * @lock: lock to prevent clobbering device states during R/W ops
+ * @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
+ * @irq_trigger: array of current IRQ trigger function 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
+ * @reg: I/O address offset for the device registers
+ */
+struct quad8 {
+ spinlock_t lock;
+ 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 irq_trigger[QUAD8_NUM_COUNTERS];
+ unsigned int synchronous_mode[QUAD8_NUM_COUNTERS];
+ unsigned int index_polarity[QUAD8_NUM_COUNTERS];
+ unsigned int cable_fault_enable;
+ struct quad8_reg __iomem *reg;
+};
+
+/* 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_RESET_COUNTERS 0x01
+#define QUAD8_CHAN_OP_ENABLE_INTERRUPT_FUNC 0x04
+#define QUAD8_CMR_QUADRATURE_X1 0x08
+#define QUAD8_CMR_QUADRATURE_X2 0x10
+#define QUAD8_CMR_QUADRATURE_X4 0x18
+
+/* Each Counter is 24 bits wide */
+#define LS7267_CNTR_MAX GENMASK(23, 0)
+
+static int quad8_signal_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ enum counter_signal_level *level)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+ unsigned int state;
+
+ /* Only Index signal levels can be read */
+ if (signal->id < 16)
+ return -EINVAL;
+
+ state = ioread8(&priv->reg->index_input_levels) & BIT(signal->id - 16);
+
+ *level = (state) ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW;
+
+ return 0;
+}
+
+static int quad8_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ struct channel_reg __iomem *const chan = priv->reg->channel + count->id;
+ unsigned long irqflags;
+ int i;
+
+ *val = 0;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ /* Reset Byte Pointer; transfer Counter to Output Latch */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_CNTR_OUT,
+ &chan->control);
+
+ for (i = 0; i < 3; i++)
+ *val |= (unsigned long)ioread8(&chan->data) << (8 * i);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_count_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ struct channel_reg __iomem *const chan = priv->reg->channel + count->id;
+ unsigned long irqflags;
+ int i;
+
+ if (val > LS7267_CNTR_MAX)
+ return -ERANGE;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ /* Reset Byte Pointer */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, &chan->control);
+
+ /* Counter can only be set via Preset Register */
+ for (i = 0; i < 3; i++)
+ iowrite8(val >> (8 * i), &chan->data);
+
+ /* Transfer Preset Register to Counter */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_PRESET_CNTR, &chan->control);
+
+ /* Reset Byte Pointer */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, &chan->control);
+
+ /* Set Preset Register back to original value */
+ val = priv->preset[count->id];
+ for (i = 0; i < 3; i++)
+ iowrite8(val >> (8 * i), &chan->data);
+
+ /* Reset Borrow, Carry, Compare, and Sign flags */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, &chan->control);
+ /* Reset Error flag */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, &chan->control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static const enum counter_function quad8_count_functions_list[] = {
+ COUNTER_FUNCTION_PULSE_DIRECTION,
+ COUNTER_FUNCTION_QUADRATURE_X1_A,
+ COUNTER_FUNCTION_QUADRATURE_X2_A,
+ COUNTER_FUNCTION_QUADRATURE_X4,
+};
+
+static int quad8_function_get(const struct quad8 *const priv, const size_t id,
+ enum counter_function *const function)
+{
+ if (!priv->quadrature_mode[id]) {
+ *function = COUNTER_FUNCTION_PULSE_DIRECTION;
+ return 0;
+ }
+
+ switch (priv->quadrature_scale[id]) {
+ case 0:
+ *function = COUNTER_FUNCTION_QUADRATURE_X1_A;
+ return 0;
+ case 1:
+ *function = COUNTER_FUNCTION_QUADRATURE_X2_A;
+ return 0;
+ case 2:
+ *function = COUNTER_FUNCTION_QUADRATURE_X4;
+ return 0;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+}
+
+static int quad8_function_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned long irqflags;
+ int retval;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ retval = quad8_function_get(priv, count->id, function);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return retval;
+}
+
+static int quad8_function_write(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function function)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ 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;
+ u8 __iomem *const control = &priv->reg->channel[id].control;
+ unsigned long irqflags;
+ unsigned int mode_cfg;
+ unsigned int idr_cfg;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ mode_cfg = priv->count_mode[id] << 1;
+ idr_cfg = priv->index_polarity[id] << 1;
+
+ if (function == COUNTER_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 */
+ iowrite8(QUAD8_CTR_IDR | idr_cfg, control);
+ }
+ } else {
+ *quadrature_mode = 1;
+
+ switch (function) {
+ case COUNTER_FUNCTION_QUADRATURE_X1_A:
+ *scale = 0;
+ mode_cfg |= QUAD8_CMR_QUADRATURE_X1;
+ break;
+ case COUNTER_FUNCTION_QUADRATURE_X2_A:
+ *scale = 1;
+ mode_cfg |= QUAD8_CMR_QUADRATURE_X2;
+ break;
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ *scale = 2;
+ mode_cfg |= QUAD8_CMR_QUADRATURE_X4;
+ break;
+ default:
+ /* should never reach this path */
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+ return -EINVAL;
+ }
+ }
+
+ /* Load mode configuration to Counter Mode Register */
+ iowrite8(QUAD8_CTR_CMR | mode_cfg, control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_direction_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_count_direction *direction)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+ unsigned int ud_flag;
+ u8 __iomem *const flag_addr = &priv->reg->channel[count->id].control;
+
+ /* U/D flag: nonzero = up, zero = down */
+ ud_flag = ioread8(flag_addr) & QUAD8_FLAG_UD;
+
+ *direction = (ud_flag) ? COUNTER_COUNT_DIRECTION_FORWARD :
+ COUNTER_COUNT_DIRECTION_BACKWARD;
+
+ return 0;
+}
+
+static const enum counter_synapse_action quad8_index_actions_list[] = {
+ COUNTER_SYNAPSE_ACTION_NONE,
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+};
+
+static const enum counter_synapse_action quad8_synapse_actions_list[] = {
+ COUNTER_SYNAPSE_ACTION_NONE,
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+ COUNTER_SYNAPSE_ACTION_FALLING_EDGE,
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
+};
+
+static int quad8_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned long irqflags;
+ int err;
+ enum counter_function function;
+ 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 = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+
+ return 0;
+ }
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ /* Get Count function and direction atomically */
+ err = quad8_function_get(priv, count->id, &function);
+ if (err) {
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+ return err;
+ }
+ err = quad8_direction_read(counter, count, &direction);
+ if (err) {
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+ return err;
+ }
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ /* Default action mode */
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+
+ /* Determine action mode based on current count function mode */
+ switch (function) {
+ case COUNTER_FUNCTION_PULSE_DIRECTION:
+ if (synapse->signal->id == signal_a_id)
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ return 0;
+ case COUNTER_FUNCTION_QUADRATURE_X1_A:
+ if (synapse->signal->id == signal_a_id) {
+ if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
+ }
+ return 0;
+ case COUNTER_FUNCTION_QUADRATURE_X2_A:
+ if (synapse->signal->id == signal_a_id)
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+}
+
+enum {
+ QUAD8_EVENT_CARRY = 0,
+ QUAD8_EVENT_COMPARE = 1,
+ QUAD8_EVENT_CARRY_BORROW = 2,
+ QUAD8_EVENT_INDEX = 3,
+};
+
+static int quad8_events_configure(struct counter_device *counter)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned long irq_enabled = 0;
+ unsigned long irqflags;
+ struct counter_event_node *event_node;
+ unsigned int next_irq_trigger;
+ unsigned long ior_cfg;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ list_for_each_entry(event_node, &counter->events_list, l) {
+ switch (event_node->event) {
+ case COUNTER_EVENT_OVERFLOW:
+ next_irq_trigger = QUAD8_EVENT_CARRY;
+ break;
+ case COUNTER_EVENT_THRESHOLD:
+ next_irq_trigger = QUAD8_EVENT_COMPARE;
+ break;
+ case COUNTER_EVENT_OVERFLOW_UNDERFLOW:
+ next_irq_trigger = QUAD8_EVENT_CARRY_BORROW;
+ break;
+ case COUNTER_EVENT_INDEX:
+ next_irq_trigger = QUAD8_EVENT_INDEX;
+ break;
+ default:
+ /* should never reach this path */
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+ return -EINVAL;
+ }
+
+ /* Enable IRQ line */
+ irq_enabled |= BIT(event_node->channel);
+
+ /* Skip configuration if it is the same as previously set */
+ if (priv->irq_trigger[event_node->channel] == next_irq_trigger)
+ continue;
+
+ /* Save new IRQ function configuration */
+ priv->irq_trigger[event_node->channel] = next_irq_trigger;
+
+ /* Load configuration to I/O Control Register */
+ ior_cfg = priv->ab_enable[event_node->channel] |
+ priv->preset_enable[event_node->channel] << 1 |
+ priv->irq_trigger[event_node->channel] << 3;
+ iowrite8(QUAD8_CTR_IOR | ior_cfg,
+ &priv->reg->channel[event_node->channel].control);
+ }
+
+ iowrite8(irq_enabled, &priv->reg->index_interrupt);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_watch_validate(struct counter_device *counter,
+ const struct counter_watch *watch)
+{
+ struct counter_event_node *event_node;
+
+ if (watch->channel > QUAD8_NUM_COUNTERS - 1)
+ return -EINVAL;
+
+ switch (watch->event) {
+ case COUNTER_EVENT_OVERFLOW:
+ case COUNTER_EVENT_THRESHOLD:
+ case COUNTER_EVENT_OVERFLOW_UNDERFLOW:
+ case COUNTER_EVENT_INDEX:
+ list_for_each_entry(event_node, &counter->next_events_list, l)
+ if (watch->channel == event_node->channel &&
+ watch->event != event_node->event)
+ return -EINVAL;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct counter_ops quad8_ops = {
+ .signal_read = quad8_signal_read,
+ .count_read = quad8_count_read,
+ .count_write = quad8_count_write,
+ .function_read = quad8_function_read,
+ .function_write = quad8_function_write,
+ .action_read = quad8_action_read,
+ .events_configure = quad8_events_configure,
+ .watch_validate = quad8_watch_validate,
+};
+
+static const char *const quad8_index_polarity_modes[] = {
+ "negative",
+ "positive"
+};
+
+static int quad8_index_polarity_get(struct counter_device *counter,
+ struct counter_signal *signal,
+ u32 *index_polarity)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+ 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,
+ u32 index_polarity)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ const size_t channel_id = signal->id - 16;
+ u8 __iomem *const control = &priv->reg->channel[channel_id].control;
+ unsigned long irqflags;
+ unsigned int idr_cfg = index_polarity << 1;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ idr_cfg |= priv->synchronous_mode[channel_id];
+
+ priv->index_polarity[channel_id] = index_polarity;
+
+ /* Load Index Control configuration to Index Control Register */
+ iowrite8(QUAD8_CTR_IDR | idr_cfg, control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_polarity_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ enum counter_signal_polarity *polarity)
+{
+ int err;
+ u32 index_polarity;
+
+ err = quad8_index_polarity_get(counter, signal, &index_polarity);
+ if (err)
+ return err;
+
+ *polarity = (index_polarity) ? COUNTER_SIGNAL_POLARITY_POSITIVE :
+ COUNTER_SIGNAL_POLARITY_NEGATIVE;
+
+ return 0;
+}
+
+static int quad8_polarity_write(struct counter_device *counter,
+ struct counter_signal *signal,
+ enum counter_signal_polarity polarity)
+{
+ const u32 pol = (polarity == COUNTER_SIGNAL_POLARITY_POSITIVE) ? 1 : 0;
+
+ return quad8_index_polarity_set(counter, signal, pol);
+}
+
+static const char *const quad8_synchronous_modes[] = {
+ "non-synchronous",
+ "synchronous"
+};
+
+static int quad8_synchronous_mode_get(struct counter_device *counter,
+ struct counter_signal *signal,
+ u32 *synchronous_mode)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+ 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,
+ u32 synchronous_mode)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ const size_t channel_id = signal->id - 16;
+ u8 __iomem *const control = &priv->reg->channel[channel_id].control;
+ unsigned long irqflags;
+ unsigned int idr_cfg = synchronous_mode;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ 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]) {
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+ return -EINVAL;
+ }
+
+ priv->synchronous_mode[channel_id] = synchronous_mode;
+
+ /* Load Index Control configuration to Index Control Register */
+ iowrite8(QUAD8_CTR_IDR | idr_cfg, control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_count_floor_read(struct counter_device *counter,
+ struct counter_count *count, u64 *floor)
+{
+ /* Only a floor of 0 is supported */
+ *floor = 0;
+
+ return 0;
+}
+
+static int quad8_count_mode_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_count_mode *cnt_mode)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+
+ /* 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_write(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_count_mode cnt_mode)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned int count_mode;
+ unsigned int mode_cfg;
+ u8 __iomem *const control = &priv->reg->channel[count->id].control;
+ unsigned long irqflags;
+
+ /* Map Generic Counter count mode to 104-QUAD-8 count mode */
+ switch (cnt_mode) {
+ case COUNTER_COUNT_MODE_NORMAL:
+ count_mode = 0;
+ break;
+ case COUNTER_COUNT_MODE_RANGE_LIMIT:
+ count_mode = 1;
+ break;
+ case COUNTER_COUNT_MODE_NON_RECYCLE:
+ count_mode = 2;
+ break;
+ case COUNTER_COUNT_MODE_MODULO_N:
+ count_mode = 3;
+ break;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ priv->count_mode[count->id] = count_mode;
+
+ /* Set count mode configuration value */
+ mode_cfg = count_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 */
+ iowrite8(QUAD8_CTR_CMR | mode_cfg, control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_count_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+
+ *enable = priv->ab_enable[count->id];
+
+ return 0;
+}
+
+static int quad8_count_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ u8 __iomem *const control = &priv->reg->channel[count->id].control;
+ unsigned long irqflags;
+ unsigned int ior_cfg;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ priv->ab_enable[count->id] = enable;
+
+ ior_cfg = enable | priv->preset_enable[count->id] << 1 |
+ priv->irq_trigger[count->id] << 3;
+
+ /* Load I/O control configuration */
+ iowrite8(QUAD8_CTR_IOR | ior_cfg, control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+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_error_noise_get(struct counter_device *counter,
+ struct counter_count *count, u32 *noise_error)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+ u8 __iomem *const flag_addr = &priv->reg->channel[count->id].control;
+
+ *noise_error = !!(ioread8(flag_addr) & QUAD8_FLAG_E);
+
+ return 0;
+}
+
+static int quad8_count_preset_read(struct counter_device *counter,
+ struct counter_count *count, u64 *preset)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+
+ *preset = priv->preset[count->id];
+
+ return 0;
+}
+
+static void quad8_preset_register_set(struct quad8 *const priv, const int id,
+ const unsigned int preset)
+{
+ struct channel_reg __iomem *const chan = priv->reg->channel + id;
+ int i;
+
+ priv->preset[id] = preset;
+
+ /* Reset Byte Pointer */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, &chan->control);
+
+ /* Set Preset Register */
+ for (i = 0; i < 3; i++)
+ iowrite8(preset >> (8 * i), &chan->data);
+}
+
+static int quad8_count_preset_write(struct counter_device *counter,
+ struct counter_count *count, u64 preset)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned long irqflags;
+
+ if (preset > LS7267_CNTR_MAX)
+ return -ERANGE;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ quad8_preset_register_set(priv, count->id, preset);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_count_ceiling_read(struct counter_device *counter,
+ struct counter_count *count, u64 *ceiling)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ /* Range Limit and Modulo-N count modes use preset value as ceiling */
+ switch (priv->count_mode[count->id]) {
+ case 1:
+ case 3:
+ *ceiling = priv->preset[count->id];
+ break;
+ default:
+ *ceiling = LS7267_CNTR_MAX;
+ break;
+ }
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_count_ceiling_write(struct counter_device *counter,
+ struct counter_count *count, u64 ceiling)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned long irqflags;
+
+ if (ceiling > LS7267_CNTR_MAX)
+ return -ERANGE;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ /* 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);
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+ return 0;
+ }
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return -EINVAL;
+}
+
+static int quad8_count_preset_enable_read(struct counter_device *counter,
+ struct counter_count *count,
+ u8 *preset_enable)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+
+ *preset_enable = !priv->preset_enable[count->id];
+
+ return 0;
+}
+
+static int quad8_count_preset_enable_write(struct counter_device *counter,
+ struct counter_count *count,
+ u8 preset_enable)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ u8 __iomem *const control = &priv->reg->channel[count->id].control;
+ unsigned long irqflags;
+ unsigned int ior_cfg;
+
+ /* Preset enable is active low in Input/Output Control register */
+ preset_enable = !preset_enable;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ priv->preset_enable[count->id] = preset_enable;
+
+ ior_cfg = priv->ab_enable[count->id] | preset_enable << 1 |
+ priv->irq_trigger[count->id] << 3;
+
+ /* Load I/O control configuration to Input / Output Control Register */
+ iowrite8(QUAD8_CTR_IOR | ior_cfg, control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_signal_cable_fault_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ u8 *cable_fault)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ const size_t channel_id = signal->id / 2;
+ unsigned long irqflags;
+ bool disabled;
+ unsigned int status;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ disabled = !(priv->cable_fault_enable & BIT(channel_id));
+
+ if (disabled) {
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+ return -EINVAL;
+ }
+
+ /* Logic 0 = cable fault */
+ status = ioread8(&priv->reg->cable_status);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ /* Mask respective channel and invert logic */
+ *cable_fault = !(status & BIT(channel_id));
+
+ return 0;
+}
+
+static int quad8_signal_cable_fault_enable_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ u8 *enable)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+ const size_t channel_id = signal->id / 2;
+
+ *enable = !!(priv->cable_fault_enable & BIT(channel_id));
+
+ return 0;
+}
+
+static int quad8_signal_cable_fault_enable_write(struct counter_device *counter,
+ struct counter_signal *signal,
+ u8 enable)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ const size_t channel_id = signal->id / 2;
+ unsigned long irqflags;
+ unsigned int cable_fault_enable;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ 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;
+
+ iowrite8(cable_fault_enable, &priv->reg->cable_status);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static int quad8_signal_fck_prescaler_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ u8 *prescaler)
+{
+ const struct quad8 *const priv = counter_priv(counter);
+
+ *prescaler = priv->fck_prescaler[signal->id / 2];
+
+ return 0;
+}
+
+static int quad8_signal_fck_prescaler_write(struct counter_device *counter,
+ struct counter_signal *signal,
+ u8 prescaler)
+{
+ struct quad8 *const priv = counter_priv(counter);
+ const size_t channel_id = signal->id / 2;
+ struct channel_reg __iomem *const chan = priv->reg->channel + channel_id;
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&priv->lock, irqflags);
+
+ priv->fck_prescaler[channel_id] = prescaler;
+
+ /* Reset Byte Pointer */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, &chan->control);
+
+ /* Set filter clock factor */
+ iowrite8(prescaler, &chan->data);
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_PRESET_PSC,
+ &chan->control);
+
+ spin_unlock_irqrestore(&priv->lock, irqflags);
+
+ return 0;
+}
+
+static struct counter_comp quad8_signal_ext[] = {
+ COUNTER_COMP_SIGNAL_BOOL("cable_fault", quad8_signal_cable_fault_read,
+ NULL),
+ COUNTER_COMP_SIGNAL_BOOL("cable_fault_enable",
+ quad8_signal_cable_fault_enable_read,
+ quad8_signal_cable_fault_enable_write),
+ COUNTER_COMP_SIGNAL_U8("filter_clock_prescaler",
+ quad8_signal_fck_prescaler_read,
+ quad8_signal_fck_prescaler_write)
+};
+
+static const enum counter_signal_polarity quad8_polarities[] = {
+ COUNTER_SIGNAL_POLARITY_POSITIVE,
+ COUNTER_SIGNAL_POLARITY_NEGATIVE,
+};
+
+static DEFINE_COUNTER_AVAILABLE(quad8_polarity_available, quad8_polarities);
+
+static DEFINE_COUNTER_ENUM(quad8_index_pol_enum, quad8_index_polarity_modes);
+static DEFINE_COUNTER_ENUM(quad8_synch_mode_enum, quad8_synchronous_modes);
+
+static struct counter_comp quad8_index_ext[] = {
+ COUNTER_COMP_SIGNAL_ENUM("index_polarity", quad8_index_polarity_get,
+ quad8_index_polarity_set,
+ quad8_index_pol_enum),
+ COUNTER_COMP_POLARITY(quad8_polarity_read, quad8_polarity_write,
+ quad8_polarity_available),
+ COUNTER_COMP_SIGNAL_ENUM("synchronous_mode", quad8_synchronous_mode_get,
+ quad8_synchronous_mode_set,
+ quad8_synch_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 enum counter_count_mode quad8_cnt_modes[] = {
+ COUNTER_COUNT_MODE_NORMAL,
+ COUNTER_COUNT_MODE_RANGE_LIMIT,
+ COUNTER_COUNT_MODE_NON_RECYCLE,
+ COUNTER_COUNT_MODE_MODULO_N,
+};
+
+static DEFINE_COUNTER_AVAILABLE(quad8_count_mode_available, quad8_cnt_modes);
+
+static DEFINE_COUNTER_ENUM(quad8_error_noise_enum, quad8_noise_error_states);
+
+static struct counter_comp quad8_count_ext[] = {
+ COUNTER_COMP_CEILING(quad8_count_ceiling_read,
+ quad8_count_ceiling_write),
+ COUNTER_COMP_FLOOR(quad8_count_floor_read, NULL),
+ COUNTER_COMP_COUNT_MODE(quad8_count_mode_read, quad8_count_mode_write,
+ quad8_count_mode_available),
+ COUNTER_COMP_DIRECTION(quad8_direction_read),
+ COUNTER_COMP_ENABLE(quad8_count_enable_read, quad8_count_enable_write),
+ COUNTER_COMP_COUNT_ENUM("error_noise", quad8_error_noise_get, NULL,
+ quad8_error_noise_enum),
+ COUNTER_COMP_PRESET(quad8_count_preset_read, quad8_count_preset_write),
+ COUNTER_COMP_PRESET_ENABLE(quad8_count_preset_enable_read,
+ 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 irqreturn_t quad8_irq_handler(int irq, void *private)
+{
+ struct counter_device *counter = private;
+ struct quad8 *const priv = counter_priv(counter);
+ unsigned long irq_status;
+ unsigned long channel;
+ u8 event;
+
+ irq_status = ioread8(&priv->reg->interrupt_status);
+ if (!irq_status)
+ return IRQ_NONE;
+
+ for_each_set_bit(channel, &irq_status, QUAD8_NUM_COUNTERS) {
+ switch (priv->irq_trigger[channel]) {
+ case QUAD8_EVENT_CARRY:
+ event = COUNTER_EVENT_OVERFLOW;
+ break;
+ case QUAD8_EVENT_COMPARE:
+ event = COUNTER_EVENT_THRESHOLD;
+ break;
+ case QUAD8_EVENT_CARRY_BORROW:
+ event = COUNTER_EVENT_OVERFLOW_UNDERFLOW;
+ break;
+ case QUAD8_EVENT_INDEX:
+ event = COUNTER_EVENT_INDEX;
+ break;
+ default:
+ /* should never reach this path */
+ WARN_ONCE(true, "invalid interrupt trigger function %u configured for channel %lu\n",
+ priv->irq_trigger[channel], channel);
+ continue;
+ }
+
+ counter_push_event(counter, event, channel);
+ }
+
+ /* Clear pending interrupts on device */
+ iowrite8(QUAD8_CHAN_OP_ENABLE_INTERRUPT_FUNC, &priv->reg->channel_oper);
+
+ return IRQ_HANDLED;
+}
+
+static void quad8_init_counter(struct channel_reg __iomem *const chan)
+{
+ unsigned long i;
+
+ /* Reset Byte Pointer */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, &chan->control);
+ /* Reset filter clock factor */
+ iowrite8(0, &chan->data);
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP | QUAD8_RLD_PRESET_PSC,
+ &chan->control);
+ /* Reset Byte Pointer */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_BP, &chan->control);
+ /* Reset Preset Register */
+ for (i = 0; i < 3; i++)
+ iowrite8(0x00, &chan->data);
+ /* Reset Borrow, Carry, Compare, and Sign flags */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_FLAGS, &chan->control);
+ /* Reset Error flag */
+ iowrite8(QUAD8_CTR_RLD | QUAD8_RLD_RESET_E, &chan->control);
+ /* Binary encoding; Normal count; non-quadrature mode */
+ iowrite8(QUAD8_CTR_CMR, &chan->control);
+ /* Disable A and B inputs; preset on index; FLG1 as Carry */
+ iowrite8(QUAD8_CTR_IOR, &chan->control);
+ /* Disable index function; negative index polarity */
+ iowrite8(QUAD8_CTR_IDR, &chan->control);
+}
+
+static int quad8_probe(struct device *dev, unsigned int id)
+{
+ struct counter_device *counter;
+ struct quad8 *priv;
+ unsigned long i;
+ 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;
+ }
+
+ counter = devm_counter_alloc(dev, sizeof(*priv));
+ if (!counter)
+ return -ENOMEM;
+ priv = counter_priv(counter);
+
+ priv->reg = devm_ioport_map(dev, base[id], QUAD8_EXTENT);
+ if (!priv->reg)
+ return -ENOMEM;
+
+ /* Initialize Counter device and driver data */
+ counter->name = dev_name(dev);
+ counter->parent = dev;
+ counter->ops = &quad8_ops;
+ counter->counts = quad8_counts;
+ counter->num_counts = ARRAY_SIZE(quad8_counts);
+ counter->signals = quad8_signals;
+ counter->num_signals = ARRAY_SIZE(quad8_signals);
+
+ spin_lock_init(&priv->lock);
+
+ /* Reset Index/Interrupt Register */
+ iowrite8(0x00, &priv->reg->index_interrupt);
+ /* Reset all counters and disable interrupt function */
+ iowrite8(QUAD8_CHAN_OP_RESET_COUNTERS, &priv->reg->channel_oper);
+ /* Set initial configuration for all counters */
+ for (i = 0; i < QUAD8_NUM_COUNTERS; i++)
+ quad8_init_counter(priv->reg->channel + i);
+ /* Disable Differential Encoder Cable Status for all channels */
+ iowrite8(0xFF, &priv->reg->cable_status);
+ /* Enable all counters and enable interrupt function */
+ iowrite8(QUAD8_CHAN_OP_ENABLE_INTERRUPT_FUNC, &priv->reg->channel_oper);
+
+ err = devm_request_irq(&counter->dev, irq[id], quad8_irq_handler,
+ IRQF_SHARED, counter->name, counter);
+ if (err)
+ return err;
+
+ err = devm_counter_add(dev, counter);
+ if (err < 0)
+ return dev_err_probe(dev, err, "Failed to add counter\n");
+
+ return 0;
+}
+
+static struct isa_driver quad8_driver = {
+ .probe = quad8_probe,
+ .driver = {
+ .name = "104-quad-8"
+ }
+};
+
+module_isa_driver_with_irq(quad8_driver, num_quad8, num_irq);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("ACCES 104-QUAD-8 driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/Kconfig b/drivers/counter/Kconfig
new file mode 100644
index 000000000..d388bf26f
--- /dev/null
+++ b/drivers/counter/Kconfig
@@ -0,0 +1,119 @@
+# 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) || COMPILE_TEST
+ 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.
+
+ The base port addresses for the devices may be configured via the base
+ array module parameter. The interrupt line numbers for the devices may
+ be configured via the irq array module parameter.
+
+config INTERRUPT_CNT
+ tristate "Interrupt counter driver"
+ depends on GPIOLIB
+ help
+ Select this option to enable interrupt counter driver. Any interrupt
+ source can be used by this driver as the event source.
+
+ To compile this driver as a module, choose M here: the
+ module will be called interrupt-cnt.
+
+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.
+
+config INTEL_QEP
+ tristate "Intel Quadrature Encoder Peripheral driver"
+ depends on PCI
+ help
+ Select this option to enable the Intel Quadrature Encoder Peripheral
+ driver.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel-qep.
+
+config TI_ECAP_CAPTURE
+ tristate "TI eCAP capture driver"
+ depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
+ depends on HAS_IOMEM
+ select REGMAP_MMIO
+ help
+ Select this option to enable the Texas Instruments Enhanced Capture
+ (eCAP) driver in input mode.
+
+ It can be used to timestamp events (falling/rising edges) detected
+ on ECAP input signal.
+
+ To compile this driver as a module, choose M here: the module
+ will be called ti-ecap-capture.
+
+endif # COUNTER
diff --git a/drivers/counter/Makefile b/drivers/counter/Makefile
new file mode 100644
index 000000000..b9a369e0d
--- /dev/null
+++ b/drivers/counter/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for Counter devices
+#
+
+obj-$(CONFIG_COUNTER) += counter.o
+counter-y := counter-core.o counter-sysfs.o counter-chrdev.o
+
+obj-$(CONFIG_104_QUAD_8) += 104-quad-8.o
+obj-$(CONFIG_INTERRUPT_CNT) += interrupt-cnt.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
+obj-$(CONFIG_INTEL_QEP) += intel-qep.o
+obj-$(CONFIG_TI_ECAP_CAPTURE) += ti-ecap-capture.o
diff --git a/drivers/counter/counter-chrdev.c b/drivers/counter/counter-chrdev.c
new file mode 100644
index 000000000..afc94d006
--- /dev/null
+++ b/drivers/counter/counter-chrdev.c
@@ -0,0 +1,676 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic Counter character device interface
+ * Copyright (C) 2020 William Breathitt Gray
+ */
+#include <linux/cdev.h>
+#include <linux/counter.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/fs.h>
+#include <linux/kfifo.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/nospec.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/timekeeping.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#include "counter-chrdev.h"
+
+struct counter_comp_node {
+ struct list_head l;
+ struct counter_component component;
+ struct counter_comp comp;
+ void *parent;
+};
+
+#define counter_comp_read_is_equal(a, b) \
+ (a.action_read == b.action_read || \
+ a.device_u8_read == b.device_u8_read || \
+ a.count_u8_read == b.count_u8_read || \
+ a.signal_u8_read == b.signal_u8_read || \
+ a.device_u32_read == b.device_u32_read || \
+ a.count_u32_read == b.count_u32_read || \
+ a.signal_u32_read == b.signal_u32_read || \
+ a.device_u64_read == b.device_u64_read || \
+ a.count_u64_read == b.count_u64_read || \
+ a.signal_u64_read == b.signal_u64_read || \
+ a.signal_array_u32_read == b.signal_array_u32_read || \
+ a.device_array_u64_read == b.device_array_u64_read || \
+ a.count_array_u64_read == b.count_array_u64_read || \
+ a.signal_array_u64_read == b.signal_array_u64_read)
+
+#define counter_comp_read_is_set(comp) \
+ (comp.action_read || \
+ comp.device_u8_read || \
+ comp.count_u8_read || \
+ comp.signal_u8_read || \
+ comp.device_u32_read || \
+ comp.count_u32_read || \
+ comp.signal_u32_read || \
+ comp.device_u64_read || \
+ comp.count_u64_read || \
+ comp.signal_u64_read || \
+ comp.signal_array_u32_read || \
+ comp.device_array_u64_read || \
+ comp.count_array_u64_read || \
+ comp.signal_array_u64_read)
+
+static ssize_t counter_chrdev_read(struct file *filp, char __user *buf,
+ size_t len, loff_t *f_ps)
+{
+ struct counter_device *const counter = filp->private_data;
+ int err;
+ unsigned int copied;
+
+ if (!counter->ops)
+ return -ENODEV;
+
+ if (len < sizeof(struct counter_event))
+ return -EINVAL;
+
+ do {
+ if (kfifo_is_empty(&counter->events)) {
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ err = wait_event_interruptible(counter->events_wait,
+ !kfifo_is_empty(&counter->events) ||
+ !counter->ops);
+ if (err < 0)
+ return err;
+ if (!counter->ops)
+ return -ENODEV;
+ }
+
+ if (mutex_lock_interruptible(&counter->events_out_lock))
+ return -ERESTARTSYS;
+ err = kfifo_to_user(&counter->events, buf, len, &copied);
+ mutex_unlock(&counter->events_out_lock);
+ if (err < 0)
+ return err;
+ } while (!copied);
+
+ return copied;
+}
+
+static __poll_t counter_chrdev_poll(struct file *filp,
+ struct poll_table_struct *pollt)
+{
+ struct counter_device *const counter = filp->private_data;
+ __poll_t events = 0;
+
+ if (!counter->ops)
+ return events;
+
+ poll_wait(filp, &counter->events_wait, pollt);
+
+ if (!kfifo_is_empty(&counter->events))
+ events = EPOLLIN | EPOLLRDNORM;
+
+ return events;
+}
+
+static void counter_events_list_free(struct list_head *const events_list)
+{
+ struct counter_event_node *p, *n;
+ struct counter_comp_node *q, *o;
+
+ list_for_each_entry_safe(p, n, events_list, l) {
+ /* Free associated component nodes */
+ list_for_each_entry_safe(q, o, &p->comp_list, l) {
+ list_del(&q->l);
+ kfree(q);
+ }
+
+ /* Free event node */
+ list_del(&p->l);
+ kfree(p);
+ }
+}
+
+static int counter_set_event_node(struct counter_device *const counter,
+ struct counter_watch *const watch,
+ const struct counter_comp_node *const cfg)
+{
+ struct counter_event_node *event_node;
+ int err = 0;
+ struct counter_comp_node *comp_node;
+
+ /* Search for event in the list */
+ list_for_each_entry(event_node, &counter->next_events_list, l)
+ if (event_node->event == watch->event &&
+ event_node->channel == watch->channel)
+ break;
+
+ /* If event is not already in the list */
+ if (&event_node->l == &counter->next_events_list) {
+ /* Allocate new event node */
+ event_node = kmalloc(sizeof(*event_node), GFP_KERNEL);
+ if (!event_node)
+ return -ENOMEM;
+
+ /* Configure event node and add to the list */
+ event_node->event = watch->event;
+ event_node->channel = watch->channel;
+ INIT_LIST_HEAD(&event_node->comp_list);
+ list_add(&event_node->l, &counter->next_events_list);
+ }
+
+ /* Check if component watch has already been set before */
+ list_for_each_entry(comp_node, &event_node->comp_list, l)
+ if (comp_node->parent == cfg->parent &&
+ counter_comp_read_is_equal(comp_node->comp, cfg->comp)) {
+ err = -EINVAL;
+ goto exit_free_event_node;
+ }
+
+ /* Allocate component node */
+ comp_node = kmalloc(sizeof(*comp_node), GFP_KERNEL);
+ if (!comp_node) {
+ err = -ENOMEM;
+ goto exit_free_event_node;
+ }
+ *comp_node = *cfg;
+
+ /* Add component node to event node */
+ list_add_tail(&comp_node->l, &event_node->comp_list);
+
+exit_free_event_node:
+ /* Free event node if no one else is watching */
+ if (list_empty(&event_node->comp_list)) {
+ list_del(&event_node->l);
+ kfree(event_node);
+ }
+
+ return err;
+}
+
+static int counter_enable_events(struct counter_device *const counter)
+{
+ unsigned long flags;
+ int err = 0;
+
+ mutex_lock(&counter->n_events_list_lock);
+ spin_lock_irqsave(&counter->events_list_lock, flags);
+
+ counter_events_list_free(&counter->events_list);
+ list_replace_init(&counter->next_events_list,
+ &counter->events_list);
+
+ if (counter->ops->events_configure)
+ err = counter->ops->events_configure(counter);
+
+ spin_unlock_irqrestore(&counter->events_list_lock, flags);
+ mutex_unlock(&counter->n_events_list_lock);
+
+ return err;
+}
+
+static int counter_disable_events(struct counter_device *const counter)
+{
+ unsigned long flags;
+ int err = 0;
+
+ spin_lock_irqsave(&counter->events_list_lock, flags);
+
+ counter_events_list_free(&counter->events_list);
+
+ if (counter->ops->events_configure)
+ err = counter->ops->events_configure(counter);
+
+ spin_unlock_irqrestore(&counter->events_list_lock, flags);
+
+ mutex_lock(&counter->n_events_list_lock);
+
+ counter_events_list_free(&counter->next_events_list);
+
+ mutex_unlock(&counter->n_events_list_lock);
+
+ return err;
+}
+
+static int counter_get_ext(const struct counter_comp *const ext,
+ const size_t num_ext, const size_t component_id,
+ size_t *const ext_idx, size_t *const id)
+{
+ struct counter_array *element;
+
+ *id = 0;
+ for (*ext_idx = 0; *ext_idx < num_ext; (*ext_idx)++) {
+ if (*id == component_id)
+ return 0;
+
+ if (ext[*ext_idx].type == COUNTER_COMP_ARRAY) {
+ element = ext[*ext_idx].priv;
+
+ if (component_id - *id < element->length)
+ return 0;
+
+ *id += element->length;
+ } else
+ (*id)++;
+ }
+
+ return -EINVAL;
+}
+
+static int counter_add_watch(struct counter_device *const counter,
+ const unsigned long arg)
+{
+ void __user *const uwatch = (void __user *)arg;
+ struct counter_watch watch;
+ struct counter_comp_node comp_node = {};
+ size_t parent, id;
+ struct counter_comp *ext;
+ size_t num_ext;
+ size_t ext_idx, ext_id;
+ int err = 0;
+
+ if (copy_from_user(&watch, uwatch, sizeof(watch)))
+ return -EFAULT;
+
+ if (watch.component.type == COUNTER_COMPONENT_NONE)
+ goto no_component;
+
+ parent = watch.component.parent;
+
+ /* Configure parent component info for comp node */
+ switch (watch.component.scope) {
+ case COUNTER_SCOPE_DEVICE:
+ ext = counter->ext;
+ num_ext = counter->num_ext;
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ if (parent >= counter->num_signals)
+ return -EINVAL;
+ parent = array_index_nospec(parent, counter->num_signals);
+
+ comp_node.parent = counter->signals + parent;
+
+ ext = counter->signals[parent].ext;
+ num_ext = counter->signals[parent].num_ext;
+ break;
+ case COUNTER_SCOPE_COUNT:
+ if (parent >= counter->num_counts)
+ return -EINVAL;
+ parent = array_index_nospec(parent, counter->num_counts);
+
+ comp_node.parent = counter->counts + parent;
+
+ ext = counter->counts[parent].ext;
+ num_ext = counter->counts[parent].num_ext;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ id = watch.component.id;
+
+ /* Configure component info for comp node */
+ switch (watch.component.type) {
+ case COUNTER_COMPONENT_SIGNAL:
+ if (watch.component.scope != COUNTER_SCOPE_SIGNAL)
+ return -EINVAL;
+
+ comp_node.comp.type = COUNTER_COMP_SIGNAL_LEVEL;
+ comp_node.comp.signal_u32_read = counter->ops->signal_read;
+ break;
+ case COUNTER_COMPONENT_COUNT:
+ if (watch.component.scope != COUNTER_SCOPE_COUNT)
+ return -EINVAL;
+
+ comp_node.comp.type = COUNTER_COMP_U64;
+ comp_node.comp.count_u64_read = counter->ops->count_read;
+ break;
+ case COUNTER_COMPONENT_FUNCTION:
+ if (watch.component.scope != COUNTER_SCOPE_COUNT)
+ return -EINVAL;
+
+ comp_node.comp.type = COUNTER_COMP_FUNCTION;
+ comp_node.comp.count_u32_read = counter->ops->function_read;
+ break;
+ case COUNTER_COMPONENT_SYNAPSE_ACTION:
+ if (watch.component.scope != COUNTER_SCOPE_COUNT)
+ return -EINVAL;
+ if (id >= counter->counts[parent].num_synapses)
+ return -EINVAL;
+ id = array_index_nospec(id, counter->counts[parent].num_synapses);
+
+ comp_node.comp.type = COUNTER_COMP_SYNAPSE_ACTION;
+ comp_node.comp.action_read = counter->ops->action_read;
+ comp_node.comp.priv = counter->counts[parent].synapses + id;
+ break;
+ case COUNTER_COMPONENT_EXTENSION:
+ err = counter_get_ext(ext, num_ext, id, &ext_idx, &ext_id);
+ if (err < 0)
+ return err;
+
+ comp_node.comp = ext[ext_idx];
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (!counter_comp_read_is_set(comp_node.comp))
+ return -EOPNOTSUPP;
+
+no_component:
+ mutex_lock(&counter->n_events_list_lock);
+
+ if (counter->ops->watch_validate) {
+ err = counter->ops->watch_validate(counter, &watch);
+ if (err < 0)
+ goto err_exit;
+ }
+
+ comp_node.component = watch.component;
+
+ err = counter_set_event_node(counter, &watch, &comp_node);
+
+err_exit:
+ mutex_unlock(&counter->n_events_list_lock);
+
+ return err;
+}
+
+static long counter_chrdev_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct counter_device *const counter = filp->private_data;
+ int ret = -ENODEV;
+
+ mutex_lock(&counter->ops_exist_lock);
+
+ if (!counter->ops)
+ goto out_unlock;
+
+ switch (cmd) {
+ case COUNTER_ADD_WATCH_IOCTL:
+ ret = counter_add_watch(counter, arg);
+ break;
+ case COUNTER_ENABLE_EVENTS_IOCTL:
+ ret = counter_enable_events(counter);
+ break;
+ case COUNTER_DISABLE_EVENTS_IOCTL:
+ ret = counter_disable_events(counter);
+ break;
+ default:
+ ret = -ENOIOCTLCMD;
+ break;
+ }
+
+out_unlock:
+ mutex_unlock(&counter->ops_exist_lock);
+
+ return ret;
+}
+
+static int counter_chrdev_open(struct inode *inode, struct file *filp)
+{
+ struct counter_device *const counter = container_of(inode->i_cdev,
+ typeof(*counter),
+ chrdev);
+
+ get_device(&counter->dev);
+ filp->private_data = counter;
+
+ return nonseekable_open(inode, filp);
+}
+
+static int counter_chrdev_release(struct inode *inode, struct file *filp)
+{
+ struct counter_device *const counter = filp->private_data;
+ int ret = 0;
+
+ mutex_lock(&counter->ops_exist_lock);
+
+ if (!counter->ops) {
+ /* Free any lingering held memory */
+ counter_events_list_free(&counter->events_list);
+ counter_events_list_free(&counter->next_events_list);
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+
+ ret = counter_disable_events(counter);
+ if (ret < 0) {
+ mutex_unlock(&counter->ops_exist_lock);
+ return ret;
+ }
+
+out_unlock:
+ mutex_unlock(&counter->ops_exist_lock);
+
+ put_device(&counter->dev);
+
+ return ret;
+}
+
+static const struct file_operations counter_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = counter_chrdev_read,
+ .poll = counter_chrdev_poll,
+ .unlocked_ioctl = counter_chrdev_ioctl,
+ .open = counter_chrdev_open,
+ .release = counter_chrdev_release,
+};
+
+int counter_chrdev_add(struct counter_device *const counter)
+{
+ /* Initialize Counter events lists */
+ INIT_LIST_HEAD(&counter->events_list);
+ INIT_LIST_HEAD(&counter->next_events_list);
+ spin_lock_init(&counter->events_list_lock);
+ mutex_init(&counter->n_events_list_lock);
+ init_waitqueue_head(&counter->events_wait);
+ spin_lock_init(&counter->events_in_lock);
+ mutex_init(&counter->events_out_lock);
+
+ /* Initialize character device */
+ cdev_init(&counter->chrdev, &counter_fops);
+
+ /* Allocate Counter events queue */
+ return kfifo_alloc(&counter->events, 64, GFP_KERNEL);
+}
+
+void counter_chrdev_remove(struct counter_device *const counter)
+{
+ kfifo_free(&counter->events);
+}
+
+static int counter_get_array_data(struct counter_device *const counter,
+ const enum counter_scope scope,
+ void *const parent,
+ const struct counter_comp *const comp,
+ const size_t idx, u64 *const value)
+{
+ const struct counter_array *const element = comp->priv;
+ u32 value_u32 = 0;
+ int ret;
+
+ switch (element->type) {
+ case COUNTER_COMP_SIGNAL_POLARITY:
+ if (scope != COUNTER_SCOPE_SIGNAL)
+ return -EINVAL;
+ ret = comp->signal_array_u32_read(counter, parent, idx,
+ &value_u32);
+ *value = value_u32;
+ return ret;
+ case COUNTER_COMP_U64:
+ switch (scope) {
+ case COUNTER_SCOPE_DEVICE:
+ return comp->device_array_u64_read(counter, idx, value);
+ case COUNTER_SCOPE_SIGNAL:
+ return comp->signal_array_u64_read(counter, parent, idx,
+ value);
+ case COUNTER_SCOPE_COUNT:
+ return comp->count_array_u64_read(counter, parent, idx,
+ value);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int counter_get_data(struct counter_device *const counter,
+ const struct counter_comp_node *const comp_node,
+ u64 *const value)
+{
+ const struct counter_comp *const comp = &comp_node->comp;
+ const enum counter_scope scope = comp_node->component.scope;
+ const size_t id = comp_node->component.id;
+ struct counter_signal *const signal = comp_node->parent;
+ struct counter_count *const count = comp_node->parent;
+ u8 value_u8 = 0;
+ u32 value_u32 = 0;
+ const struct counter_comp *ext;
+ size_t num_ext;
+ size_t ext_idx, ext_id;
+ int ret;
+
+ if (comp_node->component.type == COUNTER_COMPONENT_NONE)
+ return 0;
+
+ switch (comp->type) {
+ case COUNTER_COMP_U8:
+ case COUNTER_COMP_BOOL:
+ switch (scope) {
+ case COUNTER_SCOPE_DEVICE:
+ ret = comp->device_u8_read(counter, &value_u8);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ ret = comp->signal_u8_read(counter, signal, &value_u8);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ ret = comp->count_u8_read(counter, count, &value_u8);
+ break;
+ default:
+ return -EINVAL;
+ }
+ *value = value_u8;
+ return ret;
+ case COUNTER_COMP_SIGNAL_LEVEL:
+ case COUNTER_COMP_FUNCTION:
+ case COUNTER_COMP_ENUM:
+ case COUNTER_COMP_COUNT_DIRECTION:
+ case COUNTER_COMP_COUNT_MODE:
+ case COUNTER_COMP_SIGNAL_POLARITY:
+ switch (scope) {
+ case COUNTER_SCOPE_DEVICE:
+ ret = comp->device_u32_read(counter, &value_u32);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ ret = comp->signal_u32_read(counter, signal,
+ &value_u32);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ ret = comp->count_u32_read(counter, count, &value_u32);
+ break;
+ default:
+ return -EINVAL;
+ }
+ *value = value_u32;
+ return ret;
+ case COUNTER_COMP_U64:
+ switch (scope) {
+ case COUNTER_SCOPE_DEVICE:
+ return comp->device_u64_read(counter, value);
+ case COUNTER_SCOPE_SIGNAL:
+ return comp->signal_u64_read(counter, signal, value);
+ case COUNTER_SCOPE_COUNT:
+ return comp->count_u64_read(counter, count, value);
+ default:
+ return -EINVAL;
+ }
+ case COUNTER_COMP_SYNAPSE_ACTION:
+ ret = comp->action_read(counter, count, comp->priv, &value_u32);
+ *value = value_u32;
+ return ret;
+ case COUNTER_COMP_ARRAY:
+ switch (scope) {
+ case COUNTER_SCOPE_DEVICE:
+ ext = counter->ext;
+ num_ext = counter->num_ext;
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ ext = signal->ext;
+ num_ext = signal->num_ext;
+ break;
+ case COUNTER_SCOPE_COUNT:
+ ext = count->ext;
+ num_ext = count->num_ext;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret = counter_get_ext(ext, num_ext, id, &ext_idx, &ext_id);
+ if (ret < 0)
+ return ret;
+
+ return counter_get_array_data(counter, scope, comp_node->parent,
+ comp, id - ext_id, value);
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * counter_push_event - queue event for userspace reading
+ * @counter: pointer to Counter structure
+ * @event: triggered event
+ * @channel: event channel
+ *
+ * Note: If no one is watching for the respective event, it is silently
+ * discarded.
+ */
+void counter_push_event(struct counter_device *const counter, const u8 event,
+ const u8 channel)
+{
+ struct counter_event ev;
+ unsigned int copied = 0;
+ unsigned long flags;
+ struct counter_event_node *event_node;
+ struct counter_comp_node *comp_node;
+
+ ev.timestamp = ktime_get_ns();
+ ev.watch.event = event;
+ ev.watch.channel = channel;
+
+ /* Could be in an interrupt context, so use a spin lock */
+ spin_lock_irqsave(&counter->events_list_lock, flags);
+
+ /* Search for event in the list */
+ list_for_each_entry(event_node, &counter->events_list, l)
+ if (event_node->event == event &&
+ event_node->channel == channel)
+ break;
+
+ /* If event is not in the list */
+ if (&event_node->l == &counter->events_list)
+ goto exit_early;
+
+ /* Read and queue relevant comp for userspace */
+ list_for_each_entry(comp_node, &event_node->comp_list, l) {
+ ev.watch.component = comp_node->component;
+ ev.status = -counter_get_data(counter, comp_node, &ev.value);
+
+ copied += kfifo_in_spinlocked_noirqsave(&counter->events, &ev,
+ 1, &counter->events_in_lock);
+ }
+
+exit_early:
+ spin_unlock_irqrestore(&counter->events_list_lock, flags);
+
+ if (copied)
+ wake_up_poll(&counter->events_wait, EPOLLIN);
+}
+EXPORT_SYMBOL_NS_GPL(counter_push_event, COUNTER);
diff --git a/drivers/counter/counter-chrdev.h b/drivers/counter/counter-chrdev.h
new file mode 100644
index 000000000..5529d1670
--- /dev/null
+++ b/drivers/counter/counter-chrdev.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Counter character device interface
+ * Copyright (C) 2020 William Breathitt Gray
+ */
+#ifndef _COUNTER_CHRDEV_H_
+#define _COUNTER_CHRDEV_H_
+
+#include <linux/counter.h>
+
+int counter_chrdev_add(struct counter_device *const counter);
+void counter_chrdev_remove(struct counter_device *const counter);
+
+#endif /* _COUNTER_CHRDEV_H_ */
diff --git a/drivers/counter/counter-core.c b/drivers/counter/counter-core.c
new file mode 100644
index 000000000..09c77afb3
--- /dev/null
+++ b/drivers/counter/counter-core.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic Counter interface
+ * Copyright (C) 2020 William Breathitt Gray
+ */
+#include <linux/cdev.h>
+#include <linux/counter.h>
+#include <linux/device.h>
+#include <linux/device/bus.h>
+#include <linux/export.h>
+#include <linux/fs.h>
+#include <linux/gfp.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/kdev_t.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include "counter-chrdev.h"
+#include "counter-sysfs.h"
+
+#define COUNTER_NAME "counter"
+
+/* Provides a unique ID for each counter device */
+static DEFINE_IDA(counter_ida);
+
+struct counter_device_allochelper {
+ struct counter_device counter;
+
+ /*
+ * This is cache line aligned to ensure private data behaves like if it
+ * were kmalloced separately.
+ */
+ unsigned long privdata[] ____cacheline_aligned;
+};
+
+static void counter_device_release(struct device *dev)
+{
+ struct counter_device *const counter =
+ container_of(dev, struct counter_device, dev);
+
+ counter_chrdev_remove(counter);
+ ida_free(&counter_ida, dev->id);
+
+ kfree(container_of(counter, struct counter_device_allochelper, counter));
+}
+
+static struct device_type counter_device_type = {
+ .name = "counter_device",
+ .release = counter_device_release,
+};
+
+static struct bus_type counter_bus_type = {
+ .name = "counter",
+ .dev_name = "counter",
+};
+
+static dev_t counter_devt;
+
+/**
+ * counter_priv - access counter device private data
+ * @counter: counter device
+ *
+ * Get the counter device private data
+ */
+void *counter_priv(const struct counter_device *const counter)
+{
+ struct counter_device_allochelper *ch =
+ container_of(counter, struct counter_device_allochelper, counter);
+
+ return &ch->privdata;
+}
+EXPORT_SYMBOL_NS_GPL(counter_priv, COUNTER);
+
+/**
+ * counter_alloc - allocate a counter_device
+ * @sizeof_priv: size of the driver private data
+ *
+ * This is part one of counter registration. The structure is allocated
+ * dynamically to ensure the right lifetime for the embedded struct device.
+ *
+ * If this succeeds, call counter_put() to get rid of the counter_device again.
+ */
+struct counter_device *counter_alloc(size_t sizeof_priv)
+{
+ struct counter_device_allochelper *ch;
+ struct counter_device *counter;
+ struct device *dev;
+ int err;
+
+ ch = kzalloc(sizeof(*ch) + sizeof_priv, GFP_KERNEL);
+ if (!ch)
+ return NULL;
+
+ counter = &ch->counter;
+ dev = &counter->dev;
+
+ /* Acquire unique ID */
+ err = ida_alloc(&counter_ida, GFP_KERNEL);
+ if (err < 0)
+ goto err_ida_alloc;
+ dev->id = err;
+
+ mutex_init(&counter->ops_exist_lock);
+ dev->type = &counter_device_type;
+ dev->bus = &counter_bus_type;
+ dev->devt = MKDEV(MAJOR(counter_devt), dev->id);
+
+ err = counter_chrdev_add(counter);
+ if (err < 0)
+ goto err_chrdev_add;
+
+ device_initialize(dev);
+
+ err = dev_set_name(dev, COUNTER_NAME "%d", dev->id);
+ if (err)
+ goto err_dev_set_name;
+
+ return counter;
+
+err_dev_set_name:
+
+ counter_chrdev_remove(counter);
+err_chrdev_add:
+
+ ida_free(&counter_ida, dev->id);
+err_ida_alloc:
+
+ kfree(ch);
+
+ return NULL;
+}
+EXPORT_SYMBOL_NS_GPL(counter_alloc, COUNTER);
+
+void counter_put(struct counter_device *counter)
+{
+ put_device(&counter->dev);
+}
+EXPORT_SYMBOL_NS_GPL(counter_put, COUNTER);
+
+/**
+ * counter_add - complete registration of a counter
+ * @counter: the counter to add
+ *
+ * This is part two of counter registration.
+ *
+ * If this succeeds, call counter_unregister() to get rid of the counter_device again.
+ */
+int counter_add(struct counter_device *counter)
+{
+ int err;
+ struct device *dev = &counter->dev;
+
+ if (counter->parent) {
+ dev->parent = counter->parent;
+ dev->of_node = counter->parent->of_node;
+ }
+
+ err = counter_sysfs_add(counter);
+ if (err < 0)
+ return err;
+
+ /* implies device_add(dev) */
+ return cdev_device_add(&counter->chrdev, dev);
+}
+EXPORT_SYMBOL_NS_GPL(counter_add, COUNTER);
+
+/**
+ * counter_unregister - unregister Counter from the system
+ * @counter: pointer to Counter to unregister
+ *
+ * The Counter is unregistered from the system.
+ */
+void counter_unregister(struct counter_device *const counter)
+{
+ if (!counter)
+ return;
+
+ cdev_device_del(&counter->chrdev, &counter->dev);
+
+ mutex_lock(&counter->ops_exist_lock);
+
+ counter->ops = NULL;
+ wake_up(&counter->events_wait);
+
+ mutex_unlock(&counter->ops_exist_lock);
+}
+EXPORT_SYMBOL_NS_GPL(counter_unregister, COUNTER);
+
+static void devm_counter_release(void *counter)
+{
+ counter_unregister(counter);
+}
+
+static void devm_counter_put(void *counter)
+{
+ counter_put(counter);
+}
+
+/**
+ * devm_counter_alloc - allocate a counter_device
+ * @dev: the device to register the release callback for
+ * @sizeof_priv: size of the driver private data
+ *
+ * This is the device managed version of counter_add(). It registers a cleanup
+ * callback to care for calling counter_put().
+ */
+struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)
+{
+ struct counter_device *counter;
+ int err;
+
+ counter = counter_alloc(sizeof_priv);
+ if (!counter)
+ return NULL;
+
+ err = devm_add_action_or_reset(dev, devm_counter_put, counter);
+ if (err < 0)
+ return NULL;
+
+ return counter;
+}
+EXPORT_SYMBOL_NS_GPL(devm_counter_alloc, COUNTER);
+
+/**
+ * devm_counter_add - complete registration of a counter
+ * @dev: the device to register the release callback for
+ * @counter: the counter to add
+ *
+ * This is the device managed version of counter_add(). It registers a cleanup
+ * callback to care for calling counter_unregister().
+ */
+int devm_counter_add(struct device *dev,
+ struct counter_device *const counter)
+{
+ int err;
+
+ err = counter_add(counter);
+ if (err < 0)
+ return err;
+
+ return devm_add_action_or_reset(dev, devm_counter_release, counter);
+}
+EXPORT_SYMBOL_NS_GPL(devm_counter_add, COUNTER);
+
+#define COUNTER_DEV_MAX 256
+
+static int __init counter_init(void)
+{
+ int err;
+
+ err = bus_register(&counter_bus_type);
+ if (err < 0)
+ return err;
+
+ err = alloc_chrdev_region(&counter_devt, 0, COUNTER_DEV_MAX,
+ COUNTER_NAME);
+ if (err < 0)
+ goto err_unregister_bus;
+
+ return 0;
+
+err_unregister_bus:
+ bus_unregister(&counter_bus_type);
+ return err;
+}
+
+static void __exit counter_exit(void)
+{
+ unregister_chrdev_region(counter_devt, COUNTER_DEV_MAX);
+ 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/counter-sysfs.c b/drivers/counter/counter-sysfs.c
new file mode 100644
index 000000000..b9efe66f9
--- /dev/null
+++ b/drivers/counter/counter-sysfs.c
@@ -0,0 +1,1170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Generic Counter sysfs interface
+ * Copyright (C) 2020 William Breathitt Gray
+ */
+#include <linux/counter.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gfp.h>
+#include <linux/kernel.h>
+#include <linux/kfifo.h>
+#include <linux/kstrtox.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include "counter-sysfs.h"
+
+static inline struct counter_device *counter_from_dev(struct device *dev)
+{
+ return container_of(dev, struct counter_device, dev);
+}
+
+/**
+ * struct counter_attribute - Counter sysfs attribute
+ * @dev_attr: device attribute for sysfs
+ * @l: node to add Counter attribute to attribute group list
+ * @comp: Counter component callbacks and data
+ * @scope: Counter scope of the attribute
+ * @parent: pointer to the parent component
+ */
+struct counter_attribute {
+ struct device_attribute dev_attr;
+ struct list_head l;
+
+ struct counter_comp comp;
+ enum counter_scope scope;
+ void *parent;
+};
+
+#define to_counter_attribute(_dev_attr) \
+ container_of(_dev_attr, struct counter_attribute, dev_attr)
+
+/**
+ * struct counter_attribute_group - container for attribute group
+ * @name: name of the attribute group
+ * @attr_list: list to keep track of created attributes
+ * @num_attr: number of attributes
+ */
+struct counter_attribute_group {
+ const char *name;
+ struct list_head attr_list;
+ size_t num_attr;
+};
+
+static const char *const counter_function_str[] = {
+ [COUNTER_FUNCTION_INCREASE] = "increase",
+ [COUNTER_FUNCTION_DECREASE] = "decrease",
+ [COUNTER_FUNCTION_PULSE_DIRECTION] = "pulse-direction",
+ [COUNTER_FUNCTION_QUADRATURE_X1_A] = "quadrature x1 a",
+ [COUNTER_FUNCTION_QUADRATURE_X1_B] = "quadrature x1 b",
+ [COUNTER_FUNCTION_QUADRATURE_X2_A] = "quadrature x2 a",
+ [COUNTER_FUNCTION_QUADRATURE_X2_B] = "quadrature x2 b",
+ [COUNTER_FUNCTION_QUADRATURE_X4] = "quadrature x4"
+};
+
+static const char *const counter_signal_value_str[] = {
+ [COUNTER_SIGNAL_LEVEL_LOW] = "low",
+ [COUNTER_SIGNAL_LEVEL_HIGH] = "high"
+};
+
+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"
+};
+
+static const char *const counter_count_direction_str[] = {
+ [COUNTER_COUNT_DIRECTION_FORWARD] = "forward",
+ [COUNTER_COUNT_DIRECTION_BACKWARD] = "backward"
+};
+
+static const char *const counter_count_mode_str[] = {
+ [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"
+};
+
+static const char *const counter_signal_polarity_str[] = {
+ [COUNTER_SIGNAL_POLARITY_POSITIVE] = "positive",
+ [COUNTER_SIGNAL_POLARITY_NEGATIVE] = "negative"
+};
+
+static ssize_t counter_comp_u8_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ int err;
+ u8 data = 0;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_u8_read(counter, &data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_u8_read(counter, a->parent, &data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ err = a->comp.count_u8_read(counter, a->parent, &data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ if (a->comp.type == COUNTER_COMP_BOOL)
+ /* data should already be boolean but ensure just to be safe */
+ data = !!data;
+
+ return sysfs_emit(buf, "%u\n", (unsigned int)data);
+}
+
+static ssize_t counter_comp_u8_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ int err;
+ bool bool_data = 0;
+ u8 data = 0;
+
+ if (a->comp.type == COUNTER_COMP_BOOL) {
+ err = kstrtobool(buf, &bool_data);
+ data = bool_data;
+ } else
+ err = kstrtou8(buf, 0, &data);
+ if (err < 0)
+ return err;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_u8_write(counter, data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_u8_write(counter, a->parent, data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ err = a->comp.count_u8_write(counter, a->parent, data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ return len;
+}
+
+static ssize_t counter_comp_u32_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ const struct counter_available *const avail = a->comp.priv;
+ int err;
+ u32 data = 0;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_u32_read(counter, &data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_u32_read(counter, a->parent, &data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ if (a->comp.type == COUNTER_COMP_SYNAPSE_ACTION)
+ err = a->comp.action_read(counter, a->parent,
+ a->comp.priv, &data);
+ else
+ err = a->comp.count_u32_read(counter, a->parent, &data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ switch (a->comp.type) {
+ case COUNTER_COMP_FUNCTION:
+ return sysfs_emit(buf, "%s\n", counter_function_str[data]);
+ case COUNTER_COMP_SIGNAL_LEVEL:
+ return sysfs_emit(buf, "%s\n", counter_signal_value_str[data]);
+ case COUNTER_COMP_SYNAPSE_ACTION:
+ return sysfs_emit(buf, "%s\n", counter_synapse_action_str[data]);
+ case COUNTER_COMP_ENUM:
+ return sysfs_emit(buf, "%s\n", avail->strs[data]);
+ case COUNTER_COMP_COUNT_DIRECTION:
+ return sysfs_emit(buf, "%s\n", counter_count_direction_str[data]);
+ case COUNTER_COMP_COUNT_MODE:
+ return sysfs_emit(buf, "%s\n", counter_count_mode_str[data]);
+ case COUNTER_COMP_SIGNAL_POLARITY:
+ return sysfs_emit(buf, "%s\n", counter_signal_polarity_str[data]);
+ default:
+ return sysfs_emit(buf, "%u\n", (unsigned int)data);
+ }
+}
+
+static int counter_find_enum(u32 *const enum_item, const u32 *const enums,
+ const size_t num_enums, const char *const buf,
+ const char *const string_array[])
+{
+ size_t index;
+
+ for (index = 0; index < num_enums; index++) {
+ *enum_item = enums[index];
+ if (sysfs_streq(buf, string_array[*enum_item]))
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static ssize_t counter_comp_u32_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ struct counter_count *const count = a->parent;
+ struct counter_synapse *const synapse = a->comp.priv;
+ const struct counter_available *const avail = a->comp.priv;
+ int err;
+ u32 data = 0;
+
+ switch (a->comp.type) {
+ case COUNTER_COMP_FUNCTION:
+ err = counter_find_enum(&data, count->functions_list,
+ count->num_functions, buf,
+ counter_function_str);
+ break;
+ case COUNTER_COMP_SYNAPSE_ACTION:
+ err = counter_find_enum(&data, synapse->actions_list,
+ synapse->num_actions, buf,
+ counter_synapse_action_str);
+ break;
+ case COUNTER_COMP_ENUM:
+ err = __sysfs_match_string(avail->strs, avail->num_items, buf);
+ data = err;
+ break;
+ case COUNTER_COMP_COUNT_MODE:
+ err = counter_find_enum(&data, avail->enums, avail->num_items,
+ buf, counter_count_mode_str);
+ break;
+ case COUNTER_COMP_SIGNAL_POLARITY:
+ err = counter_find_enum(&data, avail->enums, avail->num_items,
+ buf, counter_signal_polarity_str);
+ break;
+ default:
+ err = kstrtou32(buf, 0, &data);
+ break;
+ }
+ if (err < 0)
+ return err;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_u32_write(counter, data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_u32_write(counter, a->parent, data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ if (a->comp.type == COUNTER_COMP_SYNAPSE_ACTION)
+ err = a->comp.action_write(counter, count, synapse,
+ data);
+ else
+ err = a->comp.count_u32_write(counter, count, data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ return len;
+}
+
+static ssize_t counter_comp_u64_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ int err;
+ u64 data = 0;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_u64_read(counter, &data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_u64_read(counter, a->parent, &data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ err = a->comp.count_u64_read(counter, a->parent, &data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ return sysfs_emit(buf, "%llu\n", (unsigned long long)data);
+}
+
+static ssize_t counter_comp_u64_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ int err;
+ u64 data = 0;
+
+ err = kstrtou64(buf, 0, &data);
+ if (err < 0)
+ return err;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_u64_write(counter, data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_u64_write(counter, a->parent, data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ err = a->comp.count_u64_write(counter, a->parent, data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ return len;
+}
+
+static ssize_t counter_comp_array_u32_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ const struct counter_array *const element = a->comp.priv;
+ int err;
+ u32 data = 0;
+
+ if (a->scope != COUNTER_SCOPE_SIGNAL ||
+ element->type != COUNTER_COMP_SIGNAL_POLARITY)
+ return -EINVAL;
+
+ err = a->comp.signal_array_u32_read(counter, a->parent, element->idx,
+ &data);
+ if (err < 0)
+ return err;
+
+ return sysfs_emit(buf, "%s\n", counter_signal_polarity_str[data]);
+}
+
+static ssize_t counter_comp_array_u32_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ const struct counter_array *const element = a->comp.priv;
+ int err;
+ u32 data = 0;
+
+ if (element->type != COUNTER_COMP_SIGNAL_POLARITY ||
+ a->scope != COUNTER_SCOPE_SIGNAL)
+ return -EINVAL;
+
+ err = counter_find_enum(&data, element->avail->enums,
+ element->avail->num_items, buf,
+ counter_signal_polarity_str);
+ if (err < 0)
+ return err;
+
+ err = a->comp.signal_array_u32_write(counter, a->parent, element->idx,
+ data);
+ if (err < 0)
+ return err;
+
+ return len;
+}
+
+static ssize_t counter_comp_array_u64_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ const struct counter_array *const element = a->comp.priv;
+ int err;
+ u64 data = 0;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_array_u64_read(counter, element->idx,
+ &data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_array_u64_read(counter, a->parent,
+ element->idx, &data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ err = a->comp.count_array_u64_read(counter, a->parent,
+ element->idx, &data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ return sysfs_emit(buf, "%llu\n", (unsigned long long)data);
+}
+
+static ssize_t counter_comp_array_u64_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ struct counter_device *const counter = counter_from_dev(dev);
+ const struct counter_array *const element = a->comp.priv;
+ int err;
+ u64 data = 0;
+
+ err = kstrtou64(buf, 0, &data);
+ if (err < 0)
+ return err;
+
+ switch (a->scope) {
+ case COUNTER_SCOPE_DEVICE:
+ err = a->comp.device_array_u64_write(counter, element->idx,
+ data);
+ break;
+ case COUNTER_SCOPE_SIGNAL:
+ err = a->comp.signal_array_u64_write(counter, a->parent,
+ element->idx, data);
+ break;
+ case COUNTER_SCOPE_COUNT:
+ err = a->comp.count_array_u64_write(counter, a->parent,
+ element->idx, data);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (err < 0)
+ return err;
+
+ return len;
+}
+
+static ssize_t enums_available_show(const u32 *const enums,
+ const size_t num_enums,
+ const char *const strs[], char *buf)
+{
+ size_t len = 0;
+ size_t index;
+
+ for (index = 0; index < num_enums; index++)
+ len += sysfs_emit_at(buf, len, "%s\n", strs[enums[index]]);
+
+ return len;
+}
+
+static ssize_t strs_available_show(const struct counter_available *const avail,
+ char *buf)
+{
+ size_t len = 0;
+ size_t index;
+
+ for (index = 0; index < avail->num_items; index++)
+ len += sysfs_emit_at(buf, len, "%s\n", avail->strs[index]);
+
+ return len;
+}
+
+static ssize_t counter_comp_available_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct counter_attribute *const a = to_counter_attribute(attr);
+ const struct counter_count *const count = a->parent;
+ const struct counter_synapse *const synapse = a->comp.priv;
+ const struct counter_available *const avail = a->comp.priv;
+
+ switch (a->comp.type) {
+ case COUNTER_COMP_FUNCTION:
+ return enums_available_show(count->functions_list,
+ count->num_functions,
+ counter_function_str, buf);
+ case COUNTER_COMP_SYNAPSE_ACTION:
+ return enums_available_show(synapse->actions_list,
+ synapse->num_actions,
+ counter_synapse_action_str, buf);
+ case COUNTER_COMP_ENUM:
+ return strs_available_show(avail, buf);
+ case COUNTER_COMP_COUNT_MODE:
+ return enums_available_show(avail->enums, avail->num_items,
+ counter_count_mode_str, buf);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int counter_avail_attr_create(struct device *const dev,
+ struct counter_attribute_group *const group,
+ const struct counter_comp *const comp, void *const parent)
+{
+ struct counter_attribute *counter_attr;
+ struct device_attribute *dev_attr;
+
+ counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
+ if (!counter_attr)
+ return -ENOMEM;
+
+ /* Configure Counter attribute */
+ counter_attr->comp.type = comp->type;
+ counter_attr->comp.priv = comp->priv;
+ counter_attr->parent = parent;
+
+ /* Initialize sysfs attribute */
+ dev_attr = &counter_attr->dev_attr;
+ sysfs_attr_init(&dev_attr->attr);
+
+ /* Configure device attribute */
+ dev_attr->attr.name = devm_kasprintf(dev, GFP_KERNEL, "%s_available",
+ comp->name);
+ if (!dev_attr->attr.name)
+ return -ENOMEM;
+ dev_attr->attr.mode = 0444;
+ dev_attr->show = counter_comp_available_show;
+
+ /* Store list node */
+ list_add(&counter_attr->l, &group->attr_list);
+ group->num_attr++;
+
+ return 0;
+}
+
+static int counter_attr_create(struct device *const dev,
+ struct counter_attribute_group *const group,
+ const struct counter_comp *const comp,
+ const enum counter_scope scope,
+ void *const parent)
+{
+ const struct counter_array *const array = comp->priv;
+ struct counter_attribute *counter_attr;
+ struct device_attribute *dev_attr;
+
+ counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
+ if (!counter_attr)
+ return -ENOMEM;
+
+ /* Configure Counter attribute */
+ counter_attr->comp = *comp;
+ counter_attr->scope = scope;
+ counter_attr->parent = parent;
+
+ /* Configure device attribute */
+ dev_attr = &counter_attr->dev_attr;
+ sysfs_attr_init(&dev_attr->attr);
+ dev_attr->attr.name = comp->name;
+ switch (comp->type) {
+ case COUNTER_COMP_U8:
+ case COUNTER_COMP_BOOL:
+ if (comp->device_u8_read) {
+ dev_attr->attr.mode |= 0444;
+ dev_attr->show = counter_comp_u8_show;
+ }
+ if (comp->device_u8_write) {
+ dev_attr->attr.mode |= 0200;
+ dev_attr->store = counter_comp_u8_store;
+ }
+ break;
+ case COUNTER_COMP_SIGNAL_LEVEL:
+ case COUNTER_COMP_FUNCTION:
+ case COUNTER_COMP_SYNAPSE_ACTION:
+ case COUNTER_COMP_ENUM:
+ case COUNTER_COMP_COUNT_DIRECTION:
+ case COUNTER_COMP_COUNT_MODE:
+ case COUNTER_COMP_SIGNAL_POLARITY:
+ if (comp->device_u32_read) {
+ dev_attr->attr.mode |= 0444;
+ dev_attr->show = counter_comp_u32_show;
+ }
+ if (comp->device_u32_write) {
+ dev_attr->attr.mode |= 0200;
+ dev_attr->store = counter_comp_u32_store;
+ }
+ break;
+ case COUNTER_COMP_U64:
+ if (comp->device_u64_read) {
+ dev_attr->attr.mode |= 0444;
+ dev_attr->show = counter_comp_u64_show;
+ }
+ if (comp->device_u64_write) {
+ dev_attr->attr.mode |= 0200;
+ dev_attr->store = counter_comp_u64_store;
+ }
+ break;
+ case COUNTER_COMP_ARRAY:
+ switch (array->type) {
+ case COUNTER_COMP_SIGNAL_POLARITY:
+ if (comp->signal_array_u32_read) {
+ dev_attr->attr.mode |= 0444;
+ dev_attr->show = counter_comp_array_u32_show;
+ }
+ if (comp->signal_array_u32_write) {
+ dev_attr->attr.mode |= 0200;
+ dev_attr->store = counter_comp_array_u32_store;
+ }
+ break;
+ case COUNTER_COMP_U64:
+ if (comp->device_array_u64_read) {
+ dev_attr->attr.mode |= 0444;
+ dev_attr->show = counter_comp_array_u64_show;
+ }
+ if (comp->device_array_u64_write) {
+ dev_attr->attr.mode |= 0200;
+ dev_attr->store = counter_comp_array_u64_store;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Store list node */
+ list_add(&counter_attr->l, &group->attr_list);
+ group->num_attr++;
+
+ /* Create "*_available" attribute if needed */
+ switch (comp->type) {
+ case COUNTER_COMP_FUNCTION:
+ case COUNTER_COMP_SYNAPSE_ACTION:
+ case COUNTER_COMP_ENUM:
+ case COUNTER_COMP_COUNT_MODE:
+ return counter_avail_attr_create(dev, group, comp, parent);
+ default:
+ return 0;
+ }
+}
+
+static ssize_t counter_comp_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "%s\n", to_counter_attribute(attr)->comp.name);
+}
+
+static int counter_name_attr_create(struct device *const dev,
+ struct counter_attribute_group *const group,
+ const char *const name)
+{
+ struct counter_attribute *counter_attr;
+
+ counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
+ if (!counter_attr)
+ return -ENOMEM;
+
+ /* Configure Counter attribute */
+ counter_attr->comp.name = name;
+
+ /* Configure device attribute */
+ sysfs_attr_init(&counter_attr->dev_attr.attr);
+ counter_attr->dev_attr.attr.name = "name";
+ counter_attr->dev_attr.attr.mode = 0444;
+ counter_attr->dev_attr.show = counter_comp_name_show;
+
+ /* Store list node */
+ list_add(&counter_attr->l, &group->attr_list);
+ group->num_attr++;
+
+ return 0;
+}
+
+static ssize_t counter_comp_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const size_t id = (size_t)to_counter_attribute(attr)->comp.priv;
+
+ return sysfs_emit(buf, "%zu\n", id);
+}
+
+static int counter_comp_id_attr_create(struct device *const dev,
+ struct counter_attribute_group *const group,
+ const char *name, const size_t id)
+{
+ struct counter_attribute *counter_attr;
+
+ /* Allocate Counter attribute */
+ counter_attr = devm_kzalloc(dev, sizeof(*counter_attr), GFP_KERNEL);
+ if (!counter_attr)
+ return -ENOMEM;
+
+ /* Generate component ID name */
+ name = devm_kasprintf(dev, GFP_KERNEL, "%s_component_id", name);
+ if (!name)
+ return -ENOMEM;
+
+ /* Configure Counter attribute */
+ counter_attr->comp.priv = (void *)id;
+
+ /* Configure device attribute */
+ sysfs_attr_init(&counter_attr->dev_attr.attr);
+ counter_attr->dev_attr.attr.name = name;
+ counter_attr->dev_attr.attr.mode = 0444;
+ counter_attr->dev_attr.show = counter_comp_id_show;
+
+ /* Store list node */
+ list_add(&counter_attr->l, &group->attr_list);
+ group->num_attr++;
+
+ return 0;
+}
+
+static int counter_ext_attrs_create(struct device *const dev,
+ struct counter_attribute_group *const group,
+ const struct counter_comp *const ext,
+ const enum counter_scope scope,
+ void *const parent, const size_t id)
+{
+ int err;
+
+ /* Create main extension attribute */
+ err = counter_attr_create(dev, group, ext, scope, parent);
+ if (err < 0)
+ return err;
+
+ /* Create extension id attribute */
+ return counter_comp_id_attr_create(dev, group, ext->name, id);
+}
+
+static int counter_array_attrs_create(struct device *const dev,
+ struct counter_attribute_group *const group,
+ const struct counter_comp *const comp,
+ const enum counter_scope scope,
+ void *const parent, const size_t id)
+{
+ const struct counter_array *const array = comp->priv;
+ struct counter_comp ext = *comp;
+ struct counter_array *element;
+ size_t idx;
+ int err;
+
+ /* Create an attribute for each array element */
+ for (idx = 0; idx < array->length; idx++) {
+ /* Generate array element attribute name */
+ ext.name = devm_kasprintf(dev, GFP_KERNEL, "%s%zu", comp->name,
+ idx);
+ if (!ext.name)
+ return -ENOMEM;
+
+ /* Allocate and configure array element */
+ element = devm_kzalloc(dev, sizeof(*element), GFP_KERNEL);
+ if (!element)
+ return -ENOMEM;
+ element->type = array->type;
+ element->avail = array->avail;
+ element->idx = idx;
+ ext.priv = element;
+
+ /* Create all attributes associated with the array element */
+ err = counter_ext_attrs_create(dev, group, &ext, scope, parent,
+ id + idx);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int counter_sysfs_exts_add(struct device *const dev,
+ struct counter_attribute_group *const group,
+ const struct counter_comp *const exts,
+ const size_t num_ext,
+ const enum counter_scope scope,
+ void *const parent)
+{
+ size_t i;
+ const struct counter_comp *ext;
+ int err;
+ size_t id = 0;
+ const struct counter_array *array;
+
+ /* Create attributes for each extension */
+ for (i = 0; i < num_ext; i++) {
+ ext = &exts[i];
+ if (ext->type == COUNTER_COMP_ARRAY) {
+ err = counter_array_attrs_create(dev, group, ext, scope,
+ parent, id);
+ array = ext->priv;
+ id += array->length;
+ } else {
+ err = counter_ext_attrs_create(dev, group, ext, scope,
+ parent, id);
+ id++;
+ }
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static struct counter_comp counter_signal_comp = {
+ .type = COUNTER_COMP_SIGNAL_LEVEL,
+ .name = "signal",
+};
+
+static int counter_signal_attrs_create(struct counter_device *const counter,
+ struct counter_attribute_group *const cattr_group,
+ struct counter_signal *const signal)
+{
+ const enum counter_scope scope = COUNTER_SCOPE_SIGNAL;
+ struct device *const dev = &counter->dev;
+ int err;
+ struct counter_comp comp;
+
+ /* Create main Signal attribute */
+ comp = counter_signal_comp;
+ comp.signal_u32_read = counter->ops->signal_read;
+ err = counter_attr_create(dev, cattr_group, &comp, scope, signal);
+ if (err < 0)
+ return err;
+
+ /* Create Signal name attribute */
+ err = counter_name_attr_create(dev, cattr_group, signal->name);
+ if (err < 0)
+ return err;
+
+ /* Add Signal extensions */
+ return counter_sysfs_exts_add(dev, cattr_group, signal->ext,
+ signal->num_ext, scope, signal);
+}
+
+static int counter_sysfs_signals_add(struct counter_device *const counter,
+ struct counter_attribute_group *const groups)
+{
+ size_t i;
+ int err;
+
+ /* Add each Signal */
+ for (i = 0; i < counter->num_signals; i++) {
+ /* Generate Signal attribute directory name */
+ groups[i].name = devm_kasprintf(&counter->dev, GFP_KERNEL,
+ "signal%zu", i);
+ if (!groups[i].name)
+ return -ENOMEM;
+
+ /* Create all attributes associated with Signal */
+ err = counter_signal_attrs_create(counter, groups + i,
+ counter->signals + i);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int counter_sysfs_synapses_add(struct counter_device *const counter,
+ struct counter_attribute_group *const group,
+ struct counter_count *const count)
+{
+ size_t i;
+
+ /* Add each Synapse */
+ for (i = 0; i < count->num_synapses; i++) {
+ struct device *const dev = &counter->dev;
+ struct counter_synapse *synapse;
+ size_t id;
+ struct counter_comp comp;
+ int err;
+
+ synapse = count->synapses + i;
+
+ /* Generate Synapse action name */
+ id = synapse->signal - counter->signals;
+ comp.name = devm_kasprintf(dev, GFP_KERNEL, "signal%zu_action",
+ id);
+ if (!comp.name)
+ return -ENOMEM;
+
+ /* Create action attribute */
+ comp.type = COUNTER_COMP_SYNAPSE_ACTION;
+ comp.action_read = counter->ops->action_read;
+ comp.action_write = counter->ops->action_write;
+ comp.priv = synapse;
+ err = counter_attr_create(dev, group, &comp,
+ COUNTER_SCOPE_COUNT, count);
+ if (err < 0)
+ return err;
+
+ /* Create Synapse component ID attribute */
+ err = counter_comp_id_attr_create(dev, group, comp.name, i);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static struct counter_comp counter_count_comp =
+ COUNTER_COMP_COUNT_U64("count", NULL, NULL);
+
+static struct counter_comp counter_function_comp = {
+ .type = COUNTER_COMP_FUNCTION,
+ .name = "function",
+};
+
+static int counter_count_attrs_create(struct counter_device *const counter,
+ struct counter_attribute_group *const cattr_group,
+ struct counter_count *const count)
+{
+ const enum counter_scope scope = COUNTER_SCOPE_COUNT;
+ struct device *const dev = &counter->dev;
+ int err;
+ struct counter_comp comp;
+
+ /* Create main Count attribute */
+ comp = counter_count_comp;
+ comp.count_u64_read = counter->ops->count_read;
+ comp.count_u64_write = counter->ops->count_write;
+ err = counter_attr_create(dev, cattr_group, &comp, scope, count);
+ if (err < 0)
+ return err;
+
+ /* Create Count name attribute */
+ err = counter_name_attr_create(dev, cattr_group, count->name);
+ if (err < 0)
+ return err;
+
+ /* Create Count function attribute */
+ comp = counter_function_comp;
+ comp.count_u32_read = counter->ops->function_read;
+ comp.count_u32_write = counter->ops->function_write;
+ err = counter_attr_create(dev, cattr_group, &comp, scope, count);
+ if (err < 0)
+ return err;
+
+ /* Add Count extensions */
+ return counter_sysfs_exts_add(dev, cattr_group, count->ext,
+ count->num_ext, scope, count);
+}
+
+static int counter_sysfs_counts_add(struct counter_device *const counter,
+ struct counter_attribute_group *const groups)
+{
+ size_t i;
+ struct counter_count *count;
+ int err;
+
+ /* Add each Count */
+ for (i = 0; i < counter->num_counts; i++) {
+ count = counter->counts + i;
+
+ /* Generate Count attribute directory name */
+ groups[i].name = devm_kasprintf(&counter->dev, GFP_KERNEL,
+ "count%zu", i);
+ if (!groups[i].name)
+ return -ENOMEM;
+
+ /* Add sysfs attributes of the Synapses */
+ err = counter_sysfs_synapses_add(counter, groups + i, count);
+ if (err < 0)
+ return err;
+
+ /* Create all attributes associated with Count */
+ err = counter_count_attrs_create(counter, groups + i, count);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int counter_num_signals_read(struct counter_device *counter, u8 *val)
+{
+ *val = counter->num_signals;
+ return 0;
+}
+
+static int counter_num_counts_read(struct counter_device *counter, u8 *val)
+{
+ *val = counter->num_counts;
+ return 0;
+}
+
+static int counter_events_queue_size_read(struct counter_device *counter,
+ u64 *val)
+{
+ *val = kfifo_size(&counter->events);
+ return 0;
+}
+
+static int counter_events_queue_size_write(struct counter_device *counter,
+ u64 val)
+{
+ DECLARE_KFIFO_PTR(events, struct counter_event);
+ int err;
+ unsigned long flags;
+
+ /* Allocate new events queue */
+ err = kfifo_alloc(&events, val, GFP_KERNEL);
+ if (err)
+ return err;
+
+ /* Swap in new events queue */
+ mutex_lock(&counter->events_out_lock);
+ spin_lock_irqsave(&counter->events_in_lock, flags);
+ kfifo_free(&counter->events);
+ counter->events.kfifo = events.kfifo;
+ spin_unlock_irqrestore(&counter->events_in_lock, flags);
+ mutex_unlock(&counter->events_out_lock);
+
+ return 0;
+}
+
+static struct counter_comp counter_num_signals_comp =
+ COUNTER_COMP_DEVICE_U8("num_signals", counter_num_signals_read, NULL);
+
+static struct counter_comp counter_num_counts_comp =
+ COUNTER_COMP_DEVICE_U8("num_counts", counter_num_counts_read, NULL);
+
+static struct counter_comp counter_events_queue_size_comp =
+ COUNTER_COMP_DEVICE_U64("events_queue_size",
+ counter_events_queue_size_read,
+ counter_events_queue_size_write);
+
+static int counter_sysfs_attr_add(struct counter_device *const counter,
+ struct counter_attribute_group *cattr_group)
+{
+ const enum counter_scope scope = COUNTER_SCOPE_DEVICE;
+ struct device *const dev = &counter->dev;
+ int err;
+
+ /* Add Signals sysfs attributes */
+ err = counter_sysfs_signals_add(counter, cattr_group);
+ if (err < 0)
+ return err;
+ cattr_group += counter->num_signals;
+
+ /* Add Counts sysfs attributes */
+ err = counter_sysfs_counts_add(counter, cattr_group);
+ if (err < 0)
+ return err;
+ cattr_group += counter->num_counts;
+
+ /* Create name attribute */
+ err = counter_name_attr_create(dev, cattr_group, counter->name);
+ if (err < 0)
+ return err;
+
+ /* Create num_signals attribute */
+ err = counter_attr_create(dev, cattr_group, &counter_num_signals_comp,
+ scope, NULL);
+ if (err < 0)
+ return err;
+
+ /* Create num_counts attribute */
+ err = counter_attr_create(dev, cattr_group, &counter_num_counts_comp,
+ scope, NULL);
+ if (err < 0)
+ return err;
+
+ /* Create events_queue_size attribute */
+ err = counter_attr_create(dev, cattr_group,
+ &counter_events_queue_size_comp, scope, NULL);
+ if (err < 0)
+ return err;
+
+ /* Add device extensions */
+ return counter_sysfs_exts_add(dev, cattr_group, counter->ext,
+ counter->num_ext, scope, NULL);
+
+ return 0;
+}
+
+/**
+ * counter_sysfs_add - Adds Counter sysfs attributes to the device structure
+ * @counter: Pointer to the Counter device structure
+ *
+ * Counter sysfs attributes are created and added to the respective device
+ * structure for later registration to the system. Resource-managed memory
+ * allocation is performed by this function, and this memory should be freed
+ * when no longer needed (automatically by a device_unregister call, or
+ * manually by a devres_release_all call).
+ */
+int counter_sysfs_add(struct counter_device *const counter)
+{
+ struct device *const dev = &counter->dev;
+ const size_t num_groups = counter->num_signals + counter->num_counts + 1;
+ struct counter_attribute_group *cattr_groups;
+ size_t i, j;
+ int err;
+ struct attribute_group *groups;
+ struct counter_attribute *p;
+
+ /* Allocate space for attribute groups (signals, counts, and ext) */
+ cattr_groups = devm_kcalloc(dev, num_groups, sizeof(*cattr_groups),
+ GFP_KERNEL);
+ if (!cattr_groups)
+ return -ENOMEM;
+
+ /* Initialize attribute lists */
+ for (i = 0; i < num_groups; i++)
+ INIT_LIST_HEAD(&cattr_groups[i].attr_list);
+
+ /* Add Counter device sysfs attributes */
+ err = counter_sysfs_attr_add(counter, cattr_groups);
+ if (err < 0)
+ return err;
+
+ /* Allocate attribute group pointers for association with device */
+ dev->groups = devm_kcalloc(dev, num_groups + 1, sizeof(*dev->groups),
+ GFP_KERNEL);
+ if (!dev->groups)
+ return -ENOMEM;
+
+ /* Allocate space for attribute groups */
+ groups = devm_kcalloc(dev, num_groups, sizeof(*groups), GFP_KERNEL);
+ if (!groups)
+ return -ENOMEM;
+
+ /* Prepare each group of attributes for association */
+ for (i = 0; i < num_groups; i++) {
+ groups[i].name = cattr_groups[i].name;
+
+ /* Allocate space for attribute pointers */
+ groups[i].attrs = devm_kcalloc(dev,
+ cattr_groups[i].num_attr + 1,
+ sizeof(*groups[i].attrs),
+ GFP_KERNEL);
+ if (!groups[i].attrs)
+ return -ENOMEM;
+
+ /* Add attribute pointers to attribute group */
+ j = 0;
+ list_for_each_entry(p, &cattr_groups[i].attr_list, l)
+ groups[i].attrs[j++] = &p->dev_attr.attr;
+
+ /* Associate attribute group */
+ dev->groups[i] = &groups[i];
+ }
+
+ return 0;
+}
diff --git a/drivers/counter/counter-sysfs.h b/drivers/counter/counter-sysfs.h
new file mode 100644
index 000000000..14fe566ac
--- /dev/null
+++ b/drivers/counter/counter-sysfs.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Counter sysfs interface
+ * Copyright (C) 2020 William Breathitt Gray
+ */
+#ifndef _COUNTER_SYSFS_H_
+#define _COUNTER_SYSFS_H_
+
+#include <linux/counter.h>
+
+int counter_sysfs_add(struct counter_device *const counter);
+
+#endif /* _COUNTER_SYSFS_H_ */
diff --git a/drivers/counter/ftm-quaddec.c b/drivers/counter/ftm-quaddec.c
new file mode 100644
index 000000000..aea6622a9
--- /dev/null
+++ b/drivers/counter/ftm-quaddec.c
@@ -0,0 +1,328 @@
+// 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>
+#include <linux/types.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 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, u32 *cnt_mode)
+{
+ struct ftm_quaddec *ftm = counter_priv(counter);
+ 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, u32 cnt_mode)
+{
+ struct ftm_quaddec *ftm = counter_priv(counter);
+
+ 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 const enum counter_synapse_action ftm_quaddec_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES
+};
+
+static const enum counter_function ftm_quaddec_count_functions[] = {
+ COUNTER_FUNCTION_QUADRATURE_X4
+};
+
+static int ftm_quaddec_count_read(struct counter_device *counter,
+ struct counter_count *count,
+ u64 *val)
+{
+ struct ftm_quaddec *const ftm = counter_priv(counter);
+ 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 u64 val)
+{
+ struct ftm_quaddec *const ftm = counter_priv(counter);
+
+ 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_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ *function = COUNTER_FUNCTION_QUADRATURE_X4;
+
+ return 0;
+}
+
+static int ftm_quaddec_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ *action = COUNTER_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_read = ftm_quaddec_count_function_read,
+ .action_read = ftm_quaddec_action_read,
+};
+
+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 DEFINE_COUNTER_ENUM(ftm_quaddec_prescaler_enum, ftm_quaddec_prescaler);
+
+static struct counter_comp ftm_quaddec_count_ext[] = {
+ COUNTER_COMP_COUNT_ENUM("prescaler", ftm_quaddec_get_prescaler,
+ ftm_quaddec_set_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 counter_device *counter;
+ struct ftm_quaddec *ftm;
+
+ struct device_node *node = pdev->dev.of_node;
+ struct resource *io;
+ int ret;
+
+ counter = devm_counter_alloc(&pdev->dev, sizeof(*ftm));
+ if (!counter)
+ return -ENOMEM;
+ ftm = counter_priv(counter);
+
+ 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;
+ }
+ counter->name = dev_name(&pdev->dev);
+ counter->parent = &pdev->dev;
+ counter->ops = &ftm_quaddec_cnt_ops;
+ counter->counts = &ftm_quaddec_counts;
+ counter->num_counts = 1;
+ counter->signals = ftm_quaddec_signals;
+ counter->num_signals = ARRAY_SIZE(ftm_quaddec_signals);
+
+ 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_add(&pdev->dev, counter);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
+
+ 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>");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/intel-qep.c b/drivers/counter/intel-qep.c
new file mode 100644
index 000000000..af5942e66
--- /dev/null
+++ b/drivers/counter/intel-qep.c
@@ -0,0 +1,526 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Quadrature Encoder Peripheral driver
+ *
+ * Copyright (C) 2019-2021 Intel Corporation
+ *
+ * Author: Felipe Balbi (Intel)
+ * Author: Jarkko Nikula <jarkko.nikula@linux.intel.com>
+ * Author: Raymond Tan <raymond.tan@intel.com>
+ */
+#include <linux/counter.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+
+#define INTEL_QEPCON 0x00
+#define INTEL_QEPFLT 0x04
+#define INTEL_QEPCOUNT 0x08
+#define INTEL_QEPMAX 0x0c
+#define INTEL_QEPWDT 0x10
+#define INTEL_QEPCAPDIV 0x14
+#define INTEL_QEPCNTR 0x18
+#define INTEL_QEPCAPBUF 0x1c
+#define INTEL_QEPINT_STAT 0x20
+#define INTEL_QEPINT_MASK 0x24
+
+/* QEPCON */
+#define INTEL_QEPCON_EN BIT(0)
+#define INTEL_QEPCON_FLT_EN BIT(1)
+#define INTEL_QEPCON_EDGE_A BIT(2)
+#define INTEL_QEPCON_EDGE_B BIT(3)
+#define INTEL_QEPCON_EDGE_INDX BIT(4)
+#define INTEL_QEPCON_SWPAB BIT(5)
+#define INTEL_QEPCON_OP_MODE BIT(6)
+#define INTEL_QEPCON_PH_ERR BIT(7)
+#define INTEL_QEPCON_COUNT_RST_MODE BIT(8)
+#define INTEL_QEPCON_INDX_GATING_MASK GENMASK(10, 9)
+#define INTEL_QEPCON_INDX_GATING(n) (((n) & 3) << 9)
+#define INTEL_QEPCON_INDX_PAL_PBL INTEL_QEPCON_INDX_GATING(0)
+#define INTEL_QEPCON_INDX_PAL_PBH INTEL_QEPCON_INDX_GATING(1)
+#define INTEL_QEPCON_INDX_PAH_PBL INTEL_QEPCON_INDX_GATING(2)
+#define INTEL_QEPCON_INDX_PAH_PBH INTEL_QEPCON_INDX_GATING(3)
+#define INTEL_QEPCON_CAP_MODE BIT(11)
+#define INTEL_QEPCON_FIFO_THRE_MASK GENMASK(14, 12)
+#define INTEL_QEPCON_FIFO_THRE(n) ((((n) - 1) & 7) << 12)
+#define INTEL_QEPCON_FIFO_EMPTY BIT(15)
+
+/* QEPFLT */
+#define INTEL_QEPFLT_MAX_COUNT(n) ((n) & 0x1fffff)
+
+/* QEPINT */
+#define INTEL_QEPINT_FIFOCRIT BIT(5)
+#define INTEL_QEPINT_FIFOENTRY BIT(4)
+#define INTEL_QEPINT_QEPDIR BIT(3)
+#define INTEL_QEPINT_QEPRST_UP BIT(2)
+#define INTEL_QEPINT_QEPRST_DOWN BIT(1)
+#define INTEL_QEPINT_WDT BIT(0)
+
+#define INTEL_QEPINT_MASK_ALL GENMASK(5, 0)
+
+#define INTEL_QEP_CLK_PERIOD_NS 10
+
+struct intel_qep {
+ struct mutex lock;
+ struct device *dev;
+ void __iomem *regs;
+ bool enabled;
+ /* Context save registers */
+ u32 qepcon;
+ u32 qepflt;
+ u32 qepmax;
+};
+
+static inline u32 intel_qep_readl(struct intel_qep *qep, u32 offset)
+{
+ return readl(qep->regs + offset);
+}
+
+static inline void intel_qep_writel(struct intel_qep *qep,
+ u32 offset, u32 value)
+{
+ writel(value, qep->regs + offset);
+}
+
+static void intel_qep_init(struct intel_qep *qep)
+{
+ u32 reg;
+
+ reg = intel_qep_readl(qep, INTEL_QEPCON);
+ reg &= ~INTEL_QEPCON_EN;
+ intel_qep_writel(qep, INTEL_QEPCON, reg);
+ qep->enabled = false;
+ /*
+ * Make sure peripheral is disabled by flushing the write with
+ * a dummy read
+ */
+ reg = intel_qep_readl(qep, INTEL_QEPCON);
+
+ reg &= ~(INTEL_QEPCON_OP_MODE | INTEL_QEPCON_FLT_EN);
+ reg |= INTEL_QEPCON_EDGE_A | INTEL_QEPCON_EDGE_B |
+ INTEL_QEPCON_EDGE_INDX | INTEL_QEPCON_COUNT_RST_MODE;
+ intel_qep_writel(qep, INTEL_QEPCON, reg);
+ intel_qep_writel(qep, INTEL_QEPINT_MASK, INTEL_QEPINT_MASK_ALL);
+}
+
+static int intel_qep_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct intel_qep *const qep = counter_priv(counter);
+
+ pm_runtime_get_sync(qep->dev);
+ *val = intel_qep_readl(qep, INTEL_QEPCOUNT);
+ pm_runtime_put(qep->dev);
+
+ return 0;
+}
+
+static const enum counter_function intel_qep_count_functions[] = {
+ COUNTER_FUNCTION_QUADRATURE_X4,
+};
+
+static int intel_qep_function_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ *function = COUNTER_FUNCTION_QUADRATURE_X4;
+
+ return 0;
+}
+
+static const enum counter_synapse_action intel_qep_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
+};
+
+static int intel_qep_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+}
+
+static const struct counter_ops intel_qep_counter_ops = {
+ .count_read = intel_qep_count_read,
+ .function_read = intel_qep_function_read,
+ .action_read = intel_qep_action_read,
+};
+
+#define INTEL_QEP_SIGNAL(_id, _name) { \
+ .id = (_id), \
+ .name = (_name), \
+}
+
+static struct counter_signal intel_qep_signals[] = {
+ INTEL_QEP_SIGNAL(0, "Phase A"),
+ INTEL_QEP_SIGNAL(1, "Phase B"),
+ INTEL_QEP_SIGNAL(2, "Index"),
+};
+
+#define INTEL_QEP_SYNAPSE(_signal_id) { \
+ .actions_list = intel_qep_synapse_actions, \
+ .num_actions = ARRAY_SIZE(intel_qep_synapse_actions), \
+ .signal = &intel_qep_signals[(_signal_id)], \
+}
+
+static struct counter_synapse intel_qep_count_synapses[] = {
+ INTEL_QEP_SYNAPSE(0),
+ INTEL_QEP_SYNAPSE(1),
+ INTEL_QEP_SYNAPSE(2),
+};
+
+static int intel_qep_ceiling_read(struct counter_device *counter,
+ struct counter_count *count, u64 *ceiling)
+{
+ struct intel_qep *qep = counter_priv(counter);
+
+ pm_runtime_get_sync(qep->dev);
+ *ceiling = intel_qep_readl(qep, INTEL_QEPMAX);
+ pm_runtime_put(qep->dev);
+
+ return 0;
+}
+
+static int intel_qep_ceiling_write(struct counter_device *counter,
+ struct counter_count *count, u64 max)
+{
+ struct intel_qep *qep = counter_priv(counter);
+ int ret = 0;
+
+ /* Intel QEP ceiling configuration only supports 32-bit values */
+ if (max != (u32)max)
+ return -ERANGE;
+
+ mutex_lock(&qep->lock);
+ if (qep->enabled) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ pm_runtime_get_sync(qep->dev);
+ intel_qep_writel(qep, INTEL_QEPMAX, max);
+ pm_runtime_put(qep->dev);
+
+out:
+ mutex_unlock(&qep->lock);
+ return ret;
+}
+
+static int intel_qep_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct intel_qep *qep = counter_priv(counter);
+
+ *enable = qep->enabled;
+
+ return 0;
+}
+
+static int intel_qep_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 val)
+{
+ struct intel_qep *qep = counter_priv(counter);
+ u32 reg;
+ bool changed;
+
+ mutex_lock(&qep->lock);
+ changed = val ^ qep->enabled;
+ if (!changed)
+ goto out;
+
+ pm_runtime_get_sync(qep->dev);
+ reg = intel_qep_readl(qep, INTEL_QEPCON);
+ if (val) {
+ /* Enable peripheral and keep runtime PM always on */
+ reg |= INTEL_QEPCON_EN;
+ pm_runtime_get_noresume(qep->dev);
+ } else {
+ /* Let runtime PM be idle and disable peripheral */
+ pm_runtime_put_noidle(qep->dev);
+ reg &= ~INTEL_QEPCON_EN;
+ }
+ intel_qep_writel(qep, INTEL_QEPCON, reg);
+ pm_runtime_put(qep->dev);
+ qep->enabled = val;
+
+out:
+ mutex_unlock(&qep->lock);
+ return 0;
+}
+
+static int intel_qep_spike_filter_ns_read(struct counter_device *counter,
+ struct counter_count *count,
+ u64 *length)
+{
+ struct intel_qep *qep = counter_priv(counter);
+ u32 reg;
+
+ pm_runtime_get_sync(qep->dev);
+ reg = intel_qep_readl(qep, INTEL_QEPCON);
+ if (!(reg & INTEL_QEPCON_FLT_EN)) {
+ pm_runtime_put(qep->dev);
+ return 0;
+ }
+ reg = INTEL_QEPFLT_MAX_COUNT(intel_qep_readl(qep, INTEL_QEPFLT));
+ pm_runtime_put(qep->dev);
+
+ *length = (reg + 2) * INTEL_QEP_CLK_PERIOD_NS;
+
+ return 0;
+}
+
+static int intel_qep_spike_filter_ns_write(struct counter_device *counter,
+ struct counter_count *count,
+ u64 length)
+{
+ struct intel_qep *qep = counter_priv(counter);
+ u32 reg;
+ bool enable;
+ int ret = 0;
+
+ /*
+ * Spike filter length is (MAX_COUNT + 2) clock periods.
+ * Disable filter when userspace writes 0, enable for valid
+ * nanoseconds values and error out otherwise.
+ */
+ do_div(length, INTEL_QEP_CLK_PERIOD_NS);
+ if (length == 0) {
+ enable = false;
+ length = 0;
+ } else if (length >= 2) {
+ enable = true;
+ length -= 2;
+ } else {
+ return -EINVAL;
+ }
+
+ if (length > INTEL_QEPFLT_MAX_COUNT(length))
+ return -ERANGE;
+
+ mutex_lock(&qep->lock);
+ if (qep->enabled) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ pm_runtime_get_sync(qep->dev);
+ reg = intel_qep_readl(qep, INTEL_QEPCON);
+ if (enable)
+ reg |= INTEL_QEPCON_FLT_EN;
+ else
+ reg &= ~INTEL_QEPCON_FLT_EN;
+ intel_qep_writel(qep, INTEL_QEPFLT, length);
+ intel_qep_writel(qep, INTEL_QEPCON, reg);
+ pm_runtime_put(qep->dev);
+
+out:
+ mutex_unlock(&qep->lock);
+ return ret;
+}
+
+static int intel_qep_preset_enable_read(struct counter_device *counter,
+ struct counter_count *count,
+ u8 *preset_enable)
+{
+ struct intel_qep *qep = counter_priv(counter);
+ u32 reg;
+
+ pm_runtime_get_sync(qep->dev);
+ reg = intel_qep_readl(qep, INTEL_QEPCON);
+ pm_runtime_put(qep->dev);
+
+ *preset_enable = !(reg & INTEL_QEPCON_COUNT_RST_MODE);
+
+ return 0;
+}
+
+static int intel_qep_preset_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 val)
+{
+ struct intel_qep *qep = counter_priv(counter);
+ u32 reg;
+ int ret = 0;
+
+ mutex_lock(&qep->lock);
+ if (qep->enabled) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ pm_runtime_get_sync(qep->dev);
+ reg = intel_qep_readl(qep, INTEL_QEPCON);
+ if (val)
+ reg &= ~INTEL_QEPCON_COUNT_RST_MODE;
+ else
+ reg |= INTEL_QEPCON_COUNT_RST_MODE;
+
+ intel_qep_writel(qep, INTEL_QEPCON, reg);
+ pm_runtime_put(qep->dev);
+
+out:
+ mutex_unlock(&qep->lock);
+
+ return ret;
+}
+
+static struct counter_comp intel_qep_count_ext[] = {
+ COUNTER_COMP_ENABLE(intel_qep_enable_read, intel_qep_enable_write),
+ COUNTER_COMP_CEILING(intel_qep_ceiling_read, intel_qep_ceiling_write),
+ COUNTER_COMP_PRESET_ENABLE(intel_qep_preset_enable_read,
+ intel_qep_preset_enable_write),
+ COUNTER_COMP_COUNT_U64("spike_filter_ns",
+ intel_qep_spike_filter_ns_read,
+ intel_qep_spike_filter_ns_write),
+};
+
+static struct counter_count intel_qep_counter_count[] = {
+ {
+ .id = 0,
+ .name = "Channel 1 Count",
+ .functions_list = intel_qep_count_functions,
+ .num_functions = ARRAY_SIZE(intel_qep_count_functions),
+ .synapses = intel_qep_count_synapses,
+ .num_synapses = ARRAY_SIZE(intel_qep_count_synapses),
+ .ext = intel_qep_count_ext,
+ .num_ext = ARRAY_SIZE(intel_qep_count_ext),
+ },
+};
+
+static int intel_qep_probe(struct pci_dev *pci, const struct pci_device_id *id)
+{
+ struct counter_device *counter;
+ struct intel_qep *qep;
+ struct device *dev = &pci->dev;
+ void __iomem *regs;
+ int ret;
+
+ counter = devm_counter_alloc(dev, sizeof(*qep));
+ if (!counter)
+ return -ENOMEM;
+ qep = counter_priv(counter);
+
+ ret = pcim_enable_device(pci);
+ if (ret)
+ return ret;
+
+ pci_set_master(pci);
+
+ ret = pcim_iomap_regions(pci, BIT(0), pci_name(pci));
+ if (ret)
+ return ret;
+
+ regs = pcim_iomap_table(pci)[0];
+ if (!regs)
+ return -ENOMEM;
+
+ qep->dev = dev;
+ qep->regs = regs;
+ mutex_init(&qep->lock);
+
+ intel_qep_init(qep);
+ pci_set_drvdata(pci, qep);
+
+ counter->name = pci_name(pci);
+ counter->parent = dev;
+ counter->ops = &intel_qep_counter_ops;
+ counter->counts = intel_qep_counter_count;
+ counter->num_counts = ARRAY_SIZE(intel_qep_counter_count);
+ counter->signals = intel_qep_signals;
+ counter->num_signals = ARRAY_SIZE(intel_qep_signals);
+ qep->enabled = false;
+
+ pm_runtime_put(dev);
+ pm_runtime_allow(dev);
+
+ ret = devm_counter_add(&pci->dev, counter);
+ if (ret < 0)
+ return dev_err_probe(&pci->dev, ret, "Failed to add counter\n");
+
+ return 0;
+}
+
+static void intel_qep_remove(struct pci_dev *pci)
+{
+ struct intel_qep *qep = pci_get_drvdata(pci);
+ struct device *dev = &pci->dev;
+
+ pm_runtime_forbid(dev);
+ if (!qep->enabled)
+ pm_runtime_get(dev);
+
+ intel_qep_writel(qep, INTEL_QEPCON, 0);
+}
+
+static int __maybe_unused intel_qep_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct intel_qep *qep = pci_get_drvdata(pdev);
+
+ qep->qepcon = intel_qep_readl(qep, INTEL_QEPCON);
+ qep->qepflt = intel_qep_readl(qep, INTEL_QEPFLT);
+ qep->qepmax = intel_qep_readl(qep, INTEL_QEPMAX);
+
+ return 0;
+}
+
+static int __maybe_unused intel_qep_resume(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct intel_qep *qep = pci_get_drvdata(pdev);
+
+ /*
+ * Make sure peripheral is disabled when restoring registers and
+ * control register bits that are writable only when the peripheral
+ * is disabled
+ */
+ intel_qep_writel(qep, INTEL_QEPCON, 0);
+ intel_qep_readl(qep, INTEL_QEPCON);
+
+ intel_qep_writel(qep, INTEL_QEPFLT, qep->qepflt);
+ intel_qep_writel(qep, INTEL_QEPMAX, qep->qepmax);
+ intel_qep_writel(qep, INTEL_QEPINT_MASK, INTEL_QEPINT_MASK_ALL);
+
+ /* Restore all other control register bits except enable status */
+ intel_qep_writel(qep, INTEL_QEPCON, qep->qepcon & ~INTEL_QEPCON_EN);
+ intel_qep_readl(qep, INTEL_QEPCON);
+
+ /* Restore enable status */
+ intel_qep_writel(qep, INTEL_QEPCON, qep->qepcon);
+
+ return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(intel_qep_pm_ops,
+ intel_qep_suspend, intel_qep_resume, NULL);
+
+static const struct pci_device_id intel_qep_id_table[] = {
+ /* EHL */
+ { PCI_VDEVICE(INTEL, 0x4bc3), },
+ { PCI_VDEVICE(INTEL, 0x4b81), },
+ { PCI_VDEVICE(INTEL, 0x4b82), },
+ { PCI_VDEVICE(INTEL, 0x4b83), },
+ { } /* Terminating Entry */
+};
+MODULE_DEVICE_TABLE(pci, intel_qep_id_table);
+
+static struct pci_driver intel_qep_driver = {
+ .name = "intel-qep",
+ .id_table = intel_qep_id_table,
+ .probe = intel_qep_probe,
+ .remove = intel_qep_remove,
+ .driver = {
+ .pm = &intel_qep_pm_ops,
+ }
+};
+
+module_pci_driver(intel_qep_driver);
+
+MODULE_AUTHOR("Felipe Balbi (Intel)");
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@linux.intel.com>");
+MODULE_AUTHOR("Raymond Tan <raymond.tan@intel.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Intel Quadrature Encoder Peripheral driver");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/interrupt-cnt.c b/drivers/counter/interrupt-cnt.c
new file mode 100644
index 000000000..229473855
--- /dev/null
+++ b/drivers/counter/interrupt-cnt.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+ */
+
+#include <linux/counter.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#define INTERRUPT_CNT_NAME "interrupt-cnt"
+
+struct interrupt_cnt_priv {
+ atomic_t count;
+ struct gpio_desc *gpio;
+ int irq;
+ bool enabled;
+ struct counter_signal signals;
+ struct counter_synapse synapses;
+ struct counter_count cnts;
+};
+
+static irqreturn_t interrupt_cnt_isr(int irq, void *dev_id)
+{
+ struct counter_device *counter = dev_id;
+ struct interrupt_cnt_priv *priv = counter_priv(counter);
+
+ atomic_inc(&priv->count);
+
+ counter_push_event(counter, COUNTER_EVENT_CHANGE_OF_STATE, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int interrupt_cnt_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct interrupt_cnt_priv *priv = counter_priv(counter);
+
+ *enable = priv->enabled;
+
+ return 0;
+}
+
+static int interrupt_cnt_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct interrupt_cnt_priv *priv = counter_priv(counter);
+
+ if (priv->enabled == enable)
+ return 0;
+
+ if (enable) {
+ priv->enabled = true;
+ enable_irq(priv->irq);
+ } else {
+ disable_irq(priv->irq);
+ priv->enabled = false;
+ }
+
+ return 0;
+}
+
+static struct counter_comp interrupt_cnt_ext[] = {
+ COUNTER_COMP_ENABLE(interrupt_cnt_enable_read,
+ interrupt_cnt_enable_write),
+};
+
+static const enum counter_synapse_action interrupt_cnt_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+};
+
+static int interrupt_cnt_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+
+ return 0;
+}
+
+static int interrupt_cnt_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct interrupt_cnt_priv *priv = counter_priv(counter);
+
+ *val = atomic_read(&priv->count);
+
+ return 0;
+}
+
+static int interrupt_cnt_write(struct counter_device *counter,
+ struct counter_count *count, const u64 val)
+{
+ struct interrupt_cnt_priv *priv = counter_priv(counter);
+
+ if (val != (typeof(priv->count.counter))val)
+ return -ERANGE;
+
+ atomic_set(&priv->count, val);
+
+ return 0;
+}
+
+static const enum counter_function interrupt_cnt_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+};
+
+static int interrupt_cnt_function_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ *function = COUNTER_FUNCTION_INCREASE;
+
+ return 0;
+}
+
+static int interrupt_cnt_signal_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ enum counter_signal_level *level)
+{
+ struct interrupt_cnt_priv *priv = counter_priv(counter);
+ int ret;
+
+ if (!priv->gpio)
+ return -EINVAL;
+
+ ret = gpiod_get_value(priv->gpio);
+ if (ret < 0)
+ return ret;
+
+ *level = ret ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW;
+
+ return 0;
+}
+
+static int interrupt_cnt_watch_validate(struct counter_device *counter,
+ const struct counter_watch *watch)
+{
+ if (watch->channel != 0 ||
+ watch->event != COUNTER_EVENT_CHANGE_OF_STATE)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct counter_ops interrupt_cnt_ops = {
+ .action_read = interrupt_cnt_action_read,
+ .count_read = interrupt_cnt_read,
+ .count_write = interrupt_cnt_write,
+ .function_read = interrupt_cnt_function_read,
+ .signal_read = interrupt_cnt_signal_read,
+ .watch_validate = interrupt_cnt_watch_validate,
+};
+
+static int interrupt_cnt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct counter_device *counter;
+ struct interrupt_cnt_priv *priv;
+ int ret;
+
+ counter = devm_counter_alloc(dev, sizeof(*priv));
+ if (!counter)
+ return -ENOMEM;
+ priv = counter_priv(counter);
+
+ priv->irq = platform_get_irq_optional(pdev, 0);
+ if (priv->irq == -ENXIO)
+ priv->irq = 0;
+ else if (priv->irq < 0)
+ return dev_err_probe(dev, priv->irq, "failed to get IRQ\n");
+
+ priv->gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_IN);
+ if (IS_ERR(priv->gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->gpio), "failed to get GPIO\n");
+
+ if (!priv->irq && !priv->gpio) {
+ dev_err(dev, "IRQ and GPIO are not found. At least one source should be provided\n");
+ return -ENODEV;
+ }
+
+ if (!priv->irq) {
+ int irq = gpiod_to_irq(priv->gpio);
+
+ if (irq < 0)
+ return dev_err_probe(dev, irq, "failed to get IRQ from GPIO\n");
+
+ priv->irq = irq;
+ }
+
+ priv->signals.name = devm_kasprintf(dev, GFP_KERNEL, "IRQ %d",
+ priv->irq);
+ if (!priv->signals.name)
+ return -ENOMEM;
+
+ counter->signals = &priv->signals;
+ counter->num_signals = 1;
+
+ priv->synapses.actions_list = interrupt_cnt_synapse_actions;
+ priv->synapses.num_actions = ARRAY_SIZE(interrupt_cnt_synapse_actions);
+ priv->synapses.signal = &priv->signals;
+
+ priv->cnts.name = "Channel 0 Count";
+ priv->cnts.functions_list = interrupt_cnt_functions;
+ priv->cnts.num_functions = ARRAY_SIZE(interrupt_cnt_functions);
+ priv->cnts.synapses = &priv->synapses;
+ priv->cnts.num_synapses = 1;
+ priv->cnts.ext = interrupt_cnt_ext;
+ priv->cnts.num_ext = ARRAY_SIZE(interrupt_cnt_ext);
+
+ counter->name = dev_name(dev);
+ counter->parent = dev;
+ counter->ops = &interrupt_cnt_ops;
+ counter->counts = &priv->cnts;
+ counter->num_counts = 1;
+
+ irq_set_status_flags(priv->irq, IRQ_NOAUTOEN);
+ ret = devm_request_irq(dev, priv->irq, interrupt_cnt_isr,
+ IRQF_TRIGGER_RISING | IRQF_NO_THREAD,
+ dev_name(dev), counter);
+ if (ret)
+ return ret;
+
+ ret = devm_counter_add(dev, counter);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to add counter\n");
+
+ return 0;
+}
+
+static const struct of_device_id interrupt_cnt_of_match[] = {
+ { .compatible = "interrupt-counter", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, interrupt_cnt_of_match);
+
+static struct platform_driver interrupt_cnt_driver = {
+ .probe = interrupt_cnt_probe,
+ .driver = {
+ .name = INTERRUPT_CNT_NAME,
+ .of_match_table = interrupt_cnt_of_match,
+ },
+};
+module_platform_driver(interrupt_cnt_driver);
+
+MODULE_ALIAS("platform:interrupt-counter");
+MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
+MODULE_DESCRIPTION("Interrupt counter driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/microchip-tcb-capture.c b/drivers/counter/microchip-tcb-capture.c
new file mode 100644
index 000000000..c7af13aca
--- /dev/null
+++ b/drivers/counter/microchip-tcb-capture.c
@@ -0,0 +1,407 @@
+// 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 regmap *regmap;
+ int qdec_mode;
+ int num_channels;
+ int channel[2];
+};
+
+static const enum counter_function mchp_tc_count_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+ COUNTER_FUNCTION_QUADRATURE_X4,
+};
+
+static const enum counter_synapse_action mchp_tc_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_NONE,
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+ COUNTER_SYNAPSE_ACTION_FALLING_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_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ struct mchp_tc_data *const priv = counter_priv(counter);
+
+ if (priv->qdec_mode)
+ *function = COUNTER_FUNCTION_QUADRATURE_X4;
+ else
+ *function = COUNTER_FUNCTION_INCREASE;
+
+ return 0;
+}
+
+static int mchp_tc_count_function_write(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function function)
+{
+ struct mchp_tc_data *const priv = counter_priv(counter);
+ 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 COUNTER_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 COUNTER_FUNCTION_QUADRATURE_X4:
+ 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;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+
+ 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_level *lvl)
+{
+ struct mchp_tc_data *const priv = counter_priv(counter);
+ 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);
+
+ *lvl = sigstatus ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW;
+
+ return 0;
+}
+
+static int mchp_tc_count_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ struct mchp_tc_data *const priv = counter_priv(counter);
+ 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 = COUNTER_SYNAPSE_ACTION_NONE;
+ break;
+ case ATMEL_TC_ETRGEDG_RISING:
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ break;
+ case ATMEL_TC_ETRGEDG_FALLING:
+ *action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
+ break;
+ case ATMEL_TC_ETRGEDG_BOTH:
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ break;
+ }
+
+ return 0;
+}
+
+static int mchp_tc_count_action_write(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action action)
+{
+ struct mchp_tc_data *const priv = counter_priv(counter);
+ 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 COUNTER_SYNAPSE_ACTION_NONE:
+ edge = ATMEL_TC_ETRGEDG_NONE;
+ break;
+ case COUNTER_SYNAPSE_ACTION_RISING_EDGE:
+ edge = ATMEL_TC_ETRGEDG_RISING;
+ break;
+ case COUNTER_SYNAPSE_ACTION_FALLING_EDGE:
+ edge = ATMEL_TC_ETRGEDG_FALLING;
+ break;
+ case COUNTER_SYNAPSE_ACTION_BOTH_EDGES:
+ edge = ATMEL_TC_ETRGEDG_BOTH;
+ break;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+
+ 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, u64 *val)
+{
+ struct mchp_tc_data *const priv = counter_priv(counter);
+ 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_read = mchp_tc_count_function_read,
+ .function_write = mchp_tc_count_function_write,
+ .action_read = mchp_tc_count_action_read,
+ .action_write = mchp_tc_count_action_write
+};
+
+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 counter_device *counter;
+ struct mchp_tc_data *priv;
+ char clk_name[7];
+ struct regmap *regmap;
+ struct clk *clk[3];
+ int channel;
+ int ret, i;
+
+ counter = devm_counter_alloc(&pdev->dev, sizeof(*priv));
+ if (!counter)
+ return -ENOMEM;
+ priv = counter_priv(counter);
+
+ 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;
+ counter->name = dev_name(&pdev->dev);
+ counter->parent = &pdev->dev;
+ counter->ops = &mchp_tc_ops;
+ counter->num_counts = ARRAY_SIZE(mchp_tc_counts);
+ counter->counts = mchp_tc_counts;
+ counter->num_signals = ARRAY_SIZE(mchp_tc_count_signals);
+ counter->signals = mchp_tc_count_signals;
+
+ ret = devm_counter_add(&pdev->dev, counter);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
+
+ return 0;
+}
+
+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");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/stm32-lptimer-cnt.c b/drivers/counter/stm32-lptimer-cnt.c
new file mode 100644
index 000000000..843975555
--- /dev/null
+++ b/drivers/counter/stm32-lptimer-cnt.c
@@ -0,0 +1,523 @@
+// 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>
+#include <linux/types.h>
+
+struct stm32_lptim_cnt {
+ 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);
+}
+
+/*
+ * 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 |
+ * +---------+----------+----------+---------+----------+---------+
+ */
+static const enum counter_function stm32_lptim_cnt_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+ COUNTER_FUNCTION_QUADRATURE_X4,
+};
+
+static const enum counter_synapse_action stm32_lptim_cnt_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+ COUNTER_SYNAPSE_ACTION_FALLING_EDGE,
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
+ COUNTER_SYNAPSE_ACTION_NONE,
+};
+
+static int stm32_lptim_cnt_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+ 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_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+
+ if (!priv->quadrature_mode) {
+ *function = COUNTER_FUNCTION_INCREASE;
+ return 0;
+ }
+
+ if (priv->polarity == STM32_LPTIM_CKPOL_BOTH_EDGES) {
+ *function = COUNTER_FUNCTION_QUADRATURE_X4;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int stm32_lptim_cnt_function_write(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function function)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+
+ if (stm32_lptim_is_enabled(priv))
+ return -EBUSY;
+
+ switch (function) {
+ case COUNTER_FUNCTION_INCREASE:
+ priv->quadrature_mode = 0;
+ return 0;
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ priv->quadrature_mode = 1;
+ priv->polarity = STM32_LPTIM_CKPOL_BOTH_EDGES;
+ return 0;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+}
+
+static int stm32_lptim_cnt_enable_read(struct counter_device *counter,
+ struct counter_count *count,
+ u8 *enable)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+ int ret;
+
+ ret = stm32_lptim_is_enabled(priv);
+ if (ret < 0)
+ return ret;
+
+ *enable = ret;
+
+ return 0;
+}
+
+static int stm32_lptim_cnt_enable_write(struct counter_device *counter,
+ struct counter_count *count,
+ u8 enable)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+ int 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 0;
+}
+
+static int stm32_lptim_cnt_ceiling_read(struct counter_device *counter,
+ struct counter_count *count,
+ u64 *ceiling)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+
+ *ceiling = priv->ceiling;
+
+ return 0;
+}
+
+static int stm32_lptim_cnt_ceiling_write(struct counter_device *counter,
+ struct counter_count *count,
+ u64 ceiling)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+
+ if (stm32_lptim_is_enabled(priv))
+ return -EBUSY;
+
+ if (ceiling > STM32_LPTIM_MAX_ARR)
+ return -ERANGE;
+
+ priv->ceiling = ceiling;
+
+ return 0;
+}
+
+static struct counter_comp stm32_lptim_cnt_ext[] = {
+ COUNTER_COMP_ENABLE(stm32_lptim_cnt_enable_read,
+ stm32_lptim_cnt_enable_write),
+ COUNTER_COMP_CEILING(stm32_lptim_cnt_ceiling_read,
+ stm32_lptim_cnt_ceiling_write),
+};
+
+static int stm32_lptim_cnt_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+ enum counter_function function;
+ int err;
+
+ err = stm32_lptim_cnt_function_read(counter, count, &function);
+ if (err)
+ return err;
+
+ switch (function) {
+ case COUNTER_FUNCTION_INCREASE:
+ /* LP Timer acts as up-counter on input 1 */
+ if (synapse->signal->id != count->synapses[0].signal->id) {
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+ return 0;
+ }
+
+ switch (priv->polarity) {
+ case STM32_LPTIM_CKPOL_RISING_EDGE:
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ return 0;
+ case STM32_LPTIM_CKPOL_FALLING_EDGE:
+ *action = COUNTER_SYNAPSE_ACTION_FALLING_EDGE;
+ return 0;
+ case STM32_LPTIM_CKPOL_BOTH_EDGES:
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+}
+
+static int stm32_lptim_cnt_action_write(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action action)
+{
+ struct stm32_lptim_cnt *const priv = counter_priv(counter);
+ enum counter_function function;
+ int err;
+
+ if (stm32_lptim_is_enabled(priv))
+ return -EBUSY;
+
+ err = stm32_lptim_cnt_function_read(counter, count, &function);
+ if (err)
+ return err;
+
+ /* only set polarity when in counter mode (on input 1) */
+ if (function != COUNTER_FUNCTION_INCREASE
+ || synapse->signal->id != count->synapses[0].signal->id)
+ return -EINVAL;
+
+ switch (action) {
+ case COUNTER_SYNAPSE_ACTION_RISING_EDGE:
+ priv->polarity = STM32_LPTIM_CKPOL_RISING_EDGE;
+ return 0;
+ case COUNTER_SYNAPSE_ACTION_FALLING_EDGE:
+ priv->polarity = STM32_LPTIM_CKPOL_FALLING_EDGE;
+ return 0;
+ case COUNTER_SYNAPSE_ACTION_BOTH_EDGES:
+ priv->polarity = STM32_LPTIM_CKPOL_BOTH_EDGES;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct counter_ops stm32_lptim_cnt_ops = {
+ .count_read = stm32_lptim_cnt_read,
+ .function_read = stm32_lptim_cnt_function_read,
+ .function_write = stm32_lptim_cnt_function_write,
+ .action_read = stm32_lptim_cnt_action_read,
+ .action_write = stm32_lptim_cnt_action_write,
+};
+
+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 counter_device *counter;
+ struct stm32_lptim_cnt *priv;
+ int ret;
+
+ if (IS_ERR_OR_NULL(ddata))
+ return -EINVAL;
+
+ counter = devm_counter_alloc(&pdev->dev, sizeof(*priv));
+ if (!counter)
+ return -ENOMEM;
+ priv = counter_priv(counter);
+
+ priv->dev = &pdev->dev;
+ priv->regmap = ddata->regmap;
+ priv->clk = ddata->clk;
+ priv->ceiling = STM32_LPTIM_MAX_ARR;
+
+ /* Initialize Counter device */
+ counter->name = dev_name(&pdev->dev);
+ counter->parent = &pdev->dev;
+ counter->ops = &stm32_lptim_cnt_ops;
+ if (ddata->has_encoder) {
+ counter->counts = &stm32_lptim_enc_counts;
+ counter->num_signals = ARRAY_SIZE(stm32_lptim_cnt_signals);
+ } else {
+ counter->counts = &stm32_lptim_in1_counts;
+ counter->num_signals = 1;
+ }
+ counter->num_counts = 1;
+ counter->signals = stm32_lptim_cnt_signals;
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = devm_counter_add(&pdev->dev, counter);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n");
+
+ return 0;
+}
+
+#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");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/stm32-timer-cnt.c b/drivers/counter/stm32-timer-cnt.c
new file mode 100644
index 000000000..9bf20a5d6
--- /dev/null
+++ b/drivers/counter/stm32-timer-cnt.c
@@ -0,0 +1,420 @@
+// 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>
+#include <linux/types.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 regmap *regmap;
+ struct clk *clk;
+ u32 max_arr;
+ bool enabled;
+ struct stm32_timer_regs bak;
+};
+
+static const enum counter_function stm32_count_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+ COUNTER_FUNCTION_QUADRATURE_X2_A,
+ COUNTER_FUNCTION_QUADRATURE_X2_B,
+ COUNTER_FUNCTION_QUADRATURE_X4,
+};
+
+static int stm32_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ 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 u64 val)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ 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_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 smcr;
+
+ regmap_read(priv->regmap, TIM_SMCR, &smcr);
+
+ switch (smcr & TIM_SMCR_SMS) {
+ case TIM_SMCR_SMS_SLAVE_MODE_DISABLED:
+ *function = COUNTER_FUNCTION_INCREASE;
+ return 0;
+ case TIM_SMCR_SMS_ENCODER_MODE_1:
+ *function = COUNTER_FUNCTION_QUADRATURE_X2_A;
+ return 0;
+ case TIM_SMCR_SMS_ENCODER_MODE_2:
+ *function = COUNTER_FUNCTION_QUADRATURE_X2_B;
+ return 0;
+ case TIM_SMCR_SMS_ENCODER_MODE_3:
+ *function = COUNTER_FUNCTION_QUADRATURE_X4;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int stm32_count_function_write(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function function)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 cr1, sms;
+
+ switch (function) {
+ case COUNTER_FUNCTION_INCREASE:
+ sms = TIM_SMCR_SMS_SLAVE_MODE_DISABLED;
+ break;
+ case COUNTER_FUNCTION_QUADRATURE_X2_A:
+ sms = TIM_SMCR_SMS_ENCODER_MODE_1;
+ break;
+ case COUNTER_FUNCTION_QUADRATURE_X2_B:
+ sms = TIM_SMCR_SMS_ENCODER_MODE_2;
+ break;
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ sms = TIM_SMCR_SMS_ENCODER_MODE_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 int stm32_count_direction_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_count_direction *direction)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 cr1;
+
+ regmap_read(priv->regmap, TIM_CR1, &cr1);
+ *direction = (cr1 & TIM_CR1_DIR) ? COUNTER_COUNT_DIRECTION_BACKWARD :
+ COUNTER_COUNT_DIRECTION_FORWARD;
+
+ return 0;
+}
+
+static int stm32_count_ceiling_read(struct counter_device *counter,
+ struct counter_count *count, u64 *ceiling)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 arr;
+
+ regmap_read(priv->regmap, TIM_ARR, &arr);
+
+ *ceiling = arr;
+
+ return 0;
+}
+
+static int stm32_count_ceiling_write(struct counter_device *counter,
+ struct counter_count *count, u64 ceiling)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+
+ 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 0;
+}
+
+static int stm32_count_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 cr1;
+
+ regmap_read(priv->regmap, TIM_CR1, &cr1);
+
+ *enable = cr1 & TIM_CR1_CEN;
+
+ return 0;
+}
+
+static int stm32_count_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct stm32_timer_cnt *const priv = counter_priv(counter);
+ u32 cr1;
+
+ 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 0;
+}
+
+static struct counter_comp stm32_count_ext[] = {
+ COUNTER_COMP_DIRECTION(stm32_count_direction_read),
+ COUNTER_COMP_ENABLE(stm32_count_enable_read, stm32_count_enable_write),
+ COUNTER_COMP_CEILING(stm32_count_ceiling_read,
+ stm32_count_ceiling_write),
+};
+
+static const enum counter_synapse_action stm32_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_NONE,
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES
+};
+
+static int stm32_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ enum counter_function function;
+ int err;
+
+ err = stm32_count_function_read(counter, count, &function);
+ if (err)
+ return err;
+
+ switch (function) {
+ case COUNTER_FUNCTION_INCREASE:
+ /* counts on internal clock when CEN=1 */
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+ return 0;
+ case COUNTER_FUNCTION_QUADRATURE_X2_A:
+ /* counts up/down on TI1FP1 edge depending on TI2FP2 level */
+ if (synapse->signal->id == count->synapses[0].signal->id)
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+ return 0;
+ case COUNTER_FUNCTION_QUADRATURE_X2_B:
+ /* counts up/down on TI2FP2 edge depending on TI1FP1 level */
+ if (synapse->signal->id == count->synapses[1].signal->id)
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+ return 0;
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ /* counts up/down on both TI1FP1 and TI2FP2 edges */
+ *action = COUNTER_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_read = stm32_count_function_read,
+ .function_write = stm32_count_function_write,
+ .action_read = stm32_action_read,
+};
+
+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;
+ struct counter_device *counter;
+ int ret;
+
+ if (IS_ERR_OR_NULL(ddata))
+ return -EINVAL;
+
+ counter = devm_counter_alloc(dev, sizeof(*priv));
+ if (!counter)
+ return -ENOMEM;
+
+ priv = counter_priv(counter);
+
+ priv->regmap = ddata->regmap;
+ priv->clk = ddata->clk;
+ priv->max_arr = ddata->max_arr;
+
+ counter->name = dev_name(dev);
+ counter->parent = dev;
+ counter->ops = &stm32_timer_cnt_ops;
+ counter->counts = &stm32_counts;
+ counter->num_counts = 1;
+ counter->signals = stm32_signals;
+ counter->num_signals = ARRAY_SIZE(stm32_signals);
+
+ platform_set_drvdata(pdev, priv);
+
+ /* Register Counter device */
+ ret = devm_counter_add(dev, counter);
+ if (ret < 0)
+ dev_err_probe(dev, ret, "Failed to add counter\n");
+
+ return ret;
+}
+
+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");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/ti-ecap-capture.c b/drivers/counter/ti-ecap-capture.c
new file mode 100644
index 000000000..fb1cb1774
--- /dev/null
+++ b/drivers/counter/ti-ecap-capture.c
@@ -0,0 +1,615 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ECAP Capture driver
+ *
+ * Copyright (C) 2022 Julien Panis <jpanis@baylibre.com>
+ */
+
+#include <linux/atomic.h>
+#include <linux/clk.h>
+#include <linux/counter.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#define ECAP_DRV_NAME "ecap"
+
+/* ECAP event IDs */
+#define ECAP_CEVT1 0
+#define ECAP_CEVT2 1
+#define ECAP_CEVT3 2
+#define ECAP_CEVT4 3
+#define ECAP_CNTOVF 4
+
+#define ECAP_CEVT_LAST ECAP_CEVT4
+#define ECAP_NB_CEVT (ECAP_CEVT_LAST + 1)
+
+#define ECAP_EVT_LAST ECAP_CNTOVF
+#define ECAP_NB_EVT (ECAP_EVT_LAST + 1)
+
+/* Registers */
+#define ECAP_TSCNT_REG 0x00
+
+#define ECAP_CAP_REG(i) (((i) << 2) + 0x08)
+
+#define ECAP_ECCTL_REG 0x28
+#define ECAP_CAPPOL_BIT(i) BIT((i) << 1)
+#define ECAP_EV_MODE_MASK GENMASK(7, 0)
+#define ECAP_CAPLDEN_BIT BIT(8)
+#define ECAP_CONT_ONESHT_BIT BIT(16)
+#define ECAP_STOPVALUE_MASK GENMASK(18, 17)
+#define ECAP_TSCNTSTP_BIT BIT(20)
+#define ECAP_SYNCO_DIS_MASK GENMASK(23, 22)
+#define ECAP_CAP_APWM_BIT BIT(25)
+#define ECAP_ECCTL_EN_MASK (ECAP_CAPLDEN_BIT | ECAP_TSCNTSTP_BIT)
+#define ECAP_ECCTL_CFG_MASK (ECAP_SYNCO_DIS_MASK | ECAP_STOPVALUE_MASK \
+ | ECAP_ECCTL_EN_MASK | ECAP_CAP_APWM_BIT \
+ | ECAP_CONT_ONESHT_BIT)
+
+#define ECAP_ECINT_EN_FLG_REG 0x2c
+#define ECAP_EVT_EN_MASK GENMASK(ECAP_NB_EVT, ECAP_NB_CEVT)
+#define ECAP_EVT_FLG_BIT(i) BIT((i) + 17)
+
+#define ECAP_ECINT_CLR_FRC_REG 0x30
+#define ECAP_INT_CLR_BIT BIT(0)
+#define ECAP_EVT_CLR_BIT(i) BIT((i) + 1)
+#define ECAP_EVT_CLR_MASK GENMASK(ECAP_NB_EVT, 0)
+
+#define ECAP_PID_REG 0x5c
+
+/* ECAP signals */
+#define ECAP_CLOCK_SIG 0
+#define ECAP_INPUT_SIG 1
+
+static const struct regmap_config ecap_cnt_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = ECAP_PID_REG,
+};
+
+/**
+ * struct ecap_cnt_dev - device private data structure
+ * @enabled: device state
+ * @lock: synchronization lock to prevent I/O race conditions
+ * @clk: device clock
+ * @regmap: device register map
+ * @nb_ovf: number of overflows since capture start
+ * @pm_ctx: device context for PM operations
+ * @pm_ctx.ev_mode: event mode bits
+ * @pm_ctx.time_cntr: timestamp counter value
+ */
+struct ecap_cnt_dev {
+ bool enabled;
+ struct mutex lock;
+ struct clk *clk;
+ struct regmap *regmap;
+ atomic_t nb_ovf;
+ struct {
+ u8 ev_mode;
+ u32 time_cntr;
+ } pm_ctx;
+};
+
+static u8 ecap_cnt_capture_get_evmode(struct counter_device *counter)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+ unsigned int regval;
+
+ pm_runtime_get_sync(counter->parent);
+ regmap_read(ecap_dev->regmap, ECAP_ECCTL_REG, &regval);
+ pm_runtime_put_sync(counter->parent);
+
+ return regval;
+}
+
+static void ecap_cnt_capture_set_evmode(struct counter_device *counter, u8 ev_mode)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ pm_runtime_get_sync(counter->parent);
+ regmap_update_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_EV_MODE_MASK, ev_mode);
+ pm_runtime_put_sync(counter->parent);
+}
+
+static void ecap_cnt_capture_enable(struct counter_device *counter)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ pm_runtime_get_sync(counter->parent);
+
+ /* Enable interrupts on events */
+ regmap_update_bits(ecap_dev->regmap, ECAP_ECINT_EN_FLG_REG,
+ ECAP_EVT_EN_MASK, ECAP_EVT_EN_MASK);
+
+ /* Run counter */
+ regmap_update_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_ECCTL_CFG_MASK,
+ ECAP_SYNCO_DIS_MASK | ECAP_STOPVALUE_MASK | ECAP_ECCTL_EN_MASK);
+}
+
+static void ecap_cnt_capture_disable(struct counter_device *counter)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ /* Stop counter */
+ regmap_update_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_ECCTL_EN_MASK, 0);
+
+ /* Disable interrupts on events */
+ regmap_update_bits(ecap_dev->regmap, ECAP_ECINT_EN_FLG_REG, ECAP_EVT_EN_MASK, 0);
+
+ pm_runtime_put_sync(counter->parent);
+}
+
+static u32 ecap_cnt_count_get_val(struct counter_device *counter, unsigned int reg)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+ unsigned int regval;
+
+ pm_runtime_get_sync(counter->parent);
+ regmap_read(ecap_dev->regmap, reg, &regval);
+ pm_runtime_put_sync(counter->parent);
+
+ return regval;
+}
+
+static void ecap_cnt_count_set_val(struct counter_device *counter, unsigned int reg, u32 val)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ pm_runtime_get_sync(counter->parent);
+ regmap_write(ecap_dev->regmap, reg, val);
+ pm_runtime_put_sync(counter->parent);
+}
+
+static int ecap_cnt_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ *val = ecap_cnt_count_get_val(counter, ECAP_TSCNT_REG);
+
+ return 0;
+}
+
+static int ecap_cnt_count_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ if (val > U32_MAX)
+ return -ERANGE;
+
+ ecap_cnt_count_set_val(counter, ECAP_TSCNT_REG, val);
+
+ return 0;
+}
+
+static int ecap_cnt_function_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ *function = COUNTER_FUNCTION_INCREASE;
+
+ return 0;
+}
+
+static int ecap_cnt_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ *action = (synapse->signal->id == ECAP_CLOCK_SIG) ?
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE :
+ COUNTER_SYNAPSE_ACTION_NONE;
+
+ return 0;
+}
+
+static int ecap_cnt_watch_validate(struct counter_device *counter,
+ const struct counter_watch *watch)
+{
+ if (watch->channel > ECAP_CEVT_LAST)
+ return -EINVAL;
+
+ switch (watch->event) {
+ case COUNTER_EVENT_CAPTURE:
+ case COUNTER_EVENT_OVERFLOW:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ecap_cnt_clk_get_freq(struct counter_device *counter,
+ struct counter_signal *signal, u64 *freq)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ *freq = clk_get_rate(ecap_dev->clk);
+
+ return 0;
+}
+
+static int ecap_cnt_pol_read(struct counter_device *counter,
+ struct counter_signal *signal,
+ size_t idx, enum counter_signal_polarity *pol)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+ int bitval;
+
+ pm_runtime_get_sync(counter->parent);
+ bitval = regmap_test_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_CAPPOL_BIT(idx));
+ pm_runtime_put_sync(counter->parent);
+
+ *pol = bitval ? COUNTER_SIGNAL_POLARITY_NEGATIVE : COUNTER_SIGNAL_POLARITY_POSITIVE;
+
+ return 0;
+}
+
+static int ecap_cnt_pol_write(struct counter_device *counter,
+ struct counter_signal *signal,
+ size_t idx, enum counter_signal_polarity pol)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ pm_runtime_get_sync(counter->parent);
+ if (pol == COUNTER_SIGNAL_POLARITY_NEGATIVE)
+ regmap_set_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_CAPPOL_BIT(idx));
+ else
+ regmap_clear_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_CAPPOL_BIT(idx));
+ pm_runtime_put_sync(counter->parent);
+
+ return 0;
+}
+
+static int ecap_cnt_cap_read(struct counter_device *counter,
+ struct counter_count *count,
+ size_t idx, u64 *cap)
+{
+ *cap = ecap_cnt_count_get_val(counter, ECAP_CAP_REG(idx));
+
+ return 0;
+}
+
+static int ecap_cnt_cap_write(struct counter_device *counter,
+ struct counter_count *count,
+ size_t idx, u64 cap)
+{
+ if (cap > U32_MAX)
+ return -ERANGE;
+
+ ecap_cnt_count_set_val(counter, ECAP_CAP_REG(idx), cap);
+
+ return 0;
+}
+
+static int ecap_cnt_nb_ovf_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ *val = atomic_read(&ecap_dev->nb_ovf);
+
+ return 0;
+}
+
+static int ecap_cnt_nb_ovf_write(struct counter_device *counter,
+ struct counter_count *count, u64 val)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ if (val > U32_MAX)
+ return -ERANGE;
+
+ atomic_set(&ecap_dev->nb_ovf, val);
+
+ return 0;
+}
+
+static int ecap_cnt_ceiling_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ *val = U32_MAX;
+
+ return 0;
+}
+
+static int ecap_cnt_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ *enable = ecap_dev->enabled;
+
+ return 0;
+}
+
+static int ecap_cnt_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
+
+ mutex_lock(&ecap_dev->lock);
+
+ if (enable == ecap_dev->enabled)
+ goto out;
+
+ if (enable)
+ ecap_cnt_capture_enable(counter);
+ else
+ ecap_cnt_capture_disable(counter);
+ ecap_dev->enabled = enable;
+
+out:
+ mutex_unlock(&ecap_dev->lock);
+
+ return 0;
+}
+
+static const struct counter_ops ecap_cnt_ops = {
+ .count_read = ecap_cnt_count_read,
+ .count_write = ecap_cnt_count_write,
+ .function_read = ecap_cnt_function_read,
+ .action_read = ecap_cnt_action_read,
+ .watch_validate = ecap_cnt_watch_validate,
+};
+
+static const enum counter_function ecap_cnt_functions[] = {
+ COUNTER_FUNCTION_INCREASE,
+};
+
+static const enum counter_synapse_action ecap_cnt_clock_actions[] = {
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+};
+
+static const enum counter_synapse_action ecap_cnt_input_actions[] = {
+ COUNTER_SYNAPSE_ACTION_NONE,
+};
+
+static struct counter_comp ecap_cnt_clock_ext[] = {
+ COUNTER_COMP_SIGNAL_U64("frequency", ecap_cnt_clk_get_freq, NULL),
+};
+
+static const enum counter_signal_polarity ecap_cnt_pol_avail[] = {
+ COUNTER_SIGNAL_POLARITY_POSITIVE,
+ COUNTER_SIGNAL_POLARITY_NEGATIVE,
+};
+
+static DEFINE_COUNTER_AVAILABLE(ecap_cnt_pol_available, ecap_cnt_pol_avail);
+static DEFINE_COUNTER_ARRAY_POLARITY(ecap_cnt_pol_array, ecap_cnt_pol_available, ECAP_NB_CEVT);
+
+static struct counter_comp ecap_cnt_signal_ext[] = {
+ COUNTER_COMP_ARRAY_POLARITY(ecap_cnt_pol_read, ecap_cnt_pol_write, ecap_cnt_pol_array),
+};
+
+static struct counter_signal ecap_cnt_signals[] = {
+ {
+ .id = ECAP_CLOCK_SIG,
+ .name = "Clock Signal",
+ .ext = ecap_cnt_clock_ext,
+ .num_ext = ARRAY_SIZE(ecap_cnt_clock_ext),
+ },
+ {
+ .id = ECAP_INPUT_SIG,
+ .name = "Input Signal",
+ .ext = ecap_cnt_signal_ext,
+ .num_ext = ARRAY_SIZE(ecap_cnt_signal_ext),
+ },
+};
+
+static struct counter_synapse ecap_cnt_synapses[] = {
+ {
+ .actions_list = ecap_cnt_clock_actions,
+ .num_actions = ARRAY_SIZE(ecap_cnt_clock_actions),
+ .signal = &ecap_cnt_signals[ECAP_CLOCK_SIG],
+ },
+ {
+ .actions_list = ecap_cnt_input_actions,
+ .num_actions = ARRAY_SIZE(ecap_cnt_input_actions),
+ .signal = &ecap_cnt_signals[ECAP_INPUT_SIG],
+ },
+};
+
+static DEFINE_COUNTER_ARRAY_CAPTURE(ecap_cnt_cap_array, ECAP_NB_CEVT);
+
+static struct counter_comp ecap_cnt_count_ext[] = {
+ COUNTER_COMP_ARRAY_CAPTURE(ecap_cnt_cap_read, ecap_cnt_cap_write, ecap_cnt_cap_array),
+ COUNTER_COMP_COUNT_U64("num_overflows", ecap_cnt_nb_ovf_read, ecap_cnt_nb_ovf_write),
+ COUNTER_COMP_CEILING(ecap_cnt_ceiling_read, NULL),
+ COUNTER_COMP_ENABLE(ecap_cnt_enable_read, ecap_cnt_enable_write),
+};
+
+static struct counter_count ecap_cnt_counts[] = {
+ {
+ .name = "Timestamp Counter",
+ .functions_list = ecap_cnt_functions,
+ .num_functions = ARRAY_SIZE(ecap_cnt_functions),
+ .synapses = ecap_cnt_synapses,
+ .num_synapses = ARRAY_SIZE(ecap_cnt_synapses),
+ .ext = ecap_cnt_count_ext,
+ .num_ext = ARRAY_SIZE(ecap_cnt_count_ext),
+ },
+};
+
+static irqreturn_t ecap_cnt_isr(int irq, void *dev_id)
+{
+ struct counter_device *counter_dev = dev_id;
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
+ unsigned int clr = 0;
+ unsigned int flg;
+ int i;
+
+ regmap_read(ecap_dev->regmap, ECAP_ECINT_EN_FLG_REG, &flg);
+
+ /* Check capture events */
+ for (i = 0 ; i < ECAP_NB_CEVT ; i++) {
+ if (flg & ECAP_EVT_FLG_BIT(i)) {
+ counter_push_event(counter_dev, COUNTER_EVENT_CAPTURE, i);
+ clr |= ECAP_EVT_CLR_BIT(i);
+ }
+ }
+
+ /* Check counter overflow */
+ if (flg & ECAP_EVT_FLG_BIT(ECAP_CNTOVF)) {
+ atomic_inc(&ecap_dev->nb_ovf);
+ for (i = 0 ; i < ECAP_NB_CEVT ; i++)
+ counter_push_event(counter_dev, COUNTER_EVENT_OVERFLOW, i);
+ clr |= ECAP_EVT_CLR_BIT(ECAP_CNTOVF);
+ }
+
+ clr |= ECAP_INT_CLR_BIT;
+ regmap_update_bits(ecap_dev->regmap, ECAP_ECINT_CLR_FRC_REG, ECAP_EVT_CLR_MASK, clr);
+
+ return IRQ_HANDLED;
+}
+
+static void ecap_cnt_pm_disable(void *dev)
+{
+ pm_runtime_disable(dev);
+}
+
+static int ecap_cnt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ecap_cnt_dev *ecap_dev;
+ struct counter_device *counter_dev;
+ void __iomem *mmio_base;
+ unsigned long clk_rate;
+ int ret;
+
+ counter_dev = devm_counter_alloc(dev, sizeof(*ecap_dev));
+ if (!counter_dev)
+ return -ENOMEM;
+
+ counter_dev->name = ECAP_DRV_NAME;
+ counter_dev->parent = dev;
+ counter_dev->ops = &ecap_cnt_ops;
+ counter_dev->signals = ecap_cnt_signals;
+ counter_dev->num_signals = ARRAY_SIZE(ecap_cnt_signals);
+ counter_dev->counts = ecap_cnt_counts;
+ counter_dev->num_counts = ARRAY_SIZE(ecap_cnt_counts);
+
+ ecap_dev = counter_priv(counter_dev);
+
+ mutex_init(&ecap_dev->lock);
+
+ ecap_dev->clk = devm_clk_get_enabled(dev, "fck");
+ if (IS_ERR(ecap_dev->clk))
+ return dev_err_probe(dev, PTR_ERR(ecap_dev->clk), "failed to get clock\n");
+
+ clk_rate = clk_get_rate(ecap_dev->clk);
+ if (!clk_rate) {
+ dev_err(dev, "failed to get clock rate\n");
+ return -EINVAL;
+ }
+
+ mmio_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mmio_base))
+ return PTR_ERR(mmio_base);
+
+ ecap_dev->regmap = devm_regmap_init_mmio(dev, mmio_base, &ecap_cnt_regmap_config);
+ if (IS_ERR(ecap_dev->regmap))
+ return dev_err_probe(dev, PTR_ERR(ecap_dev->regmap), "failed to init regmap\n");
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get irq\n");
+
+ ret = devm_request_irq(dev, ret, ecap_cnt_isr, 0, pdev->name, counter_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request irq\n");
+
+ platform_set_drvdata(pdev, counter_dev);
+
+ pm_runtime_enable(dev);
+
+ /* Register a cleanup callback to care for disabling PM */
+ ret = devm_add_action_or_reset(dev, ecap_cnt_pm_disable, dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add pm disable action\n");
+
+ ret = devm_counter_add(dev, counter_dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add counter\n");
+
+ return 0;
+}
+
+static int ecap_cnt_remove(struct platform_device *pdev)
+{
+ struct counter_device *counter_dev = platform_get_drvdata(pdev);
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
+
+ if (ecap_dev->enabled)
+ ecap_cnt_capture_disable(counter_dev);
+
+ return 0;
+}
+
+static int ecap_cnt_suspend(struct device *dev)
+{
+ struct counter_device *counter_dev = dev_get_drvdata(dev);
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
+
+ /* If eCAP is running, stop capture then save timestamp counter */
+ if (ecap_dev->enabled) {
+ /*
+ * Disabling capture has the following effects:
+ * - interrupts are disabled
+ * - loading of capture registers is disabled
+ * - timebase counter is stopped
+ */
+ ecap_cnt_capture_disable(counter_dev);
+ ecap_dev->pm_ctx.time_cntr = ecap_cnt_count_get_val(counter_dev, ECAP_TSCNT_REG);
+ }
+
+ ecap_dev->pm_ctx.ev_mode = ecap_cnt_capture_get_evmode(counter_dev);
+
+ clk_disable(ecap_dev->clk);
+
+ return 0;
+}
+
+static int ecap_cnt_resume(struct device *dev)
+{
+ struct counter_device *counter_dev = dev_get_drvdata(dev);
+ struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
+
+ clk_enable(ecap_dev->clk);
+
+ ecap_cnt_capture_set_evmode(counter_dev, ecap_dev->pm_ctx.ev_mode);
+
+ /* If eCAP was running, restore timestamp counter then run capture */
+ if (ecap_dev->enabled) {
+ ecap_cnt_count_set_val(counter_dev, ECAP_TSCNT_REG, ecap_dev->pm_ctx.time_cntr);
+ ecap_cnt_capture_enable(counter_dev);
+ }
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(ecap_cnt_pm_ops, ecap_cnt_suspend, ecap_cnt_resume);
+
+static const struct of_device_id ecap_cnt_of_match[] = {
+ { .compatible = "ti,am62-ecap-capture" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ecap_cnt_of_match);
+
+static struct platform_driver ecap_cnt_driver = {
+ .probe = ecap_cnt_probe,
+ .remove = ecap_cnt_remove,
+ .driver = {
+ .name = "ecap-capture",
+ .of_match_table = ecap_cnt_of_match,
+ .pm = pm_sleep_ptr(&ecap_cnt_pm_ops),
+ },
+};
+module_platform_driver(ecap_cnt_driver);
+
+MODULE_DESCRIPTION("ECAP Capture driver");
+MODULE_AUTHOR("Julien Panis <jpanis@baylibre.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(COUNTER);
diff --git a/drivers/counter/ti-eqep.c b/drivers/counter/ti-eqep.c
new file mode 100644
index 000000000..b0f24cf3e
--- /dev/null
+++ b/drivers/counter/ti-eqep.c
@@ -0,0 +1,459 @@
+// 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>
+#include <linux/types.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 {
+ TI_EQEP_COUNT_FUNC_QUAD_COUNT,
+ TI_EQEP_COUNT_FUNC_DIR_COUNT,
+ TI_EQEP_COUNT_FUNC_UP_COUNT,
+ TI_EQEP_COUNT_FUNC_DOWN_COUNT,
+};
+
+struct ti_eqep_cnt {
+ struct counter_device counter;
+ struct regmap *regmap32;
+ struct regmap *regmap16;
+};
+
+static struct ti_eqep_cnt *ti_eqep_count_from_counter(struct counter_device *counter)
+{
+ return counter_priv(counter);
+}
+
+static int ti_eqep_count_read(struct counter_device *counter,
+ struct counter_count *count, u64 *val)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+ 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, u64 val)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+ 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_read(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function *function)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+ u32 qdecctl;
+
+ regmap_read(priv->regmap16, QDECCTL, &qdecctl);
+
+ switch ((qdecctl & QDECCTL_QSRC) >> QDECCTL_QSRC_SHIFT) {
+ case TI_EQEP_COUNT_FUNC_QUAD_COUNT:
+ *function = COUNTER_FUNCTION_QUADRATURE_X4;
+ break;
+ case TI_EQEP_COUNT_FUNC_DIR_COUNT:
+ *function = COUNTER_FUNCTION_PULSE_DIRECTION;
+ break;
+ case TI_EQEP_COUNT_FUNC_UP_COUNT:
+ *function = COUNTER_FUNCTION_INCREASE;
+ break;
+ case TI_EQEP_COUNT_FUNC_DOWN_COUNT:
+ *function = COUNTER_FUNCTION_DECREASE;
+ break;
+ }
+
+ return 0;
+}
+
+static int ti_eqep_function_write(struct counter_device *counter,
+ struct counter_count *count,
+ enum counter_function function)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+ enum ti_eqep_count_func qsrc;
+
+ switch (function) {
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ qsrc = TI_EQEP_COUNT_FUNC_QUAD_COUNT;
+ break;
+ case COUNTER_FUNCTION_PULSE_DIRECTION:
+ qsrc = TI_EQEP_COUNT_FUNC_DIR_COUNT;
+ break;
+ case COUNTER_FUNCTION_INCREASE:
+ qsrc = TI_EQEP_COUNT_FUNC_UP_COUNT;
+ break;
+ case COUNTER_FUNCTION_DECREASE:
+ qsrc = TI_EQEP_COUNT_FUNC_DOWN_COUNT;
+ break;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+
+ return regmap_write_bits(priv->regmap16, QDECCTL, QDECCTL_QSRC,
+ qsrc << QDECCTL_QSRC_SHIFT);
+}
+
+static int ti_eqep_action_read(struct counter_device *counter,
+ struct counter_count *count,
+ struct counter_synapse *synapse,
+ enum counter_synapse_action *action)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+ enum counter_function function;
+ u32 qdecctl;
+ int err;
+
+ err = ti_eqep_function_read(counter, count, &function);
+ if (err)
+ return err;
+
+ switch (function) {
+ case COUNTER_FUNCTION_QUADRATURE_X4:
+ /* In quadrature mode, the rising and falling edge of both
+ * QEPA and QEPB trigger QCLK.
+ */
+ *action = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ return 0;
+ case COUNTER_FUNCTION_PULSE_DIRECTION:
+ /* 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 = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ return 0;
+ case TI_EQEP_SIGNAL_QEPB:
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+ return 0;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+ case COUNTER_FUNCTION_INCREASE:
+ case COUNTER_FUNCTION_DECREASE:
+ /* 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 = COUNTER_SYNAPSE_ACTION_BOTH_EDGES;
+ else
+ *action = COUNTER_SYNAPSE_ACTION_RISING_EDGE;
+ return 0;
+ case TI_EQEP_SIGNAL_QEPB:
+ *action = COUNTER_SYNAPSE_ACTION_NONE;
+ return 0;
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+ default:
+ /* should never reach this path */
+ return -EINVAL;
+ }
+}
+
+static const struct counter_ops ti_eqep_counter_ops = {
+ .count_read = ti_eqep_count_read,
+ .count_write = ti_eqep_count_write,
+ .function_read = ti_eqep_function_read,
+ .function_write = ti_eqep_function_write,
+ .action_read = ti_eqep_action_read,
+};
+
+static int ti_eqep_position_ceiling_read(struct counter_device *counter,
+ struct counter_count *count,
+ u64 *ceiling)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+ u32 qposmax;
+
+ regmap_read(priv->regmap32, QPOSMAX, &qposmax);
+
+ *ceiling = qposmax;
+
+ return 0;
+}
+
+static int ti_eqep_position_ceiling_write(struct counter_device *counter,
+ struct counter_count *count,
+ u64 ceiling)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+
+ if (ceiling != (u32)ceiling)
+ return -ERANGE;
+
+ regmap_write(priv->regmap32, QPOSMAX, ceiling);
+
+ return 0;
+}
+
+static int ti_eqep_position_enable_read(struct counter_device *counter,
+ struct counter_count *count, u8 *enable)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+ u32 qepctl;
+
+ regmap_read(priv->regmap16, QEPCTL, &qepctl);
+
+ *enable = !!(qepctl & QEPCTL_PHEN);
+
+ return 0;
+}
+
+static int ti_eqep_position_enable_write(struct counter_device *counter,
+ struct counter_count *count, u8 enable)
+{
+ struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
+
+ regmap_write_bits(priv->regmap16, QEPCTL, QEPCTL_PHEN, enable ? -1 : 0);
+
+ return 0;
+}
+
+static struct counter_comp ti_eqep_position_ext[] = {
+ COUNTER_COMP_CEILING(ti_eqep_position_ceiling_read,
+ ti_eqep_position_ceiling_write),
+ COUNTER_COMP_ENABLE(ti_eqep_position_enable_read,
+ 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_function ti_eqep_position_functions[] = {
+ COUNTER_FUNCTION_QUADRATURE_X4,
+ COUNTER_FUNCTION_PULSE_DIRECTION,
+ COUNTER_FUNCTION_INCREASE,
+ COUNTER_FUNCTION_DECREASE,
+};
+
+static const enum counter_synapse_action ti_eqep_position_synapse_actions[] = {
+ COUNTER_SYNAPSE_ACTION_BOTH_EDGES,
+ COUNTER_SYNAPSE_ACTION_RISING_EDGE,
+ 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 counter_device *counter;
+ struct ti_eqep_cnt *priv;
+ void __iomem *base;
+ int err;
+
+ counter = devm_counter_alloc(dev, sizeof(*priv));
+ if (!counter)
+ return -ENOMEM;
+ priv = counter_priv(counter);
+
+ 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);
+
+ counter->name = dev_name(dev);
+ counter->parent = dev;
+ counter->ops = &ti_eqep_counter_ops;
+ counter->counts = ti_eqep_counts;
+ counter->num_counts = ARRAY_SIZE(ti_eqep_counts);
+ counter->signals = ti_eqep_signals;
+ counter->num_signals = ARRAY_SIZE(ti_eqep_signals);
+
+ platform_set_drvdata(pdev, counter);
+
+ /*
+ * 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_add(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 counter_device *counter = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ counter_unregister(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");
+MODULE_IMPORT_NS(COUNTER);