1 /*
2 * Copyright (c) 2025 Silicon Laboratories Inc.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <em_device.h>
8 #include <em_acmp.h>
9 #include <zephyr/device.h>
10 #include <zephyr/drivers/comparator.h>
11 #include <zephyr/drivers/clock_control.h>
12 #include <zephyr/drivers/clock_control/clock_control_silabs.h>
13 #include <zephyr/drivers/pinctrl.h>
14 #include <zephyr/irq.h>
15 #include <zephyr/pm/device.h>
16
17 #include <zephyr/logging/log.h>
18 LOG_MODULE_REGISTER(silabs_acmp, CONFIG_COMPARATOR_LOG_LEVEL);
19
20 #define DT_DRV_COMPAT silabs_acmp
21
22 struct acmp_config {
23 ACMP_TypeDef *base;
24 const struct pinctrl_dev_config *pincfg;
25 const struct device *clock_dev;
26 const struct silabs_clock_control_cmu_config clock_cfg;
27 void (*irq_init)(void);
28 ACMP_Init_TypeDef init;
29 int input_negative;
30 int input_positive;
31 };
32
33 struct acmp_data {
34 uint32_t interrupt_mask;
35 comparator_callback_t callback;
36 void *user_data;
37 };
38
acmp_pm_action(const struct device * dev,enum pm_device_action action)39 static int acmp_pm_action(const struct device *dev, enum pm_device_action action)
40 {
41 const struct acmp_config *config = dev->config;
42 int err;
43
44 if (action == PM_DEVICE_ACTION_RESUME) {
45 err = clock_control_on(config->clock_dev,
46 (clock_control_subsys_t)&config->clock_cfg);
47 if (err < 0 && err != -EALREADY) {
48 return err;
49 }
50
51 err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
52 if (err < 0 && err != -ENOENT) {
53 LOG_ERR("failed to allocate silabs,analog-bus via pinctrl");
54 return err;
55 }
56
57 ACMP_Enable(config->base);
58 } else if (IS_ENABLED(CONFIG_PM_DEVICE) && (action == PM_DEVICE_ACTION_SUSPEND)) {
59 ACMP_Disable(config->base);
60
61 err = clock_control_off(config->clock_dev,
62 (clock_control_subsys_t)&config->clock_cfg);
63 if (err < 0) {
64 return err;
65 }
66
67 err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_SLEEP);
68 if (err < 0 && err != -ENOENT) {
69 return err;
70 }
71 } else {
72 return -ENOTSUP;
73 }
74
75 return 0;
76 }
77
acmp_init(const struct device * dev)78 static int acmp_init(const struct device *dev)
79 {
80 int err;
81 const struct acmp_config *config = dev->config;
82
83 /* Enable ACMP Clock */
84 err = clock_control_on(config->clock_dev, (clock_control_subsys_t)&config->clock_cfg);
85 if (err < 0) {
86 return err;
87 }
88
89 err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
90 if (err < 0 && err != -ENOENT) {
91 LOG_ERR("failed to allocate silabs,analog-bus via pinctrl");
92 return err;
93 }
94
95 /* Initialize the ACMP */
96 ACMP_Init(config->base, &config->init);
97
98 /* Configure the ACMP Input Channels */
99 ACMP_ChannelSet(config->base, config->input_negative, config->input_positive);
100
101 /* After initialization, the comparator should not yet be enabled. It was temporarily
102 * enabled to perform input configuration since the INPUTCTRL register has the SYNC type,
103 * disable it again here. It will be enabled by the PM resume action.
104 */
105 ACMP_Disable(config->base);
106
107 /* Initialize the irq handler */
108 config->irq_init();
109
110 return pm_device_driver_init(dev, acmp_pm_action);
111 }
112
acmp_get_output(const struct device * dev)113 static int acmp_get_output(const struct device *dev)
114 {
115 const struct acmp_config *config = dev->config;
116
117 return config->base->STATUS & ACMP_STATUS_ACMPOUT;
118 }
119
acmp_set_trigger(const struct device * dev,enum comparator_trigger trigger)120 static int acmp_set_trigger(const struct device *dev, enum comparator_trigger trigger)
121 {
122 const struct acmp_config *config = dev->config;
123 struct acmp_data *data = dev->data;
124
125 /* Disable ACMP trigger interrupts */
126 ACMP_IntDisable(config->base, ACMP_IEN_RISE | ACMP_IEN_FALL);
127
128 /* Clear ACMP trigger interrupt flags */
129 ACMP_IntClear(config->base, ACMP_IEN_RISE | ACMP_IEN_FALL);
130
131 switch (trigger) {
132 case COMPARATOR_TRIGGER_BOTH_EDGES:
133 data->interrupt_mask = ACMP_IEN_RISE | ACMP_IEN_FALL;
134 break;
135 case COMPARATOR_TRIGGER_RISING_EDGE:
136 data->interrupt_mask = ACMP_IEN_RISE;
137 break;
138 case COMPARATOR_TRIGGER_FALLING_EDGE:
139 data->interrupt_mask = ACMP_IEN_FALL;
140 break;
141 case COMPARATOR_TRIGGER_NONE:
142 data->interrupt_mask = 0;
143 break;
144 default:
145 return -EINVAL;
146 }
147
148 /* Only enable interrupts when the trigger is not none and if a
149 * callback is set.
150 */
151 if (data->interrupt_mask && data->callback != NULL) {
152 ACMP_IntEnable(config->base, data->interrupt_mask);
153 }
154
155 return 0;
156 }
157
acmp_set_trigger_callback(const struct device * dev,comparator_callback_t callback,void * user_data)158 static int acmp_set_trigger_callback(const struct device *dev, comparator_callback_t callback,
159 void *user_data)
160 {
161 const struct acmp_config *config = dev->config;
162 struct acmp_data *data = dev->data;
163
164 /* Disable ACMP trigger interrupts while setting callback */
165 ACMP_IntDisable(config->base, ACMP_IEN_RISE | ACMP_IEN_FALL);
166
167 data->callback = callback;
168 data->user_data = user_data;
169
170 if (data->callback == NULL) {
171 return 0;
172 }
173
174 /* Re-enable currently set ACMP trigger interrupts */
175 if (data->interrupt_mask) {
176 ACMP_IntEnable(config->base, data->interrupt_mask);
177 }
178
179 return 0;
180 }
181
acmp_trigger_is_pending(const struct device * dev)182 static int acmp_trigger_is_pending(const struct device *dev)
183 {
184 const struct acmp_config *config = dev->config;
185 const struct acmp_data *data = dev->data;
186
187 if (ACMP_IntGet(config->base) & data->interrupt_mask) {
188 ACMP_IntClear(config->base, data->interrupt_mask);
189 return 1;
190 }
191
192 return 0;
193 }
194
acmp_irq_handler(const struct device * dev)195 static void acmp_irq_handler(const struct device *dev)
196 {
197 const struct acmp_config *config = dev->config;
198 struct acmp_data *data = dev->data;
199
200 ACMP_IntClear(config->base, ACMP_IF_RISE | ACMP_IF_FALL);
201
202 if (data->callback == NULL) {
203 return;
204 }
205
206 data->callback(dev, data->user_data);
207 }
208
209 static DEVICE_API(comparator, acmp_api) = {
210 .get_output = acmp_get_output,
211 .set_trigger = acmp_set_trigger,
212 .set_trigger_callback = acmp_set_trigger_callback,
213 .trigger_is_pending = acmp_trigger_is_pending,
214 };
215
216 #define ACMP_DEVICE(inst) \
217 PINCTRL_DT_INST_DEFINE(inst); \
218 PM_DEVICE_DT_INST_DEFINE(inst, acmp_pm_action); \
219 \
220 static void acmp_irq_init##inst(void) \
221 { \
222 IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), acmp_irq_handler, \
223 DEVICE_DT_INST_GET(inst), 0); \
224 \
225 irq_enable(DT_INST_IRQN(inst)); \
226 } \
227 \
228 static struct acmp_data acmp_data##inst; \
229 \
230 static const struct acmp_config acmp_config##inst = { \
231 .base = (ACMP_TypeDef *)DT_INST_REG_ADDR(inst), \
232 .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
233 .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \
234 .clock_cfg = SILABS_DT_INST_CLOCK_CFG(inst), \
235 .irq_init = acmp_irq_init##inst, \
236 .init.biasProg = DT_INST_PROP(inst, bias), \
237 .init.inputRange = DT_INST_ENUM_IDX(inst, input_range), \
238 .init.accuracy = DT_INST_ENUM_IDX(inst, accuracy_mode), \
239 .init.hysteresisLevel = DT_INST_ENUM_IDX(inst, hysteresis_mode), \
240 .init.inactiveValue = false, \
241 .init.vrefDiv = DT_INST_PROP(inst, vref_divider), \
242 .init.enable = true, \
243 .input_negative = DT_INST_PROP(inst, input_negative), \
244 .input_positive = DT_INST_PROP(inst, input_positive), \
245 }; \
246 \
247 DEVICE_DT_INST_DEFINE(inst, acmp_init, PM_DEVICE_DT_INST_GET(inst), &acmp_data##inst, \
248 &acmp_config##inst, POST_KERNEL, CONFIG_COMPARATOR_INIT_PRIORITY, \
249 &acmp_api);
250
251 DT_INST_FOREACH_STATUS_OKAY(ACMP_DEVICE)
252