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