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