Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

408 lines
13 KiB

/*
* Copyright (c) 2025 ITE Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ite_it51xxx_vcmp
#include <zephyr/device.h>
#include <zephyr/devicetree/io-channels.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/comparator.h>
#include <zephyr/dt-bindings/comparator/it51xxx-vcmp.h>
#include <zephyr/dt-bindings/dt-util.h>
#include <soc_common.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(comparator_it51xxx_vcmp, CONFIG_COMPARATOR_LOG_LEVEL);
#define VCMP_CHANNEL_ID_REG_MASK GENMASK(2, 0)
#define VCMP_THRESHOLD BIT(10)
#ifdef CONFIG_ADC_IT51XXX_VOL_FULL_SCALE
#define VCMP_MAX_MVOLT 3300
#else
#define VCMP_MAX_MVOLT 3000
#endif
/* 0x20, 0x28, 0x2c: Voltage Comparator n Control Register (VCMPnCTL) (n=0 to 2) */
#define REG_VCMP_VCMP0CTL 0x20
#define REG_VCMP_VCMP1CTL 0x28
#define REG_VCMP_VCMP2CTL 0x2C
#define VCMP_CMPEN BIT(7)
#define VCMP_CMPINTEN BIT(6)
#define VCMP_GREATER_THRESHOLD BIT(5)
#define VCMP_CMP_EDGE_SENSED_MODE BIT(4)
const uint8_t vcmp_ctrl_reg[VCMP_CHANNEL_CNT] = {REG_VCMP_VCMP0CTL, REG_VCMP_VCMP1CTL,
REG_VCMP_VCMP2CTL};
/* 0x21, 0x29, 0x2D: Voltage Comparator n Status and Control Register (VCMPnSCTL) (n=0 to 2) */
#define REG_VCMP_VCMP0SCTL 0x21
#define REG_VCMP_VCMP1SCTL 0x29
#define REG_VCMP_VCMP2SCTL 0x2D
#define VCMP_CMPXRTIS BIT(6)
const uint8_t vcmp_status_ctrl_reg[VCMP_CHANNEL_CNT] = {REG_VCMP_VCMP0SCTL, REG_VCMP_VCMP1SCTL,
REG_VCMP_VCMP2SCTL};
/* 0x22, 0x2A, 0x2E: Voltage Comparator 0~2 MSB Threshold Data Buffer (10-bit resolution) */
#define REG_VCMP_CH_THRDATM 0x02
/* 0x23, 0x2B, 0x2F: Voltage Comparator 0~2 LSB Threshold Data Buffer (10-bit resolution) */
#define REG_VCMP_CH_THRDATL 0x03
/* 0x33: Voltage Comparator Scan Period 2 (VCMPSCP2) */
#define REG_VCMP_VCMPSCP2 0x33
#define SCAN_PERIOD_MASK GENMASK(7, 4)
/* Device config */
struct vcmp_it51xxx_config {
/* Voltage comparator channel base address */
uintptr_t base_ch;
/* Voltage comparator base address */
uintptr_t reg_base;
/* Voltage comparator module irq */
int irq;
/* Voltage comparator channel */
int vcmp_ch;
/* Comparator 0/1/2 Scan Period */
uint8_t scan_period;
/* Threshold assert value in mv */
int32_t threshold_mv;
/* Pointer of ADC device that will be performing measurement */
const struct device *adc;
/* Channel identifier */
uint8_t channel_id;
};
/* Driver data */
struct vcmp_it51xxx_data {
/* ADC channel config */
struct adc_channel_cfg adc_ch_cfg;
comparator_callback_t user_cb;
void *user_cb_data;
uint8_t interrupt_mask;
atomic_t triggered;
/* Pointer of voltage comparator device */
const struct device *vcmp;
};
/*
* Because all the three voltage comparators use the same interrupt number, the
* 'irq_connect_dynamic' call in the driver init function sets the passed-in dev pointer in isr
* function to the first instantiated voltage comparator.
*
* For example, when setting vcmp2 and vcmp3 to 'status = "okay";' in dts file, each time an
* interrupt is triggered,the passed-in 'const struct device *dev' argument in isr function always
* points to vcmp2 device.
*
* To access the 'struct vcmp_it51xxx_data' of respective voltage comparator instance, we
* use a array to store their address.
*/
static struct vcmp_it51xxx_data *vcmp_data[VCMP_CHANNEL_CNT];
static void vcmp_irq_handler(const struct device *dev)
{
struct vcmp_it51xxx_data *const data = dev->data;
if (data->user_cb == NULL) {
atomic_set_bit(&data->triggered, 0);
return;
}
data->user_cb(dev, data->user_cb_data);
atomic_clear_bit(&data->triggered, 0);
}
static void clear_vcmp_status(const struct device *dev, int channel)
{
const struct vcmp_it51xxx_config *const cfg = dev->config;
const uintptr_t reg_base = cfg->reg_base;
sys_write8(sys_read8(reg_base + vcmp_status_ctrl_reg[channel]),
reg_base + vcmp_status_ctrl_reg[channel]);
}
static int vcmp_set_threshold(const struct device *dev, int32_t threshold_mv)
{
const struct vcmp_it51xxx_config *const cfg = dev->config;
const uintptr_t base_ch = cfg->base_ch;
int32_t reg_val;
/*
* Tranfrom threshold from mv to raw
* NOTE: CMPXTHRDAT[9:0] = threshold(mv) * 1024 / 3000(mv)
*/
reg_val = (threshold_mv * VCMP_THRESHOLD / VCMP_MAX_MVOLT);
if (reg_val >= VCMP_THRESHOLD) {
LOG_ERR("Vcmp%d threshold only support 10-bits", cfg->vcmp_ch);
return -ENOTSUP;
}
/* Set threshold raw value */
sys_write8(reg_val & 0xff, base_ch + REG_VCMP_CH_THRDATL);
sys_write8((reg_val >> 8) & 0xff, base_ch + REG_VCMP_CH_THRDATM);
return 0;
}
static void vcmp_set_attr(const struct device *dev, enum comparator_trigger trigger)
{
const struct vcmp_it51xxx_config *const cfg = dev->config;
const uintptr_t base_ch = cfg->base_ch;
/* Set lower or higher threshold */
if (trigger == COMPARATOR_TRIGGER_RISING_EDGE) {
sys_write8(sys_read8(base_ch) | VCMP_GREATER_THRESHOLD, base_ch);
} else {
sys_write8(sys_read8(base_ch) & ~VCMP_GREATER_THRESHOLD, base_ch);
}
}
static void vcmp_enable(const struct device *dev, bool enable)
{
const struct vcmp_it51xxx_config *const cfg = dev->config;
const uintptr_t base_ch = cfg->base_ch;
if (enable) {
/* Enable voltage comparator interrupt */
sys_write8(sys_read8(base_ch) | VCMP_CMPINTEN, base_ch);
/* Start voltage comparator */
sys_write8(sys_read8(base_ch) | VCMP_CMPEN, base_ch);
} else {
/* Disable voltage comparator interrupt */
sys_write8(sys_read8(base_ch) & ~VCMP_CMPINTEN, base_ch);
/* Stop voltage comparator */
sys_write8(sys_read8(base_ch) & ~VCMP_CMPEN, base_ch);
}
}
static int it51xxx_vcmp_get_output(const struct device *dev)
{
ARG_UNUSED(dev);
LOG_ERR("Unsupported function: %s", __func__);
return -ENOTSUP;
}
static int it51xxx_vcmp_set_trigger(const struct device *dev, enum comparator_trigger trigger)
{
const struct vcmp_it51xxx_config *cfg = dev->config;
struct vcmp_it51xxx_data *data = dev->data;
/* Disable VCMP interrupt */
vcmp_enable(dev, false);
/* W/C voltage comparator specific channel interrupt status */
clear_vcmp_status(dev, cfg->vcmp_ch);
switch (trigger) {
case COMPARATOR_TRIGGER_BOTH_EDGES:
LOG_ERR("Unsupported trigger: COMPARATOR_TRIGGER_BOTH_EDGES");
return -ENOTSUP;
case COMPARATOR_TRIGGER_RISING_EDGE:
data->interrupt_mask = COMPARATOR_TRIGGER_RISING_EDGE;
vcmp_set_attr(dev, COMPARATOR_TRIGGER_RISING_EDGE);
break;
case COMPARATOR_TRIGGER_FALLING_EDGE:
data->interrupt_mask = COMPARATOR_TRIGGER_FALLING_EDGE;
vcmp_set_attr(dev, COMPARATOR_TRIGGER_FALLING_EDGE);
break;
case COMPARATOR_TRIGGER_NONE:
data->interrupt_mask = 0;
break;
default:
return -EINVAL;
}
if (data->interrupt_mask) {
vcmp_enable(dev, true);
}
return 0;
}
static int it51xxx_vcmp_set_trigger_callback(const struct device *dev,
comparator_callback_t callback, void *user_data)
{
struct vcmp_it51xxx_data *data = dev->data;
/* Disable voltage comparator and interrupt */
vcmp_enable(dev, false);
data->user_cb = callback;
data->user_cb_data = user_data;
if (callback != NULL && atomic_test_and_clear_bit(&data->triggered, 0)) {
callback(dev, user_data);
}
/* Re-enable currently set VCMP interrupt */
if (data->interrupt_mask) {
vcmp_enable(dev, true);
}
return 0;
}
static int it51xxx_vcmp_trigger_is_pending(const struct device *dev)
{
struct vcmp_it51xxx_data *data = dev->data;
return atomic_test_and_clear_bit(&data->triggered, 0);
}
/*
* All voltage comparator channels share one irq interrupt, so we
* need to handle all channels, when the interrupt fired.
*/
static void vcmp_it51xxx_isr(const struct device *dev)
{
const struct vcmp_it51xxx_config *const cfg = dev->config;
const uintptr_t reg_base = cfg->reg_base;
/*
* Comparator n Trigger Mode (CMPnTMOD)
* false (0): Less than or equal to CMPnTHRDAT [9:0]
* true (1): Greater than CMPnTHRDAT [9:0]
*/
bool comparator_mode;
/* Find out which voltage comparator triggered */
for (int idx = VCMP_CHANNEL_0; idx < VCMP_CHANNEL_CNT; idx++) {
if (sys_read8(reg_base + vcmp_status_ctrl_reg[idx]) & VCMP_CMPXRTIS) {
struct vcmp_it51xxx_data *data = vcmp_data[idx];
comparator_mode =
sys_read8(reg_base + vcmp_ctrl_reg[idx]) & VCMP_GREATER_THRESHOLD;
if ((comparator_mode && data &&
data->interrupt_mask == COMPARATOR_TRIGGER_RISING_EDGE) ||
(!comparator_mode && data &&
data->interrupt_mask == COMPARATOR_TRIGGER_FALLING_EDGE)) {
vcmp_irq_handler(data->vcmp);
}
if (comparator_mode) {
sys_write8(sys_read8(reg_base + vcmp_ctrl_reg[idx]) &
~VCMP_GREATER_THRESHOLD,
reg_base + vcmp_ctrl_reg[idx]);
} else {
sys_write8(sys_read8(reg_base + vcmp_ctrl_reg[idx]) |
VCMP_GREATER_THRESHOLD,
reg_base + vcmp_ctrl_reg[idx]);
}
/* W/C voltage comparator specific channel interrupt status */
clear_vcmp_status(dev, idx);
}
}
/* W/C voltage comparator irq interrupt status */
ite_intc_isr_clear(cfg->irq);
}
static int vcmp_it51xxx_init(const struct device *dev)
{
const struct vcmp_it51xxx_config *const cfg = dev->config;
struct vcmp_it51xxx_data *const data = dev->data;
uintptr_t base_ch = cfg->base_ch;
uintptr_t reg_vcmpscp2 = cfg->reg_base + REG_VCMP_VCMPSCP2;
uint8_t reg_value;
int err;
data->adc_ch_cfg.gain = ADC_GAIN_1;
data->adc_ch_cfg.reference = ADC_REF_INTERNAL;
data->adc_ch_cfg.acquisition_time = ADC_ACQ_TIME_DEFAULT;
data->adc_ch_cfg.channel_id = cfg->channel_id;
/* Disable voltage comparator and interrupt */
vcmp_enable(dev, false);
/*
* ADC channel signal output to voltage comparator,
* so we need to set ADC channel to alternate mode first.
*/
if (!device_is_ready(cfg->adc)) {
LOG_ERR("ADC device not ready");
return -ENODEV;
}
err = adc_channel_setup(cfg->adc, &data->adc_ch_cfg);
if (err) {
return err;
}
/* Select which ADC channel output voltage into comparator */
reg_value = FIELD_PREP(GENMASK(7, 3), sys_read8(base_ch));
reg_value |= data->adc_ch_cfg.channel_id & VCMP_CHANNEL_ID_REG_MASK;
sys_write8(reg_value, base_ch);
/* Set VCMP to Edge Sense Mode */
sys_write8(sys_read8(base_ch) | VCMP_CMP_EDGE_SENSED_MODE, base_ch);
/* Store the address of driver data for later access in ISR function*/
if (cfg->vcmp_ch >= VCMP_CHANNEL_CNT) {
LOG_ERR("invalid volt comparator channel setting(%d)", cfg->vcmp_ch);
return -EINVAL;
}
vcmp_data[cfg->vcmp_ch] = dev->data;
/*
* Set minimum scan period for "all" voltage comparator
* Three voltage comparators share a scan period setting and use the fastest one
*/
if (cfg->scan_period < FIELD_GET(SCAN_PERIOD_MASK, sys_read8(reg_vcmpscp2))) {
sys_write8(FIELD_PREP(SCAN_PERIOD_MASK, cfg->scan_period), reg_vcmpscp2);
}
/* Data must keep device reference for later access in ISR function */
data->vcmp = dev;
err = vcmp_set_threshold(dev, cfg->threshold_mv);
if (err) {
return err;
}
/*
* 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_it51xxx_isr().
*/
if (!irq_is_enabled(cfg->irq)) {
ite_intc_isr_clear(cfg->irq);
irq_connect_dynamic(cfg->irq, 0, (void (*)(const void *))vcmp_it51xxx_isr,
(const void *)dev, 0);
irq_enable(cfg->irq);
}
return 0;
}
static DEVICE_API(comparator, it51xxx_vcmp_api) = {
.get_output = it51xxx_vcmp_get_output,
.set_trigger = it51xxx_vcmp_set_trigger,
.set_trigger_callback = it51xxx_vcmp_set_trigger_callback,
.trigger_is_pending = it51xxx_vcmp_trigger_is_pending,
};
#define VCMP_IT51XXX_INIT(inst) \
static const struct vcmp_it51xxx_config vcmp_it51xxx_cfg_##inst = { \
.base_ch = DT_INST_REG_ADDR_BY_IDX(inst, 0), \
.reg_base = DT_INST_REG_ADDR_BY_IDX(inst, 1), \
.irq = DT_INST_IRQN(inst), \
.vcmp_ch = DT_INST_PROP(inst, vcmp_ch), \
.scan_period = DT_INST_PROP(inst, scan_period), \
.threshold_mv = DT_INST_PROP(inst, threshold_mv), \
.adc = DEVICE_DT_GET(DT_INST_IO_CHANNELS_CTLR(inst)), \
.channel_id = (uint8_t)DT_INST_IO_CHANNELS_INPUT(inst), \
}; \
\
static struct vcmp_it51xxx_data vcmp_it51xxx_data_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, vcmp_it51xxx_init, NULL, &vcmp_it51xxx_data_##inst, \
&vcmp_it51xxx_cfg_##inst, POST_KERNEL, \
CONFIG_COMPARATOR_INIT_PRIORITY, &it51xxx_vcmp_api);
DT_INST_FOREACH_STATUS_OKAY(VCMP_IT51XXX_INIT)