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        atomic_set(&priv->count, val);
 111
 112        return 0;
 113}
 114
 115static const enum counter_count_function interrupt_cnt_functions[] = {
 116        COUNTER_COUNT_FUNCTION_INCREASE,
 117};
 118
 119static int interrupt_cnt_function_get(struct counter_device *counter,
 120                                      struct counter_count *count,
 121                                      size_t *function)
 122{
 123        *function = 0;
 124
 125        return 0;
 126}
 127
 128static int interrupt_cnt_signal_read(struct counter_device *counter,
 129                                     struct counter_signal *signal,
 130                                     enum counter_signal_value *val)
 131{
 132        struct interrupt_cnt_priv *priv = counter->priv;
 133        int ret;
 134
 135        if (!priv->gpio)
 136                return -EINVAL;
 137
 138        ret = gpiod_get_value(priv->gpio);
 139        if (ret < 0)
 140                return ret;
 141
 142        *val = ret ? COUNTER_SIGNAL_HIGH : COUNTER_SIGNAL_LOW;
 143
 144        return 0;
 145}
 146
 147static const struct counter_ops interrupt_cnt_ops = {
 148        .action_get = interrupt_cnt_action_get,
 149        .count_read = interrupt_cnt_read,
 150        .count_write = interrupt_cnt_write,
 151        .function_get = interrupt_cnt_function_get,
 152        .signal_read  = interrupt_cnt_signal_read,
 153};
 154
 155static int interrupt_cnt_probe(struct platform_device *pdev)
 156{
 157        struct device *dev = &pdev->dev;
 158        struct interrupt_cnt_priv *priv;
 159        int ret;
 160
 161        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 162        if (!priv)
 163                return -ENOMEM;
 164
 165        priv->irq = platform_get_irq_optional(pdev,  0);
 166        if (priv->irq == -ENXIO)
 167                priv->irq = 0;
 168        else if (priv->irq < 0)
 169                return dev_err_probe(dev, priv->irq, "failed to get IRQ\n");
 170
 171        priv->gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_IN);
 172        if (IS_ERR(priv->gpio))
 173                return dev_err_probe(dev, PTR_ERR(priv->gpio), "failed to get GPIO\n");
 174
 175        if (!priv->irq && !priv->gpio) {
 176                dev_err(dev, "IRQ and GPIO are not found. At least one source should be provided\n");
 177                return -ENODEV;
 178        }
 179
 180        if (!priv->irq) {
 181                int irq = gpiod_to_irq(priv->gpio);
 182
 183                if (irq < 0)
 184                        return dev_err_probe(dev, irq, "failed to get IRQ from GPIO\n");
 185
 186                priv->irq = irq;
 187        }
 188
 189        priv->signals.name = devm_kasprintf(dev, GFP_KERNEL, "IRQ %d",
 190                                            priv->irq);
 191        if (!priv->signals.name)
 192                return -ENOMEM;
 193
 194        priv->counter.signals = &priv->signals;
 195        priv->counter.num_signals = 1;
 196
 197        priv->synapses.actions_list = interrupt_cnt_synapse_actions;
 198        priv->synapses.num_actions = ARRAY_SIZE(interrupt_cnt_synapse_actions);
 199        priv->synapses.signal = &priv->signals;
 200
 201        priv->cnts.name = "Channel 0 Count";
 202        priv->cnts.functions_list = interrupt_cnt_functions;
 203        priv->cnts.num_functions = ARRAY_SIZE(interrupt_cnt_functions);
 204        priv->cnts.synapses = &priv->synapses;
 205        priv->cnts.num_synapses = 1;
 206        priv->cnts.ext = interrupt_cnt_ext;
 207        priv->cnts.num_ext = ARRAY_SIZE(interrupt_cnt_ext);
 208
 209        priv->counter.priv = priv;
 210        priv->counter.name = dev_name(dev);
 211        priv->counter.parent = dev;
 212        priv->counter.ops = &interrupt_cnt_ops;
 213        priv->counter.counts = &priv->cnts;
 214        priv->counter.num_counts = 1;
 215
 216        irq_set_status_flags(priv->irq, IRQ_NOAUTOEN);
 217        ret = devm_request_irq(dev, priv->irq, interrupt_cnt_isr,
 218                               IRQF_TRIGGER_RISING | IRQF_NO_THREAD,
 219                               dev_name(dev), priv);
 220        if (ret)
 221                return ret;
 222
 223        return devm_counter_register(dev, &priv->counter);
 224}
 225
 226static const struct of_device_id interrupt_cnt_of_match[] = {
 227        { .compatible = "interrupt-counter", },
 228        {}
 229};
 230MODULE_DEVICE_TABLE(of, interrupt_cnt_of_match);
 231
 232static struct platform_driver interrupt_cnt_driver = {
 233        .probe = interrupt_cnt_probe,
 234        .driver = {
 235                .name = INTERRUPT_CNT_NAME,
 236                .of_match_table = interrupt_cnt_of_match,
 237        },
 238};
 239module_platform_driver(interrupt_cnt_driver);
 240
 241MODULE_ALIAS("platform:interrupt-counter");
 242MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>");
 243MODULE_DESCRIPTION("Interrupt counter driver");
 244MODULE_LICENSE("GPL v2");
 245