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