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