1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2021 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
6 #include <linux/counter.h>
7 #include <linux/gpio/consumer.h>
8 #include <linux/interrupt.h>
10 #include <linux/mod_devicetable.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
13 #include <linux/types.h>
15 #define INTERRUPT_CNT_NAME "interrupt-cnt"
17 struct interrupt_cnt_priv
{
19 struct gpio_desc
*gpio
;
22 struct counter_signal signals
;
23 struct counter_synapse synapses
;
24 struct counter_count cnts
;
27 static irqreturn_t
interrupt_cnt_isr(int irq
, void *dev_id
)
29 struct counter_device
*counter
= dev_id
;
30 struct interrupt_cnt_priv
*priv
= counter_priv(counter
);
32 atomic_inc(&priv
->count
);
34 counter_push_event(counter
, COUNTER_EVENT_CHANGE_OF_STATE
, 0);
39 static int interrupt_cnt_enable_read(struct counter_device
*counter
,
40 struct counter_count
*count
, u8
*enable
)
42 struct interrupt_cnt_priv
*priv
= counter_priv(counter
);
44 *enable
= priv
->enabled
;
49 static int interrupt_cnt_enable_write(struct counter_device
*counter
,
50 struct counter_count
*count
, u8 enable
)
52 struct interrupt_cnt_priv
*priv
= counter_priv(counter
);
54 if (priv
->enabled
== enable
)
59 enable_irq(priv
->irq
);
61 disable_irq(priv
->irq
);
62 priv
->enabled
= false;
68 static struct counter_comp interrupt_cnt_ext
[] = {
69 COUNTER_COMP_ENABLE(interrupt_cnt_enable_read
,
70 interrupt_cnt_enable_write
),
73 static const enum counter_synapse_action interrupt_cnt_synapse_actions
[] = {
74 COUNTER_SYNAPSE_ACTION_RISING_EDGE
,
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
)
82 *action
= COUNTER_SYNAPSE_ACTION_RISING_EDGE
;
87 static int interrupt_cnt_read(struct counter_device
*counter
,
88 struct counter_count
*count
, u64
*val
)
90 struct interrupt_cnt_priv
*priv
= counter_priv(counter
);
92 *val
= atomic_read(&priv
->count
);
97 static int interrupt_cnt_write(struct counter_device
*counter
,
98 struct counter_count
*count
, const u64 val
)
100 struct interrupt_cnt_priv
*priv
= counter_priv(counter
);
102 if (val
!= (typeof(priv
->count
.counter
))val
)
105 atomic_set(&priv
->count
, val
);
110 static const enum counter_function interrupt_cnt_functions
[] = {
111 COUNTER_FUNCTION_INCREASE
,
114 static int interrupt_cnt_function_read(struct counter_device
*counter
,
115 struct counter_count
*count
,
116 enum counter_function
*function
)
118 *function
= COUNTER_FUNCTION_INCREASE
;
123 static int interrupt_cnt_signal_read(struct counter_device
*counter
,
124 struct counter_signal
*signal
,
125 enum counter_signal_level
*level
)
127 struct interrupt_cnt_priv
*priv
= counter_priv(counter
);
133 ret
= gpiod_get_value(priv
->gpio
);
137 *level
= ret
? COUNTER_SIGNAL_LEVEL_HIGH
: COUNTER_SIGNAL_LEVEL_LOW
;
142 static int interrupt_cnt_watch_validate(struct counter_device
*counter
,
143 const struct counter_watch
*watch
)
145 if (watch
->channel
!= 0 ||
146 watch
->event
!= COUNTER_EVENT_CHANGE_OF_STATE
)
152 static const struct counter_ops interrupt_cnt_ops
= {
153 .action_read
= interrupt_cnt_action_read
,
154 .count_read
= interrupt_cnt_read
,
155 .count_write
= interrupt_cnt_write
,
156 .function_read
= interrupt_cnt_function_read
,
157 .signal_read
= interrupt_cnt_signal_read
,
158 .watch_validate
= interrupt_cnt_watch_validate
,
161 static int interrupt_cnt_probe(struct platform_device
*pdev
)
163 struct device
*dev
= &pdev
->dev
;
164 struct counter_device
*counter
;
165 struct interrupt_cnt_priv
*priv
;
168 counter
= devm_counter_alloc(dev
, sizeof(*priv
));
171 priv
= counter_priv(counter
);
173 priv
->irq
= platform_get_irq_optional(pdev
, 0);
174 if (priv
->irq
== -ENXIO
)
176 else if (priv
->irq
< 0)
177 return dev_err_probe(dev
, priv
->irq
, "failed to get IRQ\n");
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");
183 if (!priv
->irq
&& !priv
->gpio
) {
184 dev_err(dev
, "IRQ and GPIO are not found. At least one source should be provided\n");
189 int irq
= gpiod_to_irq(priv
->gpio
);
192 return dev_err_probe(dev
, irq
, "failed to get IRQ from GPIO\n");
197 priv
->signals
.name
= devm_kasprintf(dev
, GFP_KERNEL
, "IRQ %d",
199 if (!priv
->signals
.name
)
202 counter
->signals
= &priv
->signals
;
203 counter
->num_signals
= 1;
205 priv
->synapses
.actions_list
= interrupt_cnt_synapse_actions
;
206 priv
->synapses
.num_actions
= ARRAY_SIZE(interrupt_cnt_synapse_actions
);
207 priv
->synapses
.signal
= &priv
->signals
;
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
);
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;
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
,
226 dev_name(dev
), counter
);
230 ret
= devm_counter_add(dev
, counter
);
232 return dev_err_probe(dev
, ret
, "Failed to add counter\n");
237 static const struct of_device_id interrupt_cnt_of_match
[] = {
238 { .compatible
= "interrupt-counter", },
241 MODULE_DEVICE_TABLE(of
, interrupt_cnt_of_match
);
243 static struct platform_driver interrupt_cnt_driver
= {
244 .probe
= interrupt_cnt_probe
,
246 .name
= INTERRUPT_CNT_NAME
,
247 .of_match_table
= interrupt_cnt_of_match
,
250 module_platform_driver(interrupt_cnt_driver
);
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");
256 MODULE_IMPORT_NS("COUNTER");