linux/drivers/counter/interrupt-cnt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (c) 2021 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
   4 */
   5
   6#include <linux/counter.h>
   7#include <linux/gpio/consumer.h>
   8#include <linux/interrupt.h>
   9#include <linux/irq.h>
  10#include <linux/mod_devicetable.h>
  11#include <linux/module.h>
  12#include <linux/platform_device.h>
  13
  14#define INTERRUPT_CNT_NAME "interrupt-cnt"
  15
  16struct interrupt_cnt_priv {
  17        atomic_t count;
  18        struct counter_device counter;
  19        struct gpio_desc *gpio;
  20        int irq;
  21        bool enabled;
  22        struct counter_signal signals;
  23        struct counter_synapse synapses;
  24        struct counter_count cnts;
  25};
  26
  27static irqreturn_t interrupt_cnt_isr(int irq, void *dev_id)
  28{
  29        struct interrupt_cnt_priv *priv = dev_id;
  30
  31        atomic_inc(&priv->count);
  32
  33        return IRQ_HANDLED;
  34}
  35
  36static ssize_t interrupt_cnt_enable_read(struct counter_device *counter,
  37                                         struct counter_count *count,
  38                                         void *private, char *buf)
  39{
  40        struct interrupt_cnt_priv *priv = counter->priv;
  41
  42        return sysfs_emit(buf, "%d\n", priv->enabled);
  43}
  44
  45static ssize_t interrupt_cnt_enable_write(struct counter_device *counter,
  46                                          struct counter_count *count,
  47                                          void *private, const char *buf,
  48                                          size_t len)
  49{
  50        struct interrupt_cnt_priv *priv = counter->priv;
  51        bool enable;
  52        ssize_t ret;
  53
  54        ret = kstrtobool(buf, &enable);
  55        if (ret)
  56                return ret;
  57
  58        if (priv->enabled == enable)
  59                return len;
  60
  61        if (enable) {
  62                priv->enabled = true;
  63                enable_irq(priv->irq);
  64        } else {
  65                disable_irq(priv->irq);
  66                priv->enabled = false;
  67        }
  68
  69        return len;
  70}
  71
  72static const struct counter_count_ext interrupt_cnt_ext[] = {
  73        {
  74                .name = "enable",
  75                .read = interrupt_cnt_enable_read,
  76                .write = interrupt_cnt_enable_write,
  77        },
  78};
  79
  80static const enum counter_synapse_action interrupt_cnt_synapse_actions[] = {
  81        COUNTER_SYNAPSE_ACTION_RISING_EDGE,
  82};
  83
  84static int interrupt_cnt_action_get(struct counter_device *counter,
  85                                    struct counter_count *count,
  86                                    struct counter_synapse *synapse,
  87                                    size_t *action)
  88{
  89        *action = 0;
  90
  91        return 0;
  92}
  93
  94static int interrupt_cnt_read(struct counter_device *counter,
  95                              struct counter_count *count, unsigned long *val)
  96{
  97        struct interrupt_cnt_priv *priv = counter->priv;
  98
  99        *val = atomic_read(&priv->count);
 100
 101        return 0;
 102}
 103
 104static int interrupt_cnt_write(struct counter_device *counter,
 105                               struct counter_count *count,
 106                               const unsigned long val)
 107{
 108        struct interrupt_cnt_priv *priv = counter->priv;
 109
 110        if (val != (typeof(priv->count.counter))val)
 111                return -ERANGE;
 112
 113        atomic_set(&priv->count, val);
 114
 115        return 0;
 116}
 117
 118static const enum counter_function interrupt_cnt_functions[] = {
 119        COUNTER_FUNCTION_INCREASE,
 120};
 121
 122static int interrupt_cnt_function_get(struct counter_device *counter,
 123                                      struct counter_count *count,
 124                                      size_t *function)
 125{
 126        *function = 0;
 127
 128        return 0;
 129}
 130
 131static int interrupt_cnt_signal_read(struct counter_device *counter,
 132                                     struct counter_signal *signal,
 133                                     enum counter_signal_level *level)
 134{
 135        struct interrupt_cnt_priv *priv = counter->priv;
 136        int ret;
 137
 138        if (!priv->gpio)
 139                return -EINVAL;
 140
 141        ret = gpiod_get_value(priv->gpio);
 142        if (ret < 0)
 143                return ret;
 144
 145        *level = ret ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW;
 146
 147        return 0;
 148}
 149
 150static const struct counter_ops interrupt_cnt_ops = {
 151        .action_get = interrupt_cnt_action_get,
 152        .count_read = interrupt_cnt_read,
 153        .count_write = interrupt_cnt_write,
 154        .function_get = interrupt_cnt_function_get,
 155        .signal_read  = interrupt_cnt_signal_read,
 156};
 157
 158static int interrupt_cnt_probe(struct platform_device *pdev)
 159{
 160        struct device *dev = &pdev->dev;
 161        struct interrupt_cnt_priv *priv;
 162        int ret;
 163
 164        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 165        if (!priv)
 166                return -ENOMEM;
 167
 168        priv->irq = platform_get_irq_optional(pdev,  0);
 169        if (priv->irq == -ENXIO)
 170                priv->irq = 0;
 171        else if (priv->irq < 0)
 172                return dev_err_probe(dev, priv->irq, "failed to get IRQ\n");
 173
 174        priv->gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_IN);
 175        if (IS_ERR(priv->gpio))
 176                return dev_err_probe(dev, PTR_ERR(priv->gpio), "failed to get GPIO\n");
 177
 178        if (!priv->irq && !priv->gpio) {
 179                dev_err(dev, "IRQ and GPIO are not found. At least one source should be provided\n");
 180                return -ENODEV;
 181        }
 182
 183        if (!priv->irq) {
 184                int irq = gpiod_to_irq(priv->gpio);
 185
 186                if (irq < 0)
 187                        return dev_err_probe(dev, irq, "failed to get IRQ from GPIO\n");
 188
 189                priv->irq = irq;
 190        }
 191
 192        priv->signals.name = devm_kasprintf(dev, GFP_KERNEL, "IRQ %d",
 193                                            priv->irq);
 194        if (!priv->signals.name)
 195                return -ENOMEM;
 196
 197        priv->counter.signals = &priv->signals;
 198        priv->counter.num_signals = 1;
 199
 200        priv->synapses.actions_list = interrupt_cnt_synapse_actions;
 201        priv->synapses.num_actions = ARRAY_SIZE(interrupt_cnt_synapse_actions);
 202        priv->synapses.signal = &priv->signals;
 203
 204        priv->cnts.name = "Channel 0 Count";
 205        priv->cnts.functions_list = interrupt_cnt_functions;
 206        priv->cnts.num_functions = ARRAY_SIZE(interrupt_cnt_functions);
 207        priv->cnts.synapses = &priv->synapses;
 208        priv->cnts.num_synapses = 1;
 209        priv->cnts.ext = interrupt_cnt_ext;
 210        priv->cnts.num_ext = ARRAY_SIZE(interrupt_cnt_ext);
 211
 212        priv->counter.priv = priv;
 213        priv->counter.name = dev_name(dev);
 214        priv->counter.parent = dev;
 215        priv->counter.ops = &interrupt_cnt_ops;
 216        priv->counter.counts = &priv->cnts;
 217        priv->counter.num_counts = 1;
 218
 219        irq_set_status_flags(priv->irq, IRQ_NOAUTOEN);
 220        ret = devm_request_irq(dev, priv->irq, interrupt_cnt_isr,
 221                               IRQF_TRIGGER_RISING | IRQF_NO_THREAD,
 222                               dev_name(dev), priv);
 223        if (ret)
 224                return ret;
 225
 226        return devm_counter_register(dev, &priv->counter);
 227}
 228
 229static const struct of_device_id interrupt_cnt_of_match[] = {
 230        { .compatible = "interrupt-counter", },
 231        {}
 232};
 233MODULE_DEVICE_TABLE(of, interrupt_cnt_of_match);
 234
 235static struct platform_driver interrupt_cnt_driver = {
 236        .probe = interrupt_cnt_probe,
 237        .driver = {
 238                .name = INTERRUPT_CNT_NAME,
 239                .of_match_table = interrupt_cnt_of_match,
 240        },
 241};
 242module_platform_driver(interrupt_cnt_driver);
 243
 244MODULE_ALIAS("platform:interrupt-counter");
 245MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
 246MODULE_DESCRIPTION("Interrupt counter driver");
 247MODULE_LICENSE("GPL v2");
 248