Skip to content

Commit f5066a1

Browse files
drivers: comparator: implement stm32 comparator driver
implement the driver for the stm32 comparator peripheral Signed-off-by: Alexander Kozhinov <[email protected]>
1 parent 2158fed commit f5066a1

File tree

4 files changed

+356
-0
lines changed

4 files changed

+356
-0
lines changed

drivers/comparator/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ zephyr_library_sources_ifdef(CONFIG_COMPARATOR_NRF_COMP comparator_nrf_comp.c)
1515
zephyr_library_sources_ifdef(CONFIG_COMPARATOR_NRF_LPCOMP comparator_nrf_lpcomp.c)
1616
zephyr_library_sources_ifdef(CONFIG_COMPARATOR_SHELL comparator_shell.c)
1717
zephyr_library_sources_ifdef(CONFIG_COMPARATOR_RENESAS_RA comparator_renesas_ra.c)
18+
zephyr_library_sources_ifdef(CONFIG_COMPARATOR_STM32_COMP comparator_stm32_comp.c)

drivers/comparator/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ rsource "Kconfig.nrf_comp"
2727
rsource "Kconfig.nrf_lpcomp"
2828
rsource "Kconfig.shell"
2929
rsource "Kconfig.renesas_ra"
30+
rsource "Kconfig.stm32_comp"
3031

