1 /*
2 * Copyright (c) 2022 ITE Technology Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT ite_it8xxx2_vcmp
8
9 #include <zephyr/device.h>
10 #include <zephyr/devicetree/io-channels.h>
11 #include <zephyr/drivers/adc.h>
12 #include <zephyr/drivers/sensor.h>
13 #include <zephyr/drivers/sensor/it8xxx2_vcmp.h>
14 #include <zephyr/dt-bindings/dt-util.h>
15 #include <zephyr/dt-bindings/sensor/it8xxx2_vcmp.h>
16 #include <errno.h>
17 #include <soc.h>
18 #include <soc_dt.h>
19
20 #include <zephyr/logging/log.h>
21 LOG_MODULE_REGISTER(vcmp_ite_it8xxx2, CONFIG_SENSOR_LOG_LEVEL);
22
23 #define VCMP_REG_MASK 0x7
24 #define VCMP_RESOLUTION BIT(10)
25 #ifdef CONFIG_ADC_IT8XXX2_VOL_FULL_SCALE
26 #define VCMP_MAX_MVOLT 3300
27 #else
28 #define VCMP_MAX_MVOLT 3000
29 #endif
30
31 /* Device config */
32 struct vcmp_it8xxx2_config {
33 /* Voltage comparator x control register */
34 volatile uint8_t *reg_vcmpxctl;
35 /* Voltage comparator x channel select MSB register */
36 volatile uint8_t *reg_vcmpxcselm;
37 /* Voltage comparator scan period register */
38 volatile uint8_t *reg_vcmpscp;
39 /* Voltage comparator x threshold data buffer MSB register */
40 volatile uint8_t *reg_vcmpxthrdatm;
41 /* Voltage comparator x threshold data buffer LSB register */
42 volatile uint8_t *reg_vcmpxthrdatl;
43 /* Voltage comparator status register */
44 volatile uint8_t *reg_vcmpsts;
45 /* Voltage comparator status 2 register */
46 volatile uint8_t *reg_vcmpsts2;
47 /* Voltage comparator module irq */
48 int irq;
49 /* Voltage comparator channel */
50 int vcmp_ch;
51 /* Scan period for "all voltage comparator channel" */
52 int scan_period;
53 /*
54 * Determines the condition between ADC data and threshold_mv
55 * that will trigger voltage comparator interrupt.
56 */
57 int comparison;
58 /* Threshold assert value in mv */
59 int threshold_mv;
60 /* Pointer of ADC device that will be performing measurement */
61 const struct device *adc;
62 };
63
64 /* Driver data */
65 struct vcmp_it8xxx2_data {
66 /* ADC channel config */
67 struct adc_channel_cfg adc_ch_cfg;
68 /* Work queue to be notified when threshold assertion happens */
69 struct k_work work;
70 /* Sensor trigger hanlder to notify user of assetion */
71 sensor_trigger_handler_t handler;
72 const struct sensor_trigger *trig;
73 /* Pointer of voltage comparator device */
74 const struct device *vcmp;
75 };
76
77 /* Voltage comparator work queue address */
78 static uint32_t vcmp_work_addr[VCMP_CHANNEL_CNT];
79 #ifdef CONFIG_VCMP_IT8XXX2_WORKQUEUE
80 /*
81 * Pointer of work queue thread to be notified when threshold assertion
82 * occurs.
83 */
84 struct k_work_q *work_q;
85 #endif
86
clear_vcmp_status(const struct device * dev,int vcmp_ch)87 static void clear_vcmp_status(const struct device *dev, int vcmp_ch)
88 {
89 const struct vcmp_it8xxx2_config *const config = dev->config;
90 volatile uint8_t *reg_vcmpsts = config->reg_vcmpsts;
91 volatile uint8_t *reg_vcmpsts2 = config->reg_vcmpsts2;
92
93 /* W/C voltage comparator specific channel interrupt status */
94 if (vcmp_ch <= VCMP_CHANNEL_2) {
95 *reg_vcmpsts = BIT(vcmp_ch);
96 } else {
97 *reg_vcmpsts2 = BIT(vcmp_ch - VCMP_CHANNEL_3);
98 }
99 }
100
vcmp_enable(const struct device * dev,int enable)101 static void vcmp_enable(const struct device *dev, int enable)
102 {
103 const struct vcmp_it8xxx2_config *const config = dev->config;
104 volatile uint8_t *reg_vcmpxctl = config->reg_vcmpxctl;
105
106 if (enable) {
107 /* Enable voltage comparator specific channel interrupt */
108 *reg_vcmpxctl |= IT8XXX2_VCMP_CMPINTEN;
109 /* Start voltage comparator specific channel */
110 *reg_vcmpxctl |= IT8XXX2_VCMP_CMPEN;
111 } else {
112 /* Stop voltage comparator specific channel */
113 *reg_vcmpxctl &= ~IT8XXX2_VCMP_CMPEN;
114 /* Disable voltage comparator specific channel interrupt */
115 *reg_vcmpxctl &= ~IT8XXX2_VCMP_CMPINTEN;
116 }
117 }
118
vcmp_set_threshold(const struct device * dev,enum sensor_attribute attr,int32_t reg_val)119 static int vcmp_set_threshold(const struct device *dev,
120 enum sensor_attribute attr,
121 int32_t reg_val)
122 {
123 const struct vcmp_it8xxx2_config *const config = dev->config;
124 volatile uint8_t *reg_vcmpxthrdatm = config->reg_vcmpxthrdatm;
125 volatile uint8_t *reg_vcmpxthrdatl = config->reg_vcmpxthrdatl;
126 volatile uint8_t *reg_vcmpxctl = config->reg_vcmpxctl;
127
128 if (reg_val >= VCMP_RESOLUTION) {
129 LOG_ERR("Vcmp%d threshold only support 10-bits", config->vcmp_ch);
130 return -ENOTSUP;
131 }
132
133 /* Set threshold raw value */
134 *reg_vcmpxthrdatl = (uint8_t)(reg_val & 0xff);
135 *reg_vcmpxthrdatm = (uint8_t)((reg_val >> 8) & 0xff);
136
137 /* Set lower or higher threshold */
138 if ((attr == SENSOR_ATTR_UPPER_THRESH) ||
139 (attr == (uint16_t) SENSOR_ATTR_UPPER_VOLTAGE_THRESH)) {
140 *reg_vcmpxctl |= IT8XXX2_VCMP_GREATER_THRESHOLD;
141 } else {
142 *reg_vcmpxctl &= ~IT8XXX2_VCMP_GREATER_THRESHOLD;
143 }
144
145 return 0;
146 }
147
it8xxx2_vcmp_trigger_work_handler(struct k_work * item)148 static void it8xxx2_vcmp_trigger_work_handler(struct k_work *item)
149 {
150 struct vcmp_it8xxx2_data *data =
151 CONTAINER_OF(item, struct vcmp_it8xxx2_data, work);
152
153 if (data->handler) {
154 data->handler(data->vcmp, data->trig);
155 }
156 }
157
vcmp_ite_it8xxx2_attr_set(const struct device * dev,enum sensor_channel chan,enum sensor_attribute attr,const struct sensor_value * val)158 static int vcmp_ite_it8xxx2_attr_set(const struct device *dev,
159 enum sensor_channel chan,
160 enum sensor_attribute attr,
161 const struct sensor_value *val)
162 {
163 const struct vcmp_it8xxx2_config *const config = dev->config;
164 int32_t reg_val, ret = 0;
165
166 if (chan != SENSOR_CHAN_VOLTAGE) {
167 return -ENOTSUP;
168 }
169
170 switch ((uint16_t)attr) {
171 case SENSOR_ATTR_LOWER_THRESH:
172 case SENSOR_ATTR_UPPER_THRESH:
173 ret = vcmp_set_threshold(dev, attr, val->val1);
174 break;
175 case SENSOR_ATTR_LOWER_VOLTAGE_THRESH:
176 case SENSOR_ATTR_UPPER_VOLTAGE_THRESH:
177 /*
178 * Tranfrom threshold from mv to raw
179 * NOTE: CMPXTHRDAT[9:0] = threshold(mv) * 1024 / 3000(mv)
180 */
181 reg_val = (val->val1 * VCMP_RESOLUTION / VCMP_MAX_MVOLT);
182 ret = vcmp_set_threshold(dev, attr, reg_val);
183 break;
184 case SENSOR_ATTR_ALERT:
185 if (!!val->val1) {
186 clear_vcmp_status(dev, config->vcmp_ch);
187 vcmp_enable(dev, 1);
188 } else {
189 vcmp_enable(dev, 0);
190 clear_vcmp_status(dev, config->vcmp_ch);
191 }
192
193 break;
194 default:
195 ret = -ENOTSUP;
196 }
197
198 return ret;
199 }
200
vcmp_ite_it8xxx2_trigger_set(const struct device * dev,const struct sensor_trigger * trig,sensor_trigger_handler_t handler)201 static int vcmp_ite_it8xxx2_trigger_set(const struct device *dev,
202 const struct sensor_trigger *trig,
203 sensor_trigger_handler_t handler)
204 {
205 const struct vcmp_it8xxx2_config *const config = dev->config;
206 struct vcmp_it8xxx2_data *const data = dev->data;
207
208 if (trig->type != SENSOR_TRIG_THRESHOLD ||
209 trig->chan != SENSOR_CHAN_VOLTAGE) {
210 return -ENOTSUP;
211 }
212
213 data->handler = handler;
214 data->trig = trig;
215
216 vcmp_work_addr[config->vcmp_ch] = (uint32_t) &data->work;
217
218 return 0;
219 }
220
vcmp_it8xxx2_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)221 static int vcmp_it8xxx2_channel_get(const struct device *dev,
222 enum sensor_channel chan,
223 struct sensor_value *val)
224 {
225 const struct vcmp_it8xxx2_config *const config = dev->config;
226
227 if (chan != SENSOR_CHAN_VOLTAGE) {
228 return -ENOTSUP;
229 }
230
231 /*
232 * It8xxx2 adc and comparator module read automatically, according to
233 * {ADCCTS1, ADCCTS2} and VCMPSCP register setting.
234 */
235 val->val1 = config->vcmp_ch;
236
237 return 0;
238 }
239
240 /*
241 * All voltage comparator channels share one irq interrupt, so we
242 * need to handle all channels, when the interrupt fired.
243 */
vcmp_it8xxx2_isr(const struct device * dev)244 static void vcmp_it8xxx2_isr(const struct device *dev)
245 {
246 const struct vcmp_it8xxx2_config *const config = dev->config;
247 volatile uint8_t *reg_vcmpsts = config->reg_vcmpsts;
248 volatile uint8_t *reg_vcmpsts2 = config->reg_vcmpsts2;
249 int idx, status;
250
251 /* Find out which voltage comparator triggered */
252 status = *reg_vcmpsts & VCMP_REG_MASK;
253 status |= (*reg_vcmpsts2 & VCMP_REG_MASK) << 3;
254
255 for (idx = VCMP_CHANNEL_0; idx < VCMP_CHANNEL_CNT; idx++) {
256 if (status & BIT(idx)) {
257 /* Call triggered channel callback function in work queue */
258 if (vcmp_work_addr[idx]) {
259 #ifdef CONFIG_VCMP_IT8XXX2_WORKQUEUE
260 k_work_submit_to_queue(work_q,
261 (struct k_work *) vcmp_work_addr[idx]);
262 #else
263 k_work_submit((struct k_work *) vcmp_work_addr[idx]);
264 #endif
265 }
266 /* W/C voltage comparator specific channel interrupt status */
267 clear_vcmp_status(dev, idx);
268 }
269 }
270
271 /* W/C voltage comparator irq interrupt status */
272 ite_intc_isr_clear(config->irq);
273 }
274
vcmp_it8xxx2_init(const struct device * dev)275 static int vcmp_it8xxx2_init(const struct device *dev)
276 {
277 const struct vcmp_it8xxx2_config *const config = dev->config;
278 struct vcmp_it8xxx2_data *const data = dev->data;
279 volatile uint8_t *reg_vcmpxctl = config->reg_vcmpxctl;
280 volatile uint8_t *reg_vcmpxcselm = config->reg_vcmpxcselm;
281 volatile uint8_t *reg_vcmpscp = config->reg_vcmpscp;
282
283 /* Disable voltage comparator specific channel before init */
284 vcmp_enable(dev, 0);
285
286 /*
287 * ADC channel signal output to voltage comparator,
288 * so we need to set ADC channel to alternate mode first.
289 */
290 if (!device_is_ready(config->adc)) {
291 LOG_ERR("ADC device not ready");
292 return -ENODEV;
293 }
294 adc_channel_setup(config->adc, &data->adc_ch_cfg);
295
296 /* Select which ADC channel output voltage into comparator */
297 if (data->adc_ch_cfg.channel_id <= 7) {
298 /* ADC channel 0~7 map to value 0x0~0x7 */
299 *reg_vcmpxctl |= data->adc_ch_cfg.channel_id & VCMP_REG_MASK;
300 *reg_vcmpxcselm &= ~IT8XXX2_VCMP_VCMPXCSELM;
301 } else {
302 /* ADC channel 13~16 map to value 0x8~0xb */
303 *reg_vcmpxctl |= (data->adc_ch_cfg.channel_id - 5) & VCMP_REG_MASK;
304 *reg_vcmpxcselm |= IT8XXX2_VCMP_VCMPXCSELM;
305 }
306
307 /* Set minimum scan period for "all voltage comparator channel" */
308 if (*reg_vcmpscp > config->scan_period) {
309 *reg_vcmpscp = config->scan_period;
310 }
311
312 /* Data must keep device reference for worker handler */
313 data->vcmp = dev;
314
315 /* Init and set work item to enable notifications */
316 k_work_init(&data->work, it8xxx2_vcmp_trigger_work_handler);
317 vcmp_work_addr[config->vcmp_ch] = (uint32_t) &data->work;
318
319 /* Set threshold and comparison if set on device tree */
320 if ((config->threshold_mv != IT8XXX2_VCMP_UNDEFINED) &&
321 (config->comparison != IT8XXX2_VCMP_UNDEFINED)) {
322 enum sensor_attribute attr;
323 struct sensor_value val;
324
325 if (config->comparison == IT8XXX2_VCMP_LESS_OR_EQUAL) {
326 attr = SENSOR_ATTR_LOWER_VOLTAGE_THRESH;
327 } else {
328 attr = SENSOR_ATTR_UPPER_VOLTAGE_THRESH;
329 }
330
331 val.val1 = config->threshold_mv;
332 val.val2 = 0;
333
334 vcmp_ite_it8xxx2_attr_set(dev, SENSOR_CHAN_VOLTAGE, attr, &val);
335 }
336
337 /*
338 * All voltage comparator channels share one irq interrupt,
339 * so if the irq is enabled before, we needn't to enable again.
340 * And we will figure out the triggered channel in vcmp_it8xxx2_isr().
341 */
342 if (!irq_is_enabled(config->irq)) {
343 ite_intc_isr_clear(config->irq);
344
345 irq_connect_dynamic(config->irq, 0,
346 (void (*)(const void *))vcmp_it8xxx2_isr,
347 (const void *)dev, 0);
348
349 irq_enable(config->irq);
350 }
351
352 return 0;
353 }
354
355 static DEVICE_API(sensor, vcmp_ite_it8xxx2_api) = {
356 .attr_set = vcmp_ite_it8xxx2_attr_set,
357 .trigger_set = vcmp_ite_it8xxx2_trigger_set,
358 .channel_get = vcmp_it8xxx2_channel_get,
359 };
360
361 #ifdef CONFIG_VCMP_IT8XXX2_WORKQUEUE
362 struct k_work_q vcmp_it8xxx2_work_q;
363
364 static K_KERNEL_STACK_DEFINE(vcmp_it8xxx2_work_q_stack,
365 CONFIG_VCMP_IT8XXX2_WORKQUEUE_STACK_SIZE);
366
vcmp_it8xxx2_init_work_q(void)367 static int vcmp_it8xxx2_init_work_q(void)
368 {
369 struct k_work_queue_config cfg = {
370 .name = "vcmp_work",
371 .no_yield = false,
372 };
373
374 k_work_queue_start(&vcmp_it8xxx2_work_q,
375 vcmp_it8xxx2_work_q_stack,
376 K_KERNEL_STACK_SIZEOF(vcmp_it8xxx2_work_q_stack),
377 CONFIG_VCMP_IT8XXX2_WORKQUEUE_PRIORITY, &cfg);
378
379 work_q = &vcmp_it8xxx2_work_q;
380 return 0;
381 }
382
383 SYS_INIT(vcmp_it8xxx2_init_work_q, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY);
384 #endif
385
386 #define VCMP_IT8XXX2_INIT(inst) \
387 static const struct vcmp_it8xxx2_config vcmp_it8xxx2_cfg_##inst = { \
388 .reg_vcmpxctl = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 0), \
389 .reg_vcmpxcselm = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 1), \
390 .reg_vcmpscp = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 2), \
391 .reg_vcmpxthrdatm = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 3), \
392 .reg_vcmpxthrdatl = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 4), \
393 .reg_vcmpsts = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 5), \
394 .reg_vcmpsts2 = (uint8_t *)DT_INST_REG_ADDR_BY_IDX(inst, 6), \
395 .irq = DT_INST_IRQN(inst), \
396 .vcmp_ch = DT_INST_PROP(inst, vcmp_ch), \
397 .scan_period = DT_INST_PROP(inst, scan_period), \
398 .comparison = DT_INST_PROP(inst, comparison), \
399 .threshold_mv = DT_INST_PROP(inst, threshold_mv), \
400 .adc = DEVICE_DT_GET(DT_INST_IO_CHANNELS_CTLR(inst)), \
401 }; \
402 \
403 static struct vcmp_it8xxx2_data vcmp_it8xxx2_data_##inst = { \
404 .adc_ch_cfg.gain = ADC_GAIN_1, \
405 .adc_ch_cfg.reference = ADC_REF_INTERNAL, \
406 .adc_ch_cfg.acquisition_time = ADC_ACQ_TIME_DEFAULT, \
407 .adc_ch_cfg.channel_id = (uint8_t)DT_INST_IO_CHANNELS_INPUT(inst), \
408 }; \
409 \
410 SENSOR_DEVICE_DT_INST_DEFINE(inst, \
411 vcmp_it8xxx2_init, \
412 NULL, \
413 &vcmp_it8xxx2_data_##inst, \
414 &vcmp_it8xxx2_cfg_##inst, \
415 POST_KERNEL, \
416 CONFIG_VCMP_IT8XXX2_INIT_PRIORITY, \
417 &vcmp_ite_it8xxx2_api);
418
419 DT_INST_FOREACH_STATUS_OKAY(VCMP_IT8XXX2_INIT)
420 #ifdef CONFIG_VCMP_IT8XXX2_WORKQUEUE
421 BUILD_ASSERT(CONFIG_SENSOR_INIT_PRIORITY < CONFIG_VCMP_IT8XXX2_INIT_PRIORITY,
422 "CONFIG_SENSOR_INIT_PRIORITY must be less than CONFIG_VCMP_IT8XXX2_INIT_PRIORITY");
423 #endif
424