counter: ti-eqep: implement over/underflow events
authorDavid Lechner <dlechner@baylibre.com>
Sun, 9 Jun 2024 21:49:33 +0000 (16:49 -0500)
committerWilliam Breathitt Gray <wbg@kernel.org>
Mon, 1 Jul 2024 01:31:15 +0000 (10:31 +0900)
This adds support to the TI eQEP counter driver for subscribing to
overflow and underflow events using the counter chrdev interface.

Signed-off-by: David Lechner <dlechner@baylibre.com>
Link: https://lore.kernel.org/r/20240609-counter-ti-eqep-over-under-events-v1-1-74fe1632f5ab@baylibre.com
Signed-off-by: William Breathitt Gray <wbg@kernel.org>
drivers/counter/ti-eqep.c

index 825ae22..a27622e 100644 (file)
@@ -8,6 +8,7 @@
 #include <linux/bitops.h>
 #include <linux/clk.h>
 #include <linux/counter.h>
+#include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/mod_devicetable.h>
 #include <linux/module.h>
 #define QEPCTL_UTE             BIT(1)
 #define QEPCTL_WDE             BIT(0)
 
+#define QEINT_UTO              BIT(11)
+#define QEINT_IEL              BIT(10)
+#define QEINT_SEL              BIT(9)
+#define QEINT_PCM              BIT(8)
+#define QEINT_PCR              BIT(7)
+#define QEINT_PCO              BIT(6)
+#define QEINT_PCU              BIT(5)
+#define QEINT_WTO              BIT(4)
+#define QEINT_QDC              BIT(3)
+#define QEINT_PHE              BIT(2)
+#define QEINT_PCE              BIT(1)
+
+#define QFLG_UTO               BIT(11)
+#define QFLG_IEL               BIT(10)
+#define QFLG_SEL               BIT(9)
+#define QFLG_PCM               BIT(8)
+#define QFLG_PCR               BIT(7)
+#define QFLG_PCO               BIT(6)
+#define QFLG_PCU               BIT(5)
+#define QFLG_WTO               BIT(4)
+#define QFLG_QDC               BIT(3)
+#define QFLG_PHE               BIT(2)
+#define QFLG_PCE               BIT(1)
+#define QFLG_INT               BIT(0)
+
+#define QCLR_UTO               BIT(11)
+#define QCLR_IEL               BIT(10)
+#define QCLR_SEL               BIT(9)
+#define QCLR_PCM               BIT(8)
+#define QCLR_PCR               BIT(7)
+#define QCLR_PCO               BIT(6)
+#define QCLR_PCU               BIT(5)
+#define QCLR_WTO               BIT(4)
+#define QCLR_QDC               BIT(3)
+#define QCLR_PHE               BIT(2)
+#define QCLR_PCE               BIT(1)
+#define QCLR_INT               BIT(0)
+
 /* EQEP Inputs */
 enum {
        TI_EQEP_SIGNAL_QEPA,    /* QEPA/XCLK */
@@ -239,12 +278,49 @@ static int ti_eqep_action_read(struct counter_device *counter,
        }
 }
 
+static int ti_eqep_events_configure(struct counter_device *counter)
+{
+       struct ti_eqep_cnt *priv = counter_priv(counter);
+       struct counter_event_node *event_node;
+       u32 qeint = 0;
+
+       list_for_each_entry(event_node, &counter->events_list, l) {
+               switch (event_node->event) {
+               case COUNTER_EVENT_OVERFLOW:
+                       qeint |= QEINT_PCO;
+                       break;
+               case COUNTER_EVENT_UNDERFLOW:
+                       qeint |= QEINT_PCU;
+                       break;
+               }
+       }
+
+       return regmap_write(priv->regmap16, QEINT, qeint);
+}
+
+static int ti_eqep_watch_validate(struct counter_device *counter,
+                                 const struct counter_watch *watch)
+{
+       switch (watch->event) {
+       case COUNTER_EVENT_OVERFLOW:
+       case COUNTER_EVENT_UNDERFLOW:
+               if (watch->channel != 0)
+                       return -EINVAL;
+
+               return 0;
+       default:
+               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,
+       .events_configure = ti_eqep_events_configure,
+       .watch_validate = ti_eqep_watch_validate,
 };
 
 static int ti_eqep_position_ceiling_read(struct counter_device *counter,
@@ -355,6 +431,25 @@ static struct counter_count ti_eqep_counts[] = {
        },
 };
 
+static irqreturn_t ti_eqep_irq_handler(int irq, void *dev_id)
+{
+       struct counter_device *counter = dev_id;
+       struct ti_eqep_cnt *priv = counter_priv(counter);
+       u32 qflg;
+
+       regmap_read(priv->regmap16, QFLG, &qflg);
+
+       if (qflg & QFLG_PCO)
+               counter_push_event(counter, COUNTER_EVENT_OVERFLOW, 0);
+
+       if (qflg & QFLG_PCU)
+               counter_push_event(counter, COUNTER_EVENT_UNDERFLOW, 0);
+
+       regmap_write(priv->regmap16, QCLR, qflg);
+
+       return IRQ_HANDLED;
+}
+
 static const struct regmap_config ti_eqep_regmap32_config = {
        .name = "32-bit",
        .reg_bits = 32,
@@ -378,7 +473,7 @@ static int ti_eqep_probe(struct platform_device *pdev)
        struct ti_eqep_cnt *priv;
        void __iomem *base;
        struct clk *clk;
-       int err;
+       int err, irq;
 
        counter = devm_counter_alloc(dev, sizeof(*priv));
        if (!counter)
@@ -399,6 +494,15 @@ static int ti_eqep_probe(struct platform_device *pdev)
        if (IS_ERR(priv->regmap16))
                return PTR_ERR(priv->regmap16);
 
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       err = devm_request_threaded_irq(dev, irq, NULL, ti_eqep_irq_handler,
+                                       IRQF_ONESHOT, dev_name(dev), counter);
+       if (err < 0)
+               return dev_err_probe(dev, err, "failed to request IRQ\n");
+
        counter->name = dev_name(dev);
        counter->parent = dev;
        counter->ops = &ti_eqep_counter_ops;