diff --git a/drivers/adc/adc_npcx.c b/drivers/adc/adc_npcx.c index b0780bfed77..0f3b03b1334 100644 --- a/drivers/adc/adc_npcx.c +++ b/drivers/adc/adc_npcx.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,12 @@ LOG_MODULE_REGISTER(adc_npcx, CONFIG_ADC_LOG_LEVEL); #define NPCX_ADC_CHN_CONVERSION_MODE 0 #define NPCX_ADC_SCAN_CONVERSION_MODE 1 +/* ADC threshold detector number */ +#define NPCX_ADC_THRESHOLD_COUNT DT_INST_PROP(0, threshold_count) + +#define ADC_NPCX_THRVAL_RESOLUTION 10 +#define ADC_NPCX_THRVAL_MAX BIT_MASK(ADC_NPCX_THRVAL_RESOLUTION) + /* Device config */ struct adc_npcx_config { /* adc controller base address */ @@ -47,6 +54,46 @@ struct adc_npcx_config { const struct npcx_alt *alts_list; }; +struct adc_npcx_threshold_control { + /* + * Selects ADC channel number, for which the measured data is compared + * for threshold detection. + */ + uint8_t chnsel; + /* + * Sets relation between measured value and assetion threshold value. + * in thrval: + * 0: Threshold event is generated if Measured data > thrval. + * 1: Threshold event is generated if Measured data <= thrval. + */ + bool l_h; + /* Sets the threshold value to which measured data is compared. */ + uint16_t thrval; + /* + * Pointer of work queue thread to be notified when threshold assertion + * occurs. + */ + struct k_work *work; +}; + +struct adc_npcx_threshold_data { + /* + * While threshold interruption is enabled we need to resume to repetitive + * sampling mode after adc_npcx_read is called. This variable records + * channels being used in repetitive mode in order to set ADC registers + * back to threshold detection when adc_npcx_read is completed. + */ + uint16_t repetitive_channels; + /* + * While threshold interruption is enabled, adc_npcx_read must disable + * all active threshold running to avoid race condition, this variable + * helps restore active threshods after adc_npcs_read has finnished. + */ + uint8_t active_thresholds; + /* This array holds current configuration for each threshold. */ + struct adc_npcx_threshold_control control[NPCX_ADC_THRESHOLD_COUNT]; +}; + /* Driver data */ struct adc_npcx_data { /* Input clock for ADC converter */ @@ -64,6 +111,8 @@ struct adc_npcx_data { uint16_t *repeat_buffer; /* end pointer of buffer to ensure enough space for storing ADC data. */ uint16_t *buf_end; + /* Threshold comparator data pointer */ + struct adc_npcx_threshold_data *threshold_data; }; /* Driver convenience defines */ @@ -75,6 +124,7 @@ static void adc_npcx_isr(const struct device *dev) const struct adc_npcx_config *config = dev->config; struct adc_npcx_data *const data = dev->data; struct adc_reg *const inst = HAL_INSTANCE(dev); + struct adc_npcx_threshold_data *const t_data = data->threshold_data; uint16_t status = inst->ADCSTS; uint16_t result, channel; @@ -83,14 +133,16 @@ static void adc_npcx_isr(const struct device *dev) LOG_DBG("%s: status is %04X\n", __func__, status); /* Is end of conversion cycle event? ie. Scan conversion is done. */ - if (IS_BIT_SET(status, NPCX_ADCSTS_EOCCEV)) { + if (IS_BIT_SET(status, NPCX_ADCSTS_EOCCEV) && + IS_BIT_SET(inst->ADCCNF, NPCX_ADCCNF_INTECCEN)) { /* Stop conversion for scan conversion mode */ inst->ADCCNF |= BIT(NPCX_ADCCNF_STOP); /* Get result for each ADC selected channel */ while (data->channels) { channel = find_lsb_set(data->channels) - 1; - result = GET_FIELD(CHNDAT(config->base, channel), NPCX_CHNDAT_CHDAT_FIELD); + result = GET_FIELD(CHNDAT(config->base, channel), + NPCX_CHNDAT_CHDAT_FIELD); /* * Save ADC result and adc_npcx_validate_buffer_size() * already ensures that the buffer has enough space for @@ -101,11 +153,45 @@ static void adc_npcx_isr(const struct device *dev) } data->channels &= ~BIT(channel); } + /* Disable End of cyclic conversion interruption */ + inst->ADCCNF &= ~BIT(NPCX_ADCCNF_INTECCEN); - /* Turn off ADC and inform sampling is done */ - inst->ADCCNF &= ~(BIT(NPCX_ADCCNF_ADCEN)); + if (IS_ENABLED(CONFIG_ADC_CMP_NPCX) && + t_data->active_thresholds) { + /* Set repetitive channels back */ + inst->ADCCS = t_data->repetitive_channels; + /* Start conversion */ + inst->ADCCNF |= BIT(NPCX_ADCCNF_START); + } else { + /* Disable all channels */ + inst->ADCCS = 0; + /* Turn off ADC */ + inst->ADCCNF &= ~(BIT(NPCX_ADCCNF_ADCEN)); + } + /* Inform sampling is done */ adc_context_on_sampling_done(&data->ctx, data->adc_dev); } + + if (!(IS_ENABLED(CONFIG_ADC_CMP_NPCX) && t_data->active_thresholds)) { + return; + } + uint16_t thrcts; + + for (uint8_t i = 0; i < NPCX_ADC_THRESHOLD_COUNT; i++) { + if (IS_BIT_SET(inst->THRCTS, i) && IS_BIT_SET(inst->THRCTS, + (NPCX_THRCTS_THR1_IEN + i))) { + /* Avoid clearing other threshold status */ + thrcts = inst->THRCTS & + ~GENMASK(NPCX_ADC_THRESHOLD_COUNT - 1, 0); + /* Clear threshold status */ + thrcts |= BIT(i); + inst->THRCTS = thrcts; + /* Notify work thread */ + if (t_data->control[i].work) { + k_work_submit(t_data->control[i].work); + } + } + } } /* @@ -145,8 +231,14 @@ static void adc_npcx_start_scan(const struct device *dev) /* Turn on ADC first */ inst->ADCCNF |= BIT(NPCX_ADCCNF_ADCEN); + /* Stop conversion for scan conversion mode */ + inst->ADCCNF |= BIT(NPCX_ADCCNF_STOP); + + /* Clear end of cyclic conversion event status flag */ + inst->ADCSTS |= BIT(NPCX_ADCSTS_EOCCEV); + /* Update selected channels in scan mode by channels mask */ - inst->ADCCS = data->channels; + inst->ADCCS |= data->channels; /* Select 'Scan' Conversion mode. */ SET_FIELD(inst->ADCCNF, NPCX_ADCCNF_ADCMD_FIELD, @@ -291,6 +383,277 @@ static int adc_npcx_read_async(const struct device *dev, } #endif /* CONFIG_ADC_ASYNC */ +static void adc_npcx_set_repetitive(const struct device *dev, int chnsel, + uint8_t enable) +{ + struct adc_reg *const inst = HAL_INSTANCE(dev); + struct adc_npcx_data *const data = dev->data; + struct adc_npcx_threshold_data *const t_data = data->threshold_data; + + /* Stop ADC conversion */ + inst->ADCCNF |= BIT(NPCX_ADCCNF_STOP); + + if (enable) { + /* Turn on ADC */ + inst->ADCCNF |= BIT(NPCX_ADCCNF_ADCEN); + /* Set ADC conversion code to SW conversion mode */ + SET_FIELD(inst->ADCCNF, NPCX_ADCCNF_ADCMD_FIELD, + NPCX_ADC_SCAN_CONVERSION_MODE); + /* Update number of channel to be converted */ + inst->ADCCS |= BIT(chnsel); + /* Set conversion type to repetitive (runs continuously) */ + inst->ADCCNF |= BIT(NPCX_ADCCNF_ADCRPTC); + + t_data->repetitive_channels |= BIT(chnsel); + /* Start conversion */ + inst->ADCCNF |= BIT(NPCX_ADCCNF_START); + } else { + inst->ADCCS &= ~BIT(chnsel); + + t_data->repetitive_channels &= ~BIT(chnsel); + if (!t_data->repetitive_channels) { + /* No thesholdd active left, disable repetitive mode */ + inst->ADCCNF &= ~BIT(NPCX_ADCCNF_ADCRPTC); + /* Turn off ADC */ + inst->ADCCNF &= ~BIT(NPCX_ADCCNF_ADCEN); + } else { + /* Start conversion again */ + inst->ADCCNF |= BIT(NPCX_ADCCNF_START); + } + } +} + +int adc_npcx_threshold_ctrl_set_param(const struct device *dev, + const uint8_t th_sel, + const struct adc_npcx_threshold_param + *param) +{ + struct adc_npcx_data *const data = dev->data; + struct adc_npcx_threshold_data *const t_data = data->threshold_data; + struct adc_npcx_threshold_control *const t_ctrl = + &t_data->control[th_sel]; + int ret = 0; + + if (!IS_ENABLED(CONFIG_ADC_CMP_NPCX)) { + return -EOPNOTSUPP; + } + + if (!param || th_sel >= NPCX_ADC_THRESHOLD_COUNT) { + return -EINVAL; + } + + adc_context_lock(&data->ctx, false, NULL); + switch (param->type) { + case ADC_NPCX_THRESHOLD_PARAM_CHNSEL: + if (param->val >= NPCX_ADC_CH_COUNT) { + ret = -EINVAL; + break; + } + t_ctrl->chnsel = (uint8_t)param->val; + break; + + case ADC_NPCX_THRESHOLD_PARAM_L_H: + t_ctrl->l_h = !!param->val; + break; + + case ADC_NPCX_THRESHOLD_PARAM_THVAL: + if (param->val == 0 || param->val >= ADC_NPCX_THRVAL_MAX) { + ret = -EINVAL; + break; + } + t_ctrl->thrval = (uint16_t)param->val; + break; + + case ADC_NPCX_THRESHOLD_PARAM_WORK: + if (param->val == 0) { + ret = -EINVAL; + break; + } + t_ctrl->work = (struct k_work *)param->val; + break; + default: + ret = -EINVAL; + } + adc_context_release(&data->ctx, 0); + return ret; +} + +static int adc_npcx_threshold_ctrl_setup(const struct device *dev, + const uint8_t th_sel) +{ + struct adc_npcx_data *const data = dev->data; + struct adc_npcx_threshold_data *const t_data = data->threshold_data; + const struct adc_npcx_config *config = dev->config; + struct adc_npcx_threshold_control *const t_ctrl = + &t_data->control[th_sel]; + + if (th_sel >= NPCX_ADC_THRESHOLD_COUNT) { + return -EINVAL; + } + + adc_context_lock(&data->ctx, false, NULL); + + if (t_data->active_thresholds & BIT(th_sel)) { + /* Unable to setup threshold parameters while active */ + adc_context_release(&data->ctx, 0); + LOG_ERR("Threshold selected (%d) is active!", th_sel); + return -EBUSY; + } + + if (t_ctrl->chnsel >= NPCX_ADC_CH_COUNT || + t_ctrl->thrval >= NPCX_ADC_VREF_VOL || + t_ctrl->thrval == 0 || t_ctrl->work == 0) { + adc_context_release(&data->ctx, 0); + LOG_ERR("Threshold selected (%d) is not configured!", th_sel); + return -EINVAL; + } + + SET_FIELD(THRCTL(config->base, (th_sel + 1)), + NPCX_THRCTL_CHNSEL, t_ctrl->chnsel); + + if (t_ctrl->l_h) { + THRCTL(config->base, (th_sel + 1)) |= BIT(NPCX_THRCTL_L_H); + } else { + THRCTL(config->base, (th_sel + 1)) &= ~BIT(NPCX_THRCTL_L_H); + } + /* Set the threshold value. */ + SET_FIELD(THRCTL(config->base, (th_sel + 1)), NPCX_THRCTL_THRVAL, + t_ctrl->thrval); + + adc_context_release(&data->ctx, 0); + return 0; +} + +static int adc_npcx_threshold_enable_irq(const struct device *dev, + const uint8_t th_sel) +{ + struct adc_reg *const inst = HAL_INSTANCE(dev); + struct adc_npcx_data *const data = dev->data; + const struct adc_npcx_config *config = dev->config; + struct adc_npcx_threshold_data *const t_data = data->threshold_data; + struct adc_npcx_threshold_control *const t_ctrl = + &t_data->control[th_sel]; + uint16_t thrcts; + + if (th_sel >= NPCX_ADC_THRESHOLD_COUNT) { + LOG_ERR("Invalid ADC threshold selection! (%d)", th_sel); + return -EINVAL; + } + + adc_context_lock(&data->ctx, false, NULL); + if (t_ctrl->chnsel >= NPCX_ADC_CH_COUNT || + t_ctrl->thrval >= NPCX_ADC_VREF_VOL || + t_ctrl->thrval == 0 || t_ctrl->work == 0) { + adc_context_release(&data->ctx, 0); + LOG_ERR("Threshold selected (%d) is not configured!", th_sel); + return -EINVAL; + } + + /* Record new active threshold */ + t_data->active_thresholds |= BIT(th_sel); + + /* avoid clearing other threshold status */ + thrcts = inst->THRCTS & ~GENMASK(NPCX_ADC_THRESHOLD_COUNT - 1, 0); + + /* Enable threshold detection */ + THRCTL(config->base, (th_sel + 1)) |= BIT(NPCX_THRCTL_THEN); + + /* clear threshold status */ + thrcts |= BIT(th_sel); + + /* set enable threshold status */ + thrcts |= BIT(NPCX_THRCTS_THR1_IEN + th_sel); + + inst->THRCTS = thrcts; + + adc_npcx_set_repetitive(dev, t_data->control[th_sel].chnsel, true); + + adc_context_release(&data->ctx, 0); + return 0; +} + +int adc_npcx_threshold_disable_irq(const struct device *dev, + const uint8_t th_sel) +{ + struct adc_reg *const inst = HAL_INSTANCE(dev); + const struct adc_npcx_config *config = dev->config; + struct adc_npcx_data *const data = dev->data; + struct adc_npcx_threshold_data *const t_data = data->threshold_data; + uint16_t thrcts; + + if (!IS_ENABLED(CONFIG_ADC_CMP_NPCX)) { + return -EOPNOTSUPP; + } + + if (th_sel >= NPCX_ADC_THRESHOLD_COUNT) { + LOG_ERR("Invalid ADC threshold selection! (%d)", th_sel); + return -EINVAL; + } + + adc_context_lock(&data->ctx, false, NULL); + if (!(t_data->active_thresholds & BIT(th_sel))) { + adc_context_release(&data->ctx, 0); + LOG_ERR("Threshold selection (%d) is not enabled", th_sel); + return -ENODEV; + } + /* avoid clearing other threshold status */ + thrcts = inst->THRCTS & ~GENMASK(NPCX_ADC_THRESHOLD_COUNT - 1, 0); + + /* set enable threshold status */ + thrcts &= ~BIT(NPCX_THRCTS_THR1_IEN + th_sel); + inst->THRCTS = thrcts; + + /* Disable threshold detection */ + THRCTL(config->base, (th_sel + 1)) &= ~BIT(NPCX_THRCTL_THEN); + + /* Update active threshold */ + t_data->active_thresholds &= ~BIT(th_sel); + + adc_npcx_set_repetitive(dev, t_data->control[th_sel].chnsel, false); + + adc_context_release(&data->ctx, 0); + + return 0; +} + +int adc_npcx_threshold_ctrl_enable(const struct device *dev, uint8_t th_sel, + const bool enable) +{ + int ret; + + if (!IS_ENABLED(CONFIG_ADC_CMP_NPCX)) { + return -EOPNOTSUPP; + } + + /* Enable/Disable threshold IRQ */ + if (enable) { + /* Set control threshold registers */ + ret = adc_npcx_threshold_ctrl_setup(dev, th_sel); + if (ret) { + return ret; + } + ret = adc_npcx_threshold_enable_irq(dev, th_sel); + } else { + ret = adc_npcx_threshold_disable_irq(dev, th_sel); + } + return ret; +} + +int adc_npcx_threshold_mv_to_thrval(uint32_t val_mv, uint32_t *thrval) +{ + if (!IS_ENABLED(CONFIG_ADC_CMP_NPCX)) { + return -EOPNOTSUPP; + } + + if (val_mv >= NPCX_ADC_VREF_VOL) { + return -EINVAL; + } + + *thrval = (val_mv << ADC_NPCX_THRVAL_RESOLUTION) / + NPCX_ADC_VREF_VOL; + return 0; +} + /* ADC driver registration */ static const struct adc_driver_api adc_npcx_driver_api = { .channel_setup = adc_npcx_channel_setup, @@ -311,6 +674,8 @@ static const struct adc_npcx_config adc_npcx_cfg_0 = { .alts_list = adc_alts, }; +static struct adc_npcx_threshold_data threshold_data_0; + static struct adc_npcx_data adc_npcx_data_0 = { ADC_CONTEXT_INIT_TIMER(adc_npcx_data_0, ctx), ADC_CONTEXT_INIT_LOCK(adc_npcx_data_0, ctx), @@ -366,6 +731,10 @@ static int adc_npcx_init(const struct device *dev) inst->GENDLY = ADC_REGULAR_GENDLY_VAL; inst->MEAST = ADC_REGULAR_MEAST_VAL; + if (IS_ENABLED(CONFIG_ADC_CMP_NPCX)) { + data->threshold_data = &threshold_data_0; + } + /* Configure ADC interrupt and enable it */ IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), adc_npcx_isr, DEVICE_DT_INST_GET(0), 0); diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 7cf3b1be733..d7990f0cad1 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -103,6 +103,7 @@ add_subdirectory_ifdef(CONFIG_TACH_XEC mchp_tach_xec) add_subdirectory_ifdef(CONFIG_ITDS wsen_itds) add_subdirectory_ifdef(CONFIG_MCUX_ACMP mcux_acmp) add_subdirectory_ifdef(CONFIG_TACH_NPCX nuvoton_tach_npcx) +add_subdirectory_ifdef(CONFIG_ADC_CMP_NPCX nuvoton_adc_cmp_npcx) add_subdirectory_ifdef(CONFIG_TACH_IT8XXX2 ite_tach_it8xxx2) add_subdirectory_ifdef(CONFIG_VCMP_IT8XXX2 ite_vcmp_it8xxx2) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 8081a994430..ee05ce7ffce 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -242,6 +242,8 @@ source "drivers/sensor/mcux_acmp/Kconfig" source "drivers/sensor/nuvoton_tach_npcx/Kconfig" +source "drivers/sensor/nuvoton_adc_cmp_npcx/Kconfig" + source "drivers/sensor/ite_tach_it8xxx2/Kconfig" source "drivers/sensor/ite_vcmp_it8xxx2/Kconfig" diff --git a/drivers/sensor/nuvoton_adc_cmp_npcx/CMakeLists.txt b/drivers/sensor/nuvoton_adc_cmp_npcx/CMakeLists.txt new file mode 100644 index 00000000000..25d41ec005f --- /dev/null +++ b/drivers/sensor/nuvoton_adc_cmp_npcx/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(adc_cmp_npcx.c) diff --git a/drivers/sensor/nuvoton_adc_cmp_npcx/Kconfig b/drivers/sensor/nuvoton_adc_cmp_npcx/Kconfig new file mode 100644 index 00000000000..1e9643864c3 --- /dev/null +++ b/drivers/sensor/nuvoton_adc_cmp_npcx/Kconfig @@ -0,0 +1,14 @@ +# ADC CMP NPCX driver configuration options + +# Copyright (c) 2022 Intel Corporation. +# SPDX-License-Identifier: Apache-2.0 + +if ADC_NPCX + +config ADC_CMP_NPCX + bool "Nuvoton NPCX ADC threshold detection interruption" + help + This option enables threshold interruption using sensor + trigger API. + +endif # ADC_NPCX diff --git a/drivers/sensor/nuvoton_adc_cmp_npcx/adc_cmp_npcx.c b/drivers/sensor/nuvoton_adc_cmp_npcx/adc_cmp_npcx.c new file mode 100644 index 00000000000..34722170836 --- /dev/null +++ b/drivers/sensor/nuvoton_adc_cmp_npcx/adc_cmp_npcx.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(adc_cmp_npcx, CONFIG_SENSOR_LOG_LEVEL); + +struct adc_cmp_npcx_data { + /* 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; + /* ADC NPCX driver reference */ + const struct device *dev; +}; + +struct adc_cmp_npcx_config { + /* + * Pointer of ADC device that will be performing measurement, this + * must be provied by device tree. + */ + const struct device *adc; + /* + * ADC channel that will be used to measure signal, this must be + * provided by device tree. + */ + uint8_t chnsel; + /* Threshold selection number assigned during initialization */ + uint8_t th_sel; + /* Threshold assert value in millivolts */ + uint32_t thr_mv; + /* + * Condition to be met between measured signal and threshold assert + * value that will trigger event + */ + enum adc_cmp_npcx_comparison comparison; +}; + +#define DT_DRV_COMPAT nuvoton_adc_cmp + +#define ADC_CMP_NPCX_UNDEFINED (-1) +static void adc_cmp_npcx_trigger_work_handler(struct k_work *item) +{ + struct adc_cmp_npcx_data *data = + CONTAINER_OF(item, struct adc_cmp_npcx_data, work); + struct sensor_trigger trigger = { + .type = SENSOR_TRIG_THRESHOLD, + .chan = SENSOR_CHAN_VOLTAGE + }; + + if (data->handler) { + data->handler(data->dev, &trigger); + } +} + +static int adc_cmp_npcx_init(const struct device *dev) +{ + const struct adc_cmp_npcx_config *const config = dev->config; + struct adc_cmp_npcx_data *data = dev->data; + struct adc_npcx_threshold_param param; + int ret; + + LOG_DBG("Initialize ADC CMP threshold selection (%d)", config->th_sel); + /* Data must keep device reference for worker handler*/ + data->dev = dev; + + /* Set ADC channel selection */ + param.type = ADC_NPCX_THRESHOLD_PARAM_CHNSEL; + param.val = (uint32_t)config->chnsel; + ret = adc_npcx_threshold_ctrl_set_param(config->adc, config->th_sel, + ¶m); + if (ret) { + goto init_error; + } + + /* Init and set Worker queue to enable notifications */ + k_work_init(&data->work, adc_cmp_npcx_trigger_work_handler); + param.type = ADC_NPCX_THRESHOLD_PARAM_WORK; + param.val = (uint32_t)&data->work; + ret = adc_npcx_threshold_ctrl_set_param(config->adc, config->th_sel, + ¶m); + if (ret) { + goto init_error; + } + + /* Set threshold value if set on device tree */ + if (config->thr_mv != ADC_CMP_NPCX_UNDEFINED) { + param.type = ADC_NPCX_THRESHOLD_PARAM_THVAL; + /* Convert from millivolts to ADC raw register value */ + ret = adc_npcx_threshold_mv_to_thrval(config->thr_mv, + ¶m.val); + if (ret) { + goto init_error; + } + + ret = adc_npcx_threshold_ctrl_set_param(config->adc, + config->th_sel, ¶m); + if (ret) { + goto init_error; + } + } + + /* Set threshold comparison if set on device tree */ + if (config->comparison == ADC_CMP_NPCX_GREATER || + config->comparison == ADC_CMP_NPCX_LESS_OR_EQUAL) { + param.type = ADC_NPCX_THRESHOLD_PARAM_L_H; + param.val = + config->comparison == ADC_CMP_NPCX_GREATER ? + ADC_NPCX_THRESHOLD_PARAM_L_H_HIGHER : + ADC_NPCX_THRESHOLD_PARAM_L_H_LOWER; + ret = adc_npcx_threshold_ctrl_set_param(config->adc, + config->th_sel, ¶m); + } + +init_error: + if (ret) { + LOG_ERR("Error setting parameter %d - value %d", + (uint32_t)param.type, param.val); + } + + return ret; +} + +static int adc_cmp_npcx_set_threshold(const struct device *dev, bool is_upper, + bool is_mv, uint32_t value) +{ + const struct adc_cmp_npcx_config *const config = dev->config; + struct adc_npcx_threshold_param param; + int ret; + + param.type = ADC_NPCX_THRESHOLD_PARAM_THVAL; + if (is_mv) { + ret = adc_npcx_threshold_mv_to_thrval(value, ¶m.val); + if (ret) { + return ret; + } + } + + ret = adc_npcx_threshold_ctrl_set_param(config->adc, + config->th_sel, ¶m); + if (ret) { + return ret; + } + + param.type = ADC_NPCX_THRESHOLD_PARAM_L_H; + param.val = is_upper ? ADC_NPCX_THRESHOLD_PARAM_L_H_HIGHER : + ADC_NPCX_THRESHOLD_PARAM_L_H_LOWER; + + ret = adc_npcx_threshold_ctrl_set_param(config->adc, + config->th_sel, ¶m); + + return ret; +} + +static int adc_cmp_npcx_attr_set(const struct device *dev, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + const struct adc_cmp_npcx_config *const config = dev->config; + int ret; + + if (chan != SENSOR_CHAN_VOLTAGE) { + return -ENOTSUP; + } + + switch ((uint16_t)attr) { + case SENSOR_ATTR_LOWER_THRESH: + __fallthrough; + case SENSOR_ATTR_UPPER_THRESH: + __fallthrough; + case SENSOR_ATTR_LOWER_VOLTAGE_THRESH: + __fallthrough; + case SENSOR_ATTR_UPPER_VOLTAGE_THRESH: + ret = adc_cmp_npcx_set_threshold(dev, + /* Is upper? */ + attr == SENSOR_ATTR_UPPER_THRESH || + (uint16_t)attr == SENSOR_ATTR_UPPER_VOLTAGE_THRESH, + /* Is mV? */ + (uint16_t)attr == SENSOR_ATTR_LOWER_VOLTAGE_THRESH || + (uint16_t)attr == SENSOR_ATTR_UPPER_VOLTAGE_THRESH, + val->val1); + break; + case SENSOR_ATTR_ALERT: + ret = adc_npcx_threshold_ctrl_enable(config->adc, + config->th_sel, !!val->val1); + break; + default: + ret = -ENOTSUP; + } + return ret; +} + +static int adc_cmp_npcx_attr_get(const struct device *dev, + enum sensor_channel chan, + enum sensor_attribute attr, + struct sensor_value *val) +{ + return -ENOTSUP; +} + +static int adc_cmp_npcx_trigger_set(const struct device *dev, + const struct sensor_trigger *trig, + sensor_trigger_handler_t handler) +{ + const struct adc_cmp_npcx_config *const config = dev->config; + struct adc_cmp_npcx_data *data = dev->data; + struct adc_npcx_threshold_param param; + + if (trig == NULL || handler == NULL) { + return -EINVAL; + } + + if (trig->type != SENSOR_TRIG_THRESHOLD || + trig->chan != SENSOR_CHAN_VOLTAGE) { + return -ENOTSUP; + } + + data->handler = handler; + + param.type = ADC_NPCX_THRESHOLD_PARAM_WORK; + param.val = (uint32_t)&data->work; + return adc_npcx_threshold_ctrl_set_param(config->adc, config->th_sel, + ¶m); +} + +static int adc_cmp_npcx_sample_fetch(const struct device *dev, + enum sensor_channel chan) +{ + return -ENOTSUP; +} + +static int adc_cmp_npcx_channel_get(const struct device *dev, + enum sensor_channel chan, + struct sensor_value *val) +{ + return -ENOTSUP; +} + +static const struct sensor_driver_api adc_cmp_npcx_api = { + .attr_set = adc_cmp_npcx_attr_set, + .attr_get = adc_cmp_npcx_attr_get, + .trigger_set = adc_cmp_npcx_trigger_set, + .sample_fetch = adc_cmp_npcx_sample_fetch, + .channel_get = adc_cmp_npcx_channel_get, +}; + +#define NPCX_ADC_CMP_INIT(inst) \ + static struct adc_cmp_npcx_data adc_cmp_npcx_data_##inst; \ + static const struct adc_cmp_npcx_config adc_cmp_npcx_config_##inst = {\ + .adc = DEVICE_DT_GET(DT_INST_IO_CHANNELS_CTLR(inst)), \ + .chnsel = DT_INST_IO_CHANNELS_INPUT(inst), \ + .th_sel = inst, \ + .thr_mv = DT_INST_PROP_OR(inst, threshold_mv, \ + ADC_CMP_NPCX_UNDEFINED), \ + .comparison = DT_INST_STRING_TOKEN_OR(inst, \ + comparison, ADC_CMP_NPCX_UNDEFINED) \ + }; \ + DEVICE_DT_INST_DEFINE(inst, adc_cmp_npcx_init, NULL, \ + &adc_cmp_npcx_data_##inst, \ + &adc_cmp_npcx_config_##inst, PRE_KERNEL_2, \ + CONFIG_SENSOR_INIT_PRIORITY, \ + &adc_cmp_npcx_api); +DT_INST_FOREACH_STATUS_OKAY(NPCX_ADC_CMP_INIT) diff --git a/dts/arm/nuvoton/npcx7.dtsi b/dts/arm/nuvoton/npcx7.dtsi index 736d2b1d3c5..0a48e7040cc 100644 --- a/dts/arm/nuvoton/npcx7.dtsi +++ b/dts/arm/nuvoton/npcx7.dtsi @@ -197,6 +197,7 @@ &altf_adc8_sl /* ADC8 - PINF1 */ &altf_adc9_sl>; /* ADC9 - PINF0 */ threshold-reg-offset = <0x14>; + threshold-count = <3>; }; }; diff --git a/dts/arm/nuvoton/npcx9.dtsi b/dts/arm/nuvoton/npcx9.dtsi index 59a5abe2dfa..49d7dac2e31 100644 --- a/dts/arm/nuvoton/npcx9.dtsi +++ b/dts/arm/nuvoton/npcx9.dtsi @@ -223,6 +223,7 @@ &altf_adc10_sl /* ADC10 - PINE0 */ &altf_adc11_sl>; /* ADC11 - PINC7 */ threshold-reg-offset = <0x60>; + threshold-count = <6>; }; }; diff --git a/dts/bindings/iio/adc/nuvoton,npcx-adc.yaml b/dts/bindings/iio/adc/nuvoton,npcx-adc.yaml index e9fc222b0c8..f7dd065649e 100644 --- a/dts/bindings/iio/adc/nuvoton,npcx-adc.yaml +++ b/dts/bindings/iio/adc/nuvoton,npcx-adc.yaml @@ -22,6 +22,10 @@ properties: type: int required: true description: the offset of threshold detector register address + threshold-count: + type: int + required: true + description: the number of threshold detectors adc supports io-channel-cells: - input diff --git a/dts/bindings/sensor/nuvoton,adc-cmp.yaml b/dts/bindings/sensor/nuvoton,adc-cmp.yaml new file mode 100644 index 00000000000..e0a10e78a95 --- /dev/null +++ b/dts/bindings/sensor/nuvoton,adc-cmp.yaml @@ -0,0 +1,30 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: | + This will perform signal comparision with threshold established. + +compatible: "nuvoton,adc-cmp" + +properties: + io-channels: + type: phandle-array + required: true + description: | + ADC channel that will perform measurement. + + threshold-mv: + type: int + required: false + description: | + Value in millivolts present on ADC data as threshold assert. + + comparison: + type: string + required: false + description: | + Determines the condition to be met between ADC data and + threshold assert value that will trigger comparator event. + enum: + - ADC_CMP_NPCX_GREATER + - ADC_CMP_NPCX_LESS_OR_EQUAL diff --git a/include/zephyr/drivers/adc/adc_npcx_threshold.h b/include/zephyr/drivers/adc/adc_npcx_threshold.h new file mode 100644 index 00000000000..310428374c0 --- /dev/null +++ b/include/zephyr/drivers/adc/adc_npcx_threshold.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ADC_NPCX_THRESHOLD_H_ +#define _ADC_NPCX_THRESHOLD_H_ + +#include + +enum adc_npcx_threshold_param_l_h { + ADC_NPCX_THRESHOLD_PARAM_L_H_HIGHER, + ADC_NPCX_THRESHOLD_PARAM_L_H_LOWER, +}; + +enum adc_npcx_threshold_param_type { + /* Selects ADC channel to be used for measurement */ + ADC_NPCX_THRESHOLD_PARAM_CHNSEL, + /* Sets relation between measured value and assetion threshold value.*/ + ADC_NPCX_THRESHOLD_PARAM_L_H, + /* Sets the threshol value to which measured data is compared. */ + ADC_NPCX_THRESHOLD_PARAM_THVAL, + /* Sets worker queue thread to be notified */ + ADC_NPCX_THRESHOLD_PARAM_WORK, + + ADC_NPCX_THRESHOLD_PARAM_MAX, +}; + +struct adc_npcx_threshold_param { + /* Threshold ocntrol parameter */ + enum adc_npcx_threshold_param_type type; + /* Parameter value */ + uint32_t val; +}; + +/** + * @brief Convert input value in millivolts to corresponding threshold register + * value. + * + * @note This function is available only if @kconfig{CONFIG_ADC_CMP_NPCX} + * is selected. + * + * @param val_mv Input value in millivolts to be converted. + * @param thrval Pointer of variable to hold the result of conversion. + * + * @returns 0 on success, negative result if input cannot be converted due to + * overflow. + */ +int adc_npcx_threshold_mv_to_thrval(uint32_t val_mv, uint32_t *thrval); + +/** + * @brief Set ADC threshold parameter. + * + * @note This function is available only if @kconfig{CONFIG_ADC_CMP_NPCX} + * is selected. + * + * @param dev Pointer to the device structure for the driver instance. + * @param th_sel Threshold selected. + * @param param Pointer of parameter structure. + * See struct adc_npcx_threshold_param for supported + * parameters. + * + * @returns 0 on success, negative error code otherwise. + */ +int adc_npcx_threshold_ctrl_set_param(const struct device *dev, + const uint8_t th_sel, + const struct adc_npcx_threshold_param + *param); + +/** + * @brief Enables/Disables ADC threshold interruption. + * + * @note This function is available only if @kconfig{CONFIG_ADC_CMP_NPCX} + * is selected. + * + * @param dev Pointer to the device structure for the driver instance. + * @param th_sel Threshold selected. + * @param enable Enable or disables threshold interruption. + * + * @returns 0 on success, negative error code otherwise. + * all parameters must be configure prior enabling threshold + * interruption, otherwhise error will be returned. + */ +int adc_npcx_threshold_ctrl_enable(const struct device *dev, uint8_t th_sel, + const bool enable); + +#endif /*_ADC_NPCX_THRESHOLD_H_ */ diff --git a/include/zephyr/drivers/sensor/adc_cmp_npcx.h b/include/zephyr/drivers/sensor/adc_cmp_npcx.h new file mode 100644 index 00000000000..fc66fd80a11 --- /dev/null +++ b/include/zephyr/drivers/sensor/adc_cmp_npcx.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ADC_CMP_NPCX_H_ +#define _ADC_CMP_NPCX_H_ + +enum adc_cmp_npcx_comparison { + ADC_CMP_NPCX_GREATER, + ADC_CMP_NPCX_LESS_OR_EQUAL, +}; + +enum adc_cmp_npcx_sensor_attribute { + SENSOR_ATTR_LOWER_VOLTAGE_THRESH = SENSOR_ATTR_PRIV_START, + SENSOR_ATTR_UPPER_VOLTAGE_THRESH, +}; + +#endif /* _ADC_CMP_NPCX_H_ */