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 16 struct 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 27 static 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 36 static 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 45 static 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 72 static 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 80 static enum counter_synapse_action interrupt_cnt_synapse_actionss[] = { 81 COUNTER_SYNAPSE_ACTION_RISING_EDGE, 82 }; 83 84 static 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 94 static 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 104 static 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 115 static enum counter_count_function interrupt_cnt_functions[] = { 116 COUNTER_COUNT_FUNCTION_INCREASE, 117 }; 118 119 static 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 128 static 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 147 static 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 155 static 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_actionss; 198 priv->synapses.num_actions = ARRAY_SIZE(interrupt_cnt_synapse_actionss); 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 226 static const struct of_device_id interrupt_cnt_of_match[] = { 227 { .compatible = "interrupt-counter", }, 228 {} 229 }; 230 MODULE_DEVICE_TABLE(of, interrupt_cnt_of_match); 231 232 static 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 }; 239 module_platform_driver(interrupt_cnt_driver); 240 241 MODULE_ALIAS("platform:interrupt-counter"); 242 MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>"); 243 MODULE_DESCRIPTION("Interrupt counter driver"); 244 MODULE_LICENSE("GPL v2"); 245