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