3132
endif # COMPARATOR
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2025 Alexander Kozhinov <[email protected]>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config COMPARATOR_STM32_COMP
5+
bool "ST STM32 comparator driver"
6+
default y
7+
depends on DT_HAS_ST_STM32_COMP_ENABLED
8+
select PINCTRL
9+
select EXTI_STM32
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
/*
2+
* Copyright (c) 2025 Alexander Kozhinov <[email protected]>
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <zephyr/irq.h>
7+
#include <zephyr/kernel.h>
8+
#include <zephyr/pm/device.h>
9+
#include <zephyr/logging/log.h>
10+
#include <zephyr/sys/util_macro.h>
11+
#include <zephyr/drivers/pinctrl.h>
12+
#include <zephyr/drivers/comparator.h>
13+
#include <zephyr/drivers/clock_control.h>
14+
#include <zephyr/drivers/clock_control/stm32_clock_control.h>
15+
#include <zephyr/drivers/interrupt_controller/intc_exti_stm32.h>
16+
17+
#include <stm32_ll_comp.h>
18+
#include <stm32_ll_system.h>
19+
20+
LOG_MODULE_REGISTER(stm32_comp, CONFIG_COMPARATOR_LOG_LEVEL);
21+
22+
#define DT_DRV_COMPAT st_stm32_comp
23+
24+
#define LL_COMP_INPUT_PLUS_IN0 LL_COMP_INPUT_PLUS_IO1
25+
#define LL_COMP_INPUT_PLUS_IN1 LL_COMP_INPUT_PLUS_IO2
26+
27+
#define LL_COMP_INPUT_MINUS_IN0 LL_COMP_INPUT_MINUS_IO1
28+
#define LL_COMP_INPUT_MINUS_IN1 LL_COMP_INPUT_MINUS_IO2
29+
30+
#define STM32_COMP_DT_INST_P_IN(inst) \
31+
CONCAT(LL_COMP_INPUT_PLUS_, DT_INST_STRING_TOKEN(inst, positive_input))
32+
33+
#define STM32_COMP_DT_INST_N_IN(inst) \
34+
CONCAT(LL_COMP_INPUT_MINUS_, DT_INST_STRING_TOKEN(inst, negative_input))
35+
36+
#define STM32_COMP_DT_INST_HYST_MODE(inst) \
37+
CONCAT(LL_COMP_HYSTERESIS_, DT_INST_STRING_TOKEN(inst, hysteresis))
38+
39+
#define STM32_COMP_DT_INST_INV_OUT(inst) \
40+
CONCAT(LL_COMP_OUTPUTPOL_, DT_INST_STRING_TOKEN(inst, invert_output))
41+
42+
#define STM32_COMP_DT_INST_BLANK_SEL(inst) \
43+
CONCAT(LL_COMP_BLANKINGSRC_, DT_INST_STRING_TOKEN(inst, st_blank_sel))
44+
45+
#define STM32_COMP_DT_INST_LOCK(inst) DT_INST_PROP(inst, st_lock_enable)
46+
47+
#define STM32_COMP_DT_MILLER_EFFECT_HOLD_DISABLE(inst) \
48+
DT_INST_PROP_OR(inst, st_miller_effect_hold_disable, false)
49+
50+
#define STM32_COMP_DT_EXTI_LINE_NUMBER(inst) DT_INST_PROP(inst, st_exti_line)
51+
52+
/* Value 0 always relates to the default value of COMP PWRMODE bit field */
53+
#define STM32_COMP_DT_POWER_MODE(inst) \
54+
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, st_power_mode), \
55+
(CONCAT(LL_COMP_POWERMODE_, \
56+
DT_INST_STRING_TOKEN(inst, st_power_mode))), \
57+
(0))
58+
59+
struct stm32_comp_config {
60+
COMP_TypeDef *comp;
61+
struct stm32_pclken *pclken;
62+
const struct pinctrl_dev_config *pincfg;
63+
void (*irq_init)(void);
64+
uint32_t irq_nr;
65+
uint32_t exti_line_number;
66+
bool lock_enable;
67+
bool miller_effect_hold_disable;
68+
uint32_t power_mode;
69+
uint32_t input_plus;
70+
uint32_t input_minus;
71+
uint32_t hysteresis;
72+
uint32_t invert_output;
73+
uint32_t blank_sel;
74+
};
75+
76+
struct stm32_comp_data {
77+
comparator_callback_t callback;
78+
void *user_data;
79+
};
80+
81+
static bool stm32_comp_is_resumed(const struct device *dev)
82+
{
83+
#ifdef CONFIG_PM_DEVICE
84+
enum pm_device_state state;
85+
86+
(void)pm_device_state_get(dev, &state);
87+
return state == PM_DEVICE_STATE_ACTIVE;
88+
#else
89+
return true;
90+
#endif /* CONFIG_PM_DEVICE */
91+
}
92+
93+
static int stm32_comp_get_output(const struct device *dev)
94+
{
95+
const struct stm32_comp_config *cfg = dev->config;
96+
COMP_TypeDef *comp = cfg->comp;
97+
98+
return LL_COMP_ReadOutputLevel(comp);
99+
}
100+
101+
static int stm32_comp_set_trigger(const struct device *dev, enum comparator_trigger trigger)
102+
{
103+
const struct stm32_comp_config *cfg = dev->config;
104+
struct stm32_comp_data *data = dev->data;
105+
stm32_exti_trigger_type exti_trigger = 0U;
106+
COMP_TypeDef *comp = cfg->comp;
107+
int ret = 0;
108+
109+
switch (trigger) {
110+
case COMPARATOR_TRIGGER_NONE:
111+
exti_trigger = STM32_EXTI_TRIG_NONE;
112+
break;
113+
case COMPARATOR_TRIGGER_RISING_EDGE:
114+
exti_trigger = STM32_EXTI_TRIG_RISING;
115+
break;
116+
case COMPARATOR_TRIGGER_FALLING_EDGE:
117+
exti_trigger = STM32_EXTI_TRIG_FALLING;
118+
break;
119+
case COMPARATOR_TRIGGER_BOTH_EDGES:
120+
exti_trigger = STM32_EXTI_TRIG_BOTH;
121+
break;
122+
default:
123+
LOG_ERR("%s: Unsupported trigger mode %d", dev->name, trigger);
124+
return -ENOTSUP;
125+
}
126+
127+
irq_disable(cfg->irq_nr);
128+
LL_COMP_Disable(comp);
129+
130+
ret = stm32_exti_enable(cfg->exti_line_number, exti_trigger,
131+
STM32_EXTI_MODE_IT);
132+
if (ret != 0) {
133+
LOG_ERR("%s: EXTI init failed (%d)", dev->name, ret);
134+
return ret;
135+
}
136+
137+
if (stm32_comp_is_resumed(dev)) {
138+
LL_COMP_Enable(comp);
139+
}
140+
141+
if (data->callback != NULL) {
142+
irq_enable(cfg->irq_nr);
143+
}
144+
145+
return ret;
146+
}
147+
148+
static int stm32_comp_trigger_is_pending(const struct device *dev)
149+
{
150+
const struct stm32_comp_config *cfg = dev->config;
151+
152+
if (stm32_exti_is_pending(cfg->exti_line_number)) {
153+
stm32_exti_clear_pending(cfg->exti_line_number);
154+
return 1;
155+
}
156+
return 0;
157+
}
158+
159+
static int stm32_comp_set_trigger_callback(const struct device *dev, comparator_callback_t callback,
160+
void *user_data)
161+
{
162+
const struct stm32_comp_config *cfg = dev->config;
163+
struct stm32_comp_data *data = dev->data;
164+
165+
irq_disable(cfg->irq_nr);
166+
167+
data->callback = callback;
168+
data->user_data = user_data;
169+
170+
irq_enable(cfg->irq_nr);
171+
172+
if (data->callback != NULL && stm32_comp_trigger_is_pending(dev)) {
173+
callback(dev, user_data);
174+
}
175+
176+
return 0;
177+
}
178+
179+
static int stm32_comp_pm_callback(const struct device *dev, enum pm_device_action action)
180+
{
181+
const struct stm32_comp_config *cfg = dev->config;
182+
COMP_TypeDef *comp = cfg->comp;
183+
184+
if (LL_COMP_IsLocked(comp)) {
185+
LOG_ERR("%s is locked", dev->name);
186+
return -EACCES;
187+
}
188+
189+
if (action == PM_DEVICE_ACTION_RESUME) {
190+
LL_COMP_Enable(comp);
191+
if (cfg->lock_enable) {
192+
LL_COMP_Lock(comp);
193+
}
194+
}
195+
196+
if (IS_ENABLED(CONFIG_PM_DEVICE) && action == PM_DEVICE_ACTION_SUSPEND) {
197+
LL_COMP_Disable(comp);
198+
}
199+
200+
return 0;
201+
}
202+
203+
static void stm32_comp_irq_handler(const struct device *dev)
204+
{
205+
const struct stm32_comp_config *cfg = dev->config;
206+
struct stm32_comp_data *data = dev->data;
207+
208+
if (stm32_exti_is_pending(cfg->exti_line_number)) {
209+
stm32_exti_clear_pending(cfg->exti_line_number);
210+
}
211+
212+
if (data->callback == NULL) {
213+
return;
214+
}
215+
216+
data->callback(dev, data->user_data);
217+
}
218+
219+
static int stm32_comp_init(const struct device *dev)
220+
{
221+
const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
222+
const struct stm32_comp_config *cfg = dev->config;
223+
COMP_TypeDef *comp = cfg->comp;
224+
int ret = 0;
225+
226+
if (!device_is_ready(clk)) {
227+
LOG_ERR("%s clock control device not ready", dev->name);
228+
return -ENODEV;
229+
}
230+
231+
/* Enable COMP bus clock */
232+
ret = clock_control_on(clk, &cfg->pclken[0]);
233+
if (ret != 0) {
234+
LOG_ERR("%s clock op failed (%d)", dev->name, ret);
235+
return ret;
236+
}
237+
238+
/* Enable COMP clock source */
239+
#ifndef CONFIG_SOC_SERIES_STM32G4X
240+
/* stm32g4 clock configuration is not necessary,
241+
* since it is enough to turn APB2 clock on (see: RM0440 Rev 8 767/2138)
242+
*/
243+
ret = clock_control_configure(clk, &cfg->pclken[1], NULL);
244+
if (ret != 0) {
245+
LOG_ERR("%s clock configure failed (%d)", dev->name, ret);
246+
return ret;
247+
}
248+
#endif /* !CONFIG_SOC_SERIES_STM32G4X */
249+
250+
/* Configure COMP inputs as specified in Device Tree, if any */
251+
ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
252+
if (ret < 0 && ret != -ENOENT) {
253+
/*
254+
* If the COMP is used only with internal channels, then no pinctrl is
255+
* provided in Device Tree, and pinctrl_apply_state returns -ENOENT,
256+
* but this should not be treated as an error.
257+
*/
258+
LOG_ERR("%s pinctrl setup failed (%d)", dev->name, ret);
259+
return ret;
260+
}
261+
262+
if (LL_COMP_IsLocked(comp)) {
263+
/* COMP instance shall not be locked */
264+
LOG_ERR("%s COMP instance is locked", dev->name);
265+
return -EACCES;
266+
}
267+
268+
#ifndef CONFIG_SOC_SERIES_STM32G4X
269+
LL_COMP_SetPowerMode(comp, cfg->power_mode);
270+
#endif /* !CONFIG_SOC_SERIES_STM32G4X */
271+
272+
LL_COMP_SetInputMinus(comp, cfg->input_minus);
273+
LL_COMP_SetInputPlus(comp, cfg->input_plus);
274+
LL_COMP_SetInputHysteresis(comp, cfg->hysteresis);
275+
LL_COMP_SetOutputPolarity(comp, cfg->invert_output);
276+
LL_COMP_SetOutputBlankingSource(comp, cfg->blank_sel);
277+
278+
#if defined(CONFIG_SOC_SERIES_STM32G4X)
279+
/* Undocumented bit, refer to st,miller-effect-hold-disable DT binding */
280+
WRITE_BIT(comp->CSR, 1, cfg->miller_effect_hold_disable);
281+
#endif /* CONFIG_SOC_SERIES_STM32G4X */
282+
283+
cfg->irq_init();
284+
285+
return pm_device_driver_init(dev, stm32_comp_pm_callback);
286+
}
287+
288+
static DEVICE_API(comparator, stm32_comp_comp_api) = {
289+
.get_output = stm32_comp_get_output,
290+
.set_trigger = stm32_comp_set_trigger,
291+
.set_trigger_callback = stm32_comp_set_trigger_callback,
292+
.trigger_is_pending = stm32_comp_trigger_is_pending,
293+
};
294+
295+
#define STM32_COMP_IRQ_HANDLER_SYM(inst) stm32_comp_irq_init_##inst
296+
297+
#define STM32_COMP_IRQ_HANDLER_DEFINE(inst) \
298+
\
299+
static void STM32_COMP_IRQ_HANDLER_SYM(inst)(void) \
300+
{ \
301+
IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), \
302+
stm32_comp_irq_handler, DEVICE_DT_INST_GET(inst), 0); \
303+
irq_enable(DT_INST_IRQN(inst)); \
304+
}
305+
306+
#define STM32_COMP_DEVICE(inst) \
307+
\
308+
static struct stm32_pclken comp_##inst##_clk[] = STM32_DT_INST_CLOCKS(inst); \
309+
\
310+
PINCTRL_DT_INST_DEFINE(inst); \
311+
\
312+
static struct stm32_comp_data stm32_comp_data_##inst; \
313+
\
314+
STM32_COMP_IRQ_HANDLER_DEFINE(inst) \
315+
static const struct stm32_comp_config stm32_comp_config_##inst = { \
316+
.comp = (COMP_TypeDef *)DT_INST_REG_ADDR(inst), \
317+
.pclken = comp_##inst##_clk, \
318+
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
319+
.irq_init = STM32_COMP_IRQ_HANDLER_SYM(inst), \
320+
.irq_nr = DT_INST_IRQN(inst), \
321+
.exti_line_number = STM32_COMP_DT_EXTI_LINE_NUMBER(inst), \
322+
.lock_enable = STM32_COMP_DT_INST_LOCK(inst), \
323+
.miller_effect_hold_disable = \
324+
STM32_COMP_DT_MILLER_EFFECT_HOLD_DISABLE(inst), \
325+
.power_mode = STM32_COMP_DT_POWER_MODE(inst), \
326+
.input_plus = STM32_COMP_DT_INST_P_IN(inst), \
327+
.input_minus = STM32_COMP_DT_INST_N_IN(inst), \
328+
.hysteresis = STM32_COMP_DT_INST_HYST_MODE(inst), \
329+
.invert_output = STM32_COMP_DT_INST_INV_OUT(inst), \
330+
.blank_sel = STM32_COMP_DT_INST_BLANK_SEL(inst) \
331+
}; \
332+
\
333+
PM_DEVICE_DT_INST_DEFINE(inst, stm32_comp_pm_callback); \
334+
\
335+
DEVICE_DT_INST_DEFINE(inst, \
336+
stm32_comp_init, \
337+
PM_DEVICE_DT_INST_GET(inst), \
338+
&stm32_comp_data_##inst, \
339+
&stm32_comp_config_##inst, \
340+
POST_KERNEL, \
341+
CONFIG_COMPARATOR_INIT_PRIORITY, \
342+
&stm32_comp_comp_api \
343+
);
344+
345+
DT_INST_FOREACH_STATUS_OKAY(STM32_COMP_DEVICE)

0 commit comments

Comments
 (0)