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