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