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