]>
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> | |
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 | ||
bc84957d | 80 | static const enum counter_synapse_action interrupt_cnt_synapse_actions[] = { |
a55ebd47 OR |
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 | ||
e2ff3198 WBG |
110 | if (val != (typeof(priv->count.counter))val) |
111 | return -ERANGE; | |
112 | ||
a55ebd47 OR |
113 | atomic_set(&priv->count, val); |
114 | ||
115 | return 0; | |
116 | } | |
117 | ||
394a0150 WBG |
118 | static const enum counter_function interrupt_cnt_functions[] = { |
119 | COUNTER_FUNCTION_INCREASE, | |
a55ebd47 OR |
120 | }; |
121 | ||
122 | static int interrupt_cnt_function_get(struct counter_device *counter, | |
123 | struct counter_count *count, | |
124 | size_t *function) | |
125 | { | |
126 | *function = 0; | |
127 | ||
128 | return 0; | |
129 | } | |
130 | ||
131 | static int interrupt_cnt_signal_read(struct counter_device *counter, | |
132 | struct counter_signal *signal, | |
493b938a | 133 | enum counter_signal_level *level) |
a55ebd47 OR |
134 | { |
135 | struct interrupt_cnt_priv *priv = counter->priv; | |
136 | int ret; | |
137 | ||
138 | if (!priv->gpio) | |
139 | return -EINVAL; | |
140 | ||
141 | ret = gpiod_get_value(priv->gpio); | |
142 | if (ret < 0) | |
143 | return ret; | |
144 | ||
493b938a | 145 | *level = ret ? COUNTER_SIGNAL_LEVEL_HIGH : COUNTER_SIGNAL_LEVEL_LOW; |
a55ebd47 OR |
146 | |
147 | return 0; | |
148 | } | |
149 | ||
150 | static const struct counter_ops interrupt_cnt_ops = { | |
151 | .action_get = interrupt_cnt_action_get, | |
152 | .count_read = interrupt_cnt_read, | |
153 | .count_write = interrupt_cnt_write, | |
154 | .function_get = interrupt_cnt_function_get, | |
155 | .signal_read = interrupt_cnt_signal_read, | |
156 | }; | |
157 | ||
158 | static int interrupt_cnt_probe(struct platform_device *pdev) | |
159 | { | |
160 | struct device *dev = &pdev->dev; | |
161 | struct interrupt_cnt_priv *priv; | |
162 | int ret; | |
163 | ||
164 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
165 | if (!priv) | |
166 | return -ENOMEM; | |
167 | ||
168 | priv->irq = platform_get_irq_optional(pdev, 0); | |
169 | if (priv->irq == -ENXIO) | |
170 | priv->irq = 0; | |
171 | else if (priv->irq < 0) | |
172 | return dev_err_probe(dev, priv->irq, "failed to get IRQ\n"); | |
173 | ||
174 | priv->gpio = devm_gpiod_get_optional(dev, NULL, GPIOD_IN); | |
175 | if (IS_ERR(priv->gpio)) | |
176 | return dev_err_probe(dev, PTR_ERR(priv->gpio), "failed to get GPIO\n"); | |
177 | ||
178 | if (!priv->irq && !priv->gpio) { | |
179 | dev_err(dev, "IRQ and GPIO are not found. At least one source should be provided\n"); | |
180 | return -ENODEV; | |
181 | } | |
182 | ||
183 | if (!priv->irq) { | |
184 | int irq = gpiod_to_irq(priv->gpio); | |
185 | ||
186 | if (irq < 0) | |
187 | return dev_err_probe(dev, irq, "failed to get IRQ from GPIO\n"); | |
188 | ||
189 | priv->irq = irq; | |
190 | } | |
191 | ||
192 | priv->signals.name = devm_kasprintf(dev, GFP_KERNEL, "IRQ %d", | |
193 | priv->irq); | |
194 | if (!priv->signals.name) | |
195 | return -ENOMEM; | |
196 | ||
197 | priv->counter.signals = &priv->signals; | |
198 | priv->counter.num_signals = 1; | |
199 | ||
bc84957d WBG |
200 | priv->synapses.actions_list = interrupt_cnt_synapse_actions; |
201 | priv->synapses.num_actions = ARRAY_SIZE(interrupt_cnt_synapse_actions); | |
a55ebd47 OR |
202 | priv->synapses.signal = &priv->signals; |
203 | ||
204 | priv->cnts.name = "Channel 0 Count"; | |
205 | priv->cnts.functions_list = interrupt_cnt_functions; | |
206 | priv->cnts.num_functions = ARRAY_SIZE(interrupt_cnt_functions); | |
207 | priv->cnts.synapses = &priv->synapses; | |
208 | priv->cnts.num_synapses = 1; | |
209 | priv->cnts.ext = interrupt_cnt_ext; | |
210 | priv->cnts.num_ext = ARRAY_SIZE(interrupt_cnt_ext); | |
211 | ||
212 | priv->counter.priv = priv; | |
213 | priv->counter.name = dev_name(dev); | |
214 | priv->counter.parent = dev; | |
215 | priv->counter.ops = &interrupt_cnt_ops; | |
216 | priv->counter.counts = &priv->cnts; | |
217 | priv->counter.num_counts = 1; | |
218 | ||
219 | irq_set_status_flags(priv->irq, IRQ_NOAUTOEN); | |
220 | ret = devm_request_irq(dev, priv->irq, interrupt_cnt_isr, | |
221 | IRQF_TRIGGER_RISING | IRQF_NO_THREAD, | |
222 | dev_name(dev), priv); | |
223 | if (ret) | |
224 | return ret; | |
225 | ||
226 | return devm_counter_register(dev, &priv->counter); | |
227 | } | |
228 | ||
229 | static const struct of_device_id interrupt_cnt_of_match[] = { | |
230 | { .compatible = "interrupt-counter", }, | |
231 | {} | |
232 | }; | |
233 | MODULE_DEVICE_TABLE(of, interrupt_cnt_of_match); | |
234 | ||
235 | static struct platform_driver interrupt_cnt_driver = { | |
236 | .probe = interrupt_cnt_probe, | |
237 | .driver = { | |
238 | .name = INTERRUPT_CNT_NAME, | |
239 | .of_match_table = interrupt_cnt_of_match, | |
240 | }, | |
241 | }; | |
242 | module_platform_driver(interrupt_cnt_driver); | |
243 | ||
244 | MODULE_ALIAS("platform:interrupt-counter"); | |
245 | MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>"); | |
246 | MODULE_DESCRIPTION("Interrupt counter driver"); | |
247 | MODULE_LICENSE("GPL v2"); |