From d37f8441043b064eabe5d924e93d103a91c40b44 Mon Sep 17 00:00:00 2001 From: Bjarki Arge Andreasen Date: Mon, 19 Aug 2024 11:53:09 +0200 Subject: [PATCH] drivers: comparator: add mcux acmp device driver Add mcux SDK based kinetis acmp device driver implementing the comparator device driver API. Signed-off-by: Bjarki Arge Andreasen --- drivers/comparator/CMakeLists.txt | 1 + drivers/comparator/Kconfig | 1 + drivers/comparator/Kconfig.mcux_acmp | 9 + drivers/comparator/comparator_mcux_acmp.c | 651 ++++++++++++++++++ dts/bindings/comparator/nxp,kinetis-acmp.yaml | 34 +- include/zephyr/drivers/comparator/mcux_acmp.h | 136 ++++ 6 files changed, 831 insertions(+), 1 deletion(-) create mode 100644 drivers/comparator/Kconfig.mcux_acmp create mode 100644 drivers/comparator/comparator_mcux_acmp.c create mode 100644 include/zephyr/drivers/comparator/mcux_acmp.h diff --git a/drivers/comparator/CMakeLists.txt b/drivers/comparator/CMakeLists.txt index d0dc726bf58..b5fe633a333 100644 --- a/drivers/comparator/CMakeLists.txt +++ b/drivers/comparator/CMakeLists.txt @@ -6,5 +6,6 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/comparator.h) zephyr_library() zephyr_library_sources_ifdef(CONFIG_USERSPACE comparator_handlers.c) +zephyr_library_sources_ifdef(CONFIG_COMPARATOR_MCUX_ACMP comparator_mcux_acmp.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_NRF_COMP comparator_nrf_comp.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_NRF_LPCOMP comparator_nrf_lpcomp.c) diff --git a/drivers/comparator/Kconfig b/drivers/comparator/Kconfig index 819936024b9..871ee5d9ed6 100644 --- a/drivers/comparator/Kconfig +++ b/drivers/comparator/Kconfig @@ -18,6 +18,7 @@ config COMPARATOR_INIT_PRIORITY help Comparator device driver initialization priority. +rsource "Kconfig.mcux_acmp" rsource "Kconfig.nrf_comp" rsource "Kconfig.nrf_lpcomp" diff --git a/drivers/comparator/Kconfig.mcux_acmp b/drivers/comparator/Kconfig.mcux_acmp new file mode 100644 index 00000000000..8109cb1cdd2 --- /dev/null +++ b/drivers/comparator/Kconfig.mcux_acmp @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config COMPARATOR_MCUX_ACMP + bool "NXP MCUX ACMP comparator driver" + default y + depends on DT_HAS_NXP_KINETIS_ACMP_ENABLED + select PINCTRL + select MCUX_ACMP diff --git a/drivers/comparator/comparator_mcux_acmp.c b/drivers/comparator/comparator_mcux_acmp.c new file mode 100644 index 00000000000..4d76f105e34 --- /dev/null +++ b/drivers/comparator/comparator_mcux_acmp.c @@ -0,0 +1,651 @@ +/* + * Copyright (c) 2020 Vestas Wind Systems A/S + * Copyright (c) 2022 NXP + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(nxp_kinetis_acmp, CONFIG_COMPARATOR_LOG_LEVEL); + +#define DT_DRV_COMPAT nxp_kinetis_acmp + +/* + * DAC is a register defined in the MCUX HAL. We don't need it here and it conflicts + * with the COMP_MCUX_ACMP_PORT_INPUT_DAC definition so undef it here. + */ +#ifdef DAC +#undef DAC +#endif + +#if defined(FSL_FEATURE_ACMP_HAS_C0_OFFSET_BIT) && (FSL_FEATURE_ACMP_HAS_C0_OFFSET_BIT == 1U) +#define COMP_MCUX_ACMP_HAS_OFFSET 1 +#else +#define COMP_MCUX_ACMP_HAS_OFFSET 0 +#endif + +#if defined(FSL_FEATURE_ACMP_HAS_C0_HYSTCTR_BIT) && (FSL_FEATURE_ACMP_HAS_C0_HYSTCTR_BIT == 1U) +#define COMP_MCUX_ACMP_HAS_HYSTERESIS 1 +#else +#define COMP_MCUX_ACMP_HAS_HYSTERESIS 0 +#endif + +#if defined(FSL_FEATURE_ACMP_HAS_C1_INPSEL_BIT) && (FSL_FEATURE_ACMP_HAS_C1_INPSEL_BIT == 1U) +#define COMP_MCUX_ACMP_HAS_INPSEL 1 +#else +#define COMP_MCUX_ACMP_HAS_INPSEL 0 +#endif + +#if defined(FSL_FEATURE_ACMP_HAS_C1_INNSEL_BIT) && (FSL_FEATURE_ACMP_HAS_C1_INNSEL_BIT == 1U) +#define COMP_MCUX_ACMP_HAS_INNSEL 1 +#else +#define COMP_MCUX_ACMP_HAS_INNSEL 0 +#endif + +#if defined(FSL_FEATURE_ACMP_HAS_C1_DACOE_BIT) && (FSL_FEATURE_ACMP_HAS_C1_DACOE_BIT == 1U) +#define COMP_MCUX_ACMP_HAS_DAC_OUT_ENABLE 1 +#else +#define COMP_MCUX_ACMP_HAS_DAC_OUT_ENABLE 0 +#endif + +#if defined(FSL_FEATURE_ACMP_HAS_C1_DMODE_BIT) && (FSL_FEATURE_ACMP_HAS_C1_DMODE_BIT == 1U) +#define COMP_MCUX_ACMP_HAS_DAC_WORK_MODE 1 +#else +#define COMP_MCUX_ACMP_HAS_DAC_WORK_MODE 0 +#endif + +#if defined(FSL_FEATURE_ACMP_HAS_C3_REG) && (FSL_FEATURE_ACMP_HAS_C3_REG != 0U) +#define COMP_MCUX_ACMP_HAS_DISCRETE_MODE 1 +#else +#define COMP_MCUX_ACMP_HAS_DISCRETE_MODE 0 +#endif + +#if !(defined(FSL_FEATURE_ACMP_HAS_NO_WINDOW_MODE) && (FSL_FEATURE_ACMP_HAS_NO_WINDOW_MODE == 1U)) +#define COMP_MCUX_ACMP_HAS_WINDOW_MODE 1 +#else +#define COMP_MCUX_ACMP_HAS_WINDOW_MODE 0 +#endif + +#define MCUX_ACMP_ENUM(name, value) \ + _CONCAT_4(COMP_MCUX_ACMP_, name, _, value) + +#define MCUX_ACMP_DT_INST_ENUM(inst, name, prop) \ + MCUX_ACMP_ENUM(name, DT_INST_STRING_TOKEN(inst, prop)) + +#define MCUX_ACMP_DT_INST_ENUM_OR(inst, name, prop, or) \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prop), \ + (MCUX_ACMP_DT_INST_ENUM(inst, name, prop)), \ + (MCUX_ACMP_ENUM(name, or))) + +#define MCUX_ACMP_DT_INST_OFFSET_MODE(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, OFFSET_MODE, offset_mode, LEVEL0) + +#define MCUX_ACMP_DT_INST_HYST_MODE(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, HYSTERESIS_MODE, hysteresis_mode, LEVEL0) + +#define MCUX_ACMP_DT_INST_EN_HS_MODE(inst) \ + DT_INST_PROP(inst, enable_high_speed_mode) + +#define MCUX_ACMP_DT_INST_INV_OUT(inst) \ + DT_INST_PROP(inst, invert_output) + +#define MCUX_ACMP_DT_INST_USE_UNFILTERED_OUT(inst) \ + DT_INST_PROP(inst, use_unfiltered_output) + +#define MCUX_ACMP_DT_INST_EN_PIN_OUT(inst) \ + DT_INST_PROP(inst, enable_pin_out) + +#define MCUX_ACMP_DT_INST_MODE_CONFIG_INIT(inst) \ + { \ + .offset_mode = MCUX_ACMP_DT_INST_OFFSET_MODE(inst), \ + .hysteresis_mode = MCUX_ACMP_DT_INST_HYST_MODE(inst), \ + .enable_high_speed_mode = MCUX_ACMP_DT_INST_EN_HS_MODE(inst), \ + .invert_output = MCUX_ACMP_DT_INST_INV_OUT(inst), \ + .use_unfiltered_output = MCUX_ACMP_DT_INST_USE_UNFILTERED_OUT(inst), \ + .enable_pin_output = MCUX_ACMP_DT_INST_EN_PIN_OUT(inst), \ + } + +#define MCUX_ACMP_DT_INST_P_MUX_IN(inst) \ + MCUX_ACMP_DT_INST_ENUM(inst, MUX_INPUT, positive_mux_input) + +#define MCUX_ACMP_DT_INST_N_MUX_IN(inst) \ + MCUX_ACMP_DT_INST_ENUM(inst, MUX_INPUT, negative_mux_input) + +#define MCUX_ACMP_DT_INST_P_PORT_IN(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, PORT_INPUT, positive_port_input, MUX) + +#define MCUX_ACMP_DT_INST_N_PORT_IN(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, PORT_INPUT, negative_port_input, MUX) + +#define MCUX_ACMP_DT_INST_INPUT_CONFIG_INIT(inst) \ + { \ + .positive_mux_input = MCUX_ACMP_DT_INST_P_MUX_IN(inst), \ + .negative_mux_input = MCUX_ACMP_DT_INST_N_MUX_IN(inst), \ + .positive_port_input = MCUX_ACMP_DT_INST_P_PORT_IN(inst), \ + .negative_port_input = MCUX_ACMP_DT_INST_N_PORT_IN(inst), \ + } + +#define MCUX_ACMP_DT_INST_FILTER_EN_SAMPLE(inst) \ + DT_INST_PROP(inst, filter_enable_sample) + +#define MCUX_ACMP_DT_INST_FILTER_COUNT(inst) \ + DT_INST_PROP_OR(inst, filter_count, 0) + +#define MCUX_ACMP_DT_INST_FILTER_PERIOD(inst) \ + DT_INST_PROP_OR(inst, filter_period, 0) + +#define MCUX_ACMP_DT_INST_FILTER_CONFIG_INIT(inst) \ + { \ + .enable_sample = MCUX_ACMP_DT_INST_FILTER_EN_SAMPLE(inst), \ + .filter_count = MCUX_ACMP_DT_INST_FILTER_COUNT(inst), \ + .filter_period = MCUX_ACMP_DT_INST_FILTER_PERIOD(inst), \ + } + +#define MCUX_ACMP_DT_INST_DAC_VREF_SOURCE(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, DAC_VREF_SOURCE, dac_vref_source, VIN1) + +#define MCUX_ACMP_DT_INST_DAC_VALUE(inst) \ + DT_INST_PROP_OR(inst, dac_value, 0) + +#define MCUX_ACMP_DT_INST_DAC_EN(inst) \ + DT_INST_PROP(inst, dac_enable) + +#define MCUX_ACMP_DT_INST_DAC_EN_HS(inst) \ + DT_INST_PROP(inst, dac_enable_high_speed) + +#define MCUX_ACMP_DT_INST_DAC_CONFIG_INIT(inst) \ + { \ + .vref_source = MCUX_ACMP_DT_INST_DAC_VREF_SOURCE(inst), \ + .value = MCUX_ACMP_DT_INST_DAC_VALUE(inst), \ + .enable_output = MCUX_ACMP_DT_INST_DAC_EN(inst), \ + .enable_high_speed_mode = MCUX_ACMP_DT_INST_DAC_EN_HS(inst), \ + } + +#define MCUX_ACMP_DT_INST_DM_EN_P_CH(inst) \ + DT_INST_PROP(inst, discrete_mode_enable_positive_channel) + +#define MCUX_ACMP_DT_INST_DM_EN_N_CH(inst) \ + DT_INST_PROP(inst, discrete_mode_enable_negative_channel) + +#define MCUX_ACMP_DT_INST_DM_EN_RES_DIV(inst) \ + DT_INST_PROP(inst, discrete_mode_enable_resistor_divider) + +#define MCUX_ACMP_DT_INST_DM_CLOCK_SOURCE(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, DM_CLOCK, discrete_mode_clock_source, SLOW) + +#define MCUX_ACMP_DT_INST_DM_SAMPLE_TIME(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, DM_SAMPLE_TIME, discrete_mode_sample_time, T1) + +#define MCUX_ACMP_DT_INST_DM_PHASE1_TIME(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, DM_PHASE_TIME, discrete_mode_phase1_time, ALT0) + +#define MCUX_ACMP_DT_INST_DM_PHASE2_TIME(inst) \ + MCUX_ACMP_DT_INST_ENUM_OR(inst, DM_PHASE_TIME, discrete_mode_phase2_time, ALT0) + +#define MCUX_ACMP_DT_INST_DM_CONFIG_INIT(inst) \ + { \ + .enable_positive_channel = MCUX_ACMP_DT_INST_DM_EN_P_CH(inst), \ + .enable_negative_channel = MCUX_ACMP_DT_INST_DM_EN_N_CH(inst), \ + .enable_resistor_divider = MCUX_ACMP_DT_INST_DM_EN_RES_DIV(inst), \ + .clock_source = MCUX_ACMP_DT_INST_DM_CLOCK_SOURCE(inst), \ + .sample_time = MCUX_ACMP_DT_INST_DM_SAMPLE_TIME(inst), \ + .phase1_time = MCUX_ACMP_DT_INST_DM_PHASE1_TIME(inst), \ + .phase2_time = MCUX_ACMP_DT_INST_DM_PHASE2_TIME(inst), \ + } + +#define MCUX_ACMP_DT_INST_EN_WINDOW_MODE(inst) \ + DT_INST_PROP(inst, enable_window_mode) + +struct mcux_acmp_config { + CMP_Type *base; + const struct pinctrl_dev_config *pincfg; + void (*irq_init)(void); + const struct comp_mcux_acmp_mode_config mode_config; + const struct comp_mcux_acmp_input_config input_config; + const struct comp_mcux_acmp_filter_config filter_config; + const struct comp_mcux_acmp_dac_config dac_config; +#if COMP_MCUX_ACMP_HAS_DISCRETE_MODE + const struct comp_mcux_acmp_dm_config dm_config; +#endif +#if COMP_MCUX_ACMP_HAS_WINDOW_MODE + bool enable_window_mode; +#endif +}; + +#if MCUX_ACMP_HAS_OFFSET +BUILD_ASSERT((int)kACMP_OffsetLevel0 == (int)COMP_MCUX_ACMP_OFFSET_MODE_LEVEL0); +BUILD_ASSERT((int)kACMP_OffsetLevel1 == (int)COMP_MCUX_ACMP_OFFSET_MODE_LEVEL1); +#endif + +#if COMP_MCUX_ACMP_HAS_HYSTERESIS +BUILD_ASSERT((int)kACMP_HysteresisLevel0 == (int)COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL0); +BUILD_ASSERT((int)kACMP_HysteresisLevel1 == (int)COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL1); +BUILD_ASSERT((int)kACMP_HysteresisLevel2 == (int)COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL2); +BUILD_ASSERT((int)kACMP_HysteresisLevel3 == (int)COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL3); +#endif + +BUILD_ASSERT((int)kACMP_VrefSourceVin1 == (int)COMP_MCUX_ACMP_DAC_VREF_SOURCE_VIN1); +BUILD_ASSERT((int)kACMP_VrefSourceVin2 == (int)COMP_MCUX_ACMP_DAC_VREF_SOURCE_VIN2); + +#if MCUX_ACMP_HAS_INPSEL || MCUX_ACMP_HAS_INNSEL +BUILD_ASSERT((int)kACMP_PortInputFromDAC == (int)COMP_MCUX_ACMP_PORT_INPUT_DAC); +BUILD_ASSERT((int)kACMP_PortInputFromMux == (int)COMP_MCUX_ACMP_PORT_INPUT_MUX); +#endif + +#if COMP_MCUX_ACMP_HAS_DISCRETE_MODE +BUILD_ASSERT((int)kACMP_DiscreteClockSlow == (int)COMP_MCUX_ACMP_DM_CLOCK_SLOW); +BUILD_ASSERT((int)kACMP_DiscreteClockFast == (int)COMP_MCUX_ACMP_DM_CLOCK_FAST); + +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs1T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T1); +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs2T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T2); +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs4T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T4); +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs8T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T8); +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs16T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T16); +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs32T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T32); +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs64T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T64); +BUILD_ASSERT((int)kACMP_DiscreteSampleTimeAs256T == (int)COMP_MCUX_ACMP_DM_SAMPLE_TIME_T256); + +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt0 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT0); +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt1 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT1); +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt2 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT2); +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt3 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT3); +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt4 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT4); +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt5 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT5); +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt6 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT6); +BUILD_ASSERT((int)kACMP_DiscretePhaseTimeAlt7 == (int)COMP_MCUX_ACMP_DM_PHASE_TIME_ALT7); +#endif + +struct mcux_acmp_data { + uint32_t interrupt_mask; + comparator_callback_t callback; + void *user_data; +}; + +#if CONFIG_PM_DEVICE +static bool mcux_acmp_is_resumed(const struct device *dev) +{ + enum pm_device_state state; + + (void)pm_device_state_get(dev, &state); + return state == PM_DEVICE_STATE_ACTIVE; +} +#else +static bool mcux_acmp_is_resumed(const struct device *dev) +{ + ARG_UNUSED(dev); + return true; +} +#endif + +static int mcux_acmp_get_output(const struct device *dev) +{ + const struct mcux_acmp_config *config = dev->config; + uint32_t status; + + status = ACMP_GetStatusFlags(config->base); + return (status & kACMP_OutputAssertEventFlag) ? 1 : 0; +} + +static int mcux_acmp_set_trigger(const struct device *dev, + enum comparator_trigger trigger) +{ + const struct mcux_acmp_config *config = dev->config; + struct mcux_acmp_data *data = dev->data; + + ACMP_DisableInterrupts(config->base, UINT32_MAX); + + switch (trigger) { + case COMPARATOR_TRIGGER_NONE: + data->interrupt_mask = 0; + break; + + case COMPARATOR_TRIGGER_RISING_EDGE: + data->interrupt_mask = kACMP_OutputRisingInterruptEnable; + break; + + case COMPARATOR_TRIGGER_FALLING_EDGE: + data->interrupt_mask = kACMP_OutputFallingInterruptEnable; + break; + + case COMPARATOR_TRIGGER_BOTH_EDGES: + data->interrupt_mask = kACMP_OutputFallingInterruptEnable | + kACMP_OutputRisingInterruptEnable; + break; + } + + if (data->interrupt_mask && data->callback != NULL) { + ACMP_EnableInterrupts(config->base, data->interrupt_mask); + } + + return 0; +} + +static int mcux_acmp_set_trigger_callback(const struct device *dev, + comparator_callback_t callback, + void *user_data) +{ + const struct mcux_acmp_config *config = dev->config; + struct mcux_acmp_data *data = dev->data; + + ACMP_DisableInterrupts(config->base, UINT32_MAX); + + data->callback = callback; + data->user_data = user_data; + + if (data->callback == NULL) { + return 0; + } + + if (data->interrupt_mask) { + ACMP_EnableInterrupts(config->base, data->interrupt_mask); + } + + return 0; +} + +static int mcux_acmp_trigger_is_pending(const struct device *dev) +{ + const struct mcux_acmp_config *config = dev->config; + struct mcux_acmp_data *data = dev->data; + uint32_t status_flags; + + status_flags = ACMP_GetStatusFlags(config->base); + ACMP_ClearStatusFlags(config->base, UINT32_MAX); + + if ((data->interrupt_mask & kACMP_OutputRisingInterruptEnable) && + (status_flags & kACMP_OutputRisingEventFlag)) { + return 1; + } + + if ((data->interrupt_mask & kACMP_OutputFallingInterruptEnable) && + (status_flags & kACMP_OutputFallingEventFlag)) { + return 1; + } + + return 0; +} + +static const struct comparator_driver_api mcux_acmp_comp_api = { + .get_output = mcux_acmp_get_output, + .set_trigger = mcux_acmp_set_trigger, + .set_trigger_callback = mcux_acmp_set_trigger_callback, + .trigger_is_pending = mcux_acmp_trigger_is_pending, +}; + +static void comp_mcux_acmp_init_mode_config(const struct device *dev, + const struct comp_mcux_acmp_mode_config *config) +{ + const struct mcux_acmp_config *dev_config = dev->config; + acmp_config_t acmp_config; + +#if COMP_MCUX_ACMP_HAS_OFFSET + acmp_config.offsetMode = (acmp_offset_mode_t)config->offset_mode; +#endif + +#if COMP_MCUX_ACMP_HAS_HYSTERESIS + acmp_config.hysteresisMode = (acmp_hysteresis_mode_t)config->hysteresis_mode; +#endif + + acmp_config.enableHighSpeed = config->enable_high_speed_mode; + acmp_config.enableInvertOutput = config->invert_output; + acmp_config.useUnfilteredOutput = config->use_unfiltered_output; + acmp_config.enablePinOut = config->enable_pin_output; + + ACMP_Init(dev_config->base, &acmp_config); +} + +int comp_mcux_acmp_set_mode_config(const struct device *dev, + const struct comp_mcux_acmp_mode_config *config) +{ + const struct mcux_acmp_config *dev_config = dev->config; + + comp_mcux_acmp_init_mode_config(dev, config); + + if (mcux_acmp_is_resumed(dev)) { + ACMP_Enable(dev_config->base, true); + } + + return 0; +} + +int comp_mcux_acmp_set_input_config(const struct device *dev, + const struct comp_mcux_acmp_input_config *config) +{ + const struct mcux_acmp_config *dev_config = dev->config; + acmp_channel_config_t acmp_channel_config; + +#if COMP_MCUX_ACMP_HAS_INPSEL + acmp_channel_config.positivePortInput = (acmp_port_input_t)config->positive_port_input; +#endif + + acmp_channel_config.plusMuxInput = (uint32_t)config->positive_mux_input; + +#if COMP_MCUX_ACMP_HAS_INNSEL + acmp_channel_config.negativePortInput = (acmp_port_input_t)config->negative_port_input; +#endif + + acmp_channel_config.minusMuxInput = (uint32_t)config->negative_mux_input; + + ACMP_SetChannelConfig(dev_config->base, &acmp_channel_config); + return 0; +} + +int comp_mcux_acmp_set_filter_config(const struct device *dev, + const struct comp_mcux_acmp_filter_config *config) +{ + const struct mcux_acmp_config *dev_config = dev->config; + acmp_filter_config_t acmp_filter_config; + + if (config->enable_sample && config->filter_count == 0) { + return -EINVAL; + } + + if (config->filter_count > 7) { + return -EINVAL; + } + + acmp_filter_config.enableSample = config->enable_sample; + acmp_filter_config.filterCount = config->filter_count; + acmp_filter_config.filterPeriod = config->filter_period; + + ACMP_SetFilterConfig(dev_config->base, &acmp_filter_config); + return 0; +} + +int comp_mcux_acmp_set_dac_config(const struct device *dev, + const struct comp_mcux_acmp_dac_config *config) +{ + const struct mcux_acmp_config *dev_config = dev->config; + acmp_dac_config_t acmp_dac_config; + + acmp_dac_config.referenceVoltageSource = + (acmp_reference_voltage_source_t)config->vref_source; + + acmp_dac_config.DACValue = config->value; + +#if COMP_MCUX_ACMP_HAS_DAC_OUT_ENABLE + acmp_dac_config.enableOutput = config->enable_output; +#endif + +#if COMP_MCUX_ACMP_HAS_DAC_WORK_MODE + acmp_dac_config.workMode = config->enable_high_speed_mode + ? kACMP_DACWorkHighSpeedMode + : kACMP_DACWorkLowSpeedMode; +#endif + + ACMP_SetDACConfig(dev_config->base, &acmp_dac_config); + return 0; +} + +#if COMP_MCUX_ACMP_HAS_DISCRETE_MODE +int comp_mcux_acmp_set_dm_config(const struct device *dev, + const struct comp_mcux_acmp_dm_config *config) +{ + const struct mcux_acmp_config *dev_config = dev->config; + acmp_discrete_mode_config_t acmp_dm_config; + + acmp_dm_config.enablePositiveChannelDiscreteMode = config->enable_positive_channel; + acmp_dm_config.enableNegativeChannelDiscreteMode = config->enable_negative_channel; + acmp_dm_config.enableResistorDivider = config->enable_resistor_divider; + acmp_dm_config.clockSource = (acmp_discrete_clock_source_t)config->clock_source; + acmp_dm_config.sampleTime = (acmp_discrete_sample_time_t)config->sample_time; + acmp_dm_config.phase1Time = (acmp_discrete_phase_time_t)config->phase1_time; + acmp_dm_config.phase2Time = (acmp_discrete_phase_time_t)config->phase2_time; + + ACMP_SetDiscreteModeConfig(dev_config->base, &acmp_dm_config); + return 0; +} +#endif + +#if COMP_MCUX_ACMP_HAS_WINDOW_MODE +int comp_mcux_acmp_set_window_mode(const struct device *dev, bool enable) +{ + const struct mcux_acmp_config *config = dev->config; + + ACMP_EnableWindowMode(config->base, enable); + return 0; +} +#endif + +static int mcux_acmp_pm_callback(const struct device *dev, enum pm_device_action action) +{ + const struct mcux_acmp_config *config = dev->config; + + if (action == PM_DEVICE_ACTION_RESUME) { + ACMP_Enable(config->base, true); + } + +#if CONFIG_PM_DEVICE + if (action == PM_DEVICE_ACTION_SUSPEND) { + ACMP_Enable(config->base, false); + } +#endif + + return 0; +} + +static void mcux_acmp_irq_handler(const struct device *dev) +{ + const struct mcux_acmp_config *config = dev->config; + struct mcux_acmp_data *data = dev->data; + + ACMP_ClearStatusFlags(config->base, UINT32_MAX); + + if (data->callback == NULL) { + return; + } + + data->callback(dev, data->user_data); +} + +static int mcux_acmp_init(const struct device *dev) +{ + const struct mcux_acmp_config *config = dev->config; + int ret; + + ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); + if (ret) { + LOG_ERR("failed to set %s", "pincfg"); + return ret; + } + + comp_mcux_acmp_init_mode_config(dev, &config->mode_config); + + ret = comp_mcux_acmp_set_input_config(dev, &config->input_config); + if (ret) { + LOG_ERR("failed to set %s", "input config"); + return ret; + } + + ret = comp_mcux_acmp_set_filter_config(dev, &config->filter_config); + if (ret) { + LOG_ERR("failed to set %s", "filter config"); + return ret; + } + + ret = comp_mcux_acmp_set_dac_config(dev, &config->dac_config); + if (ret) { + LOG_ERR("failed to set %s", "dac config"); + return ret; + } + +#if COMP_MCUX_ACMP_HAS_DISCRETE_MODE + ret = comp_mcux_acmp_set_dm_config(dev, &config->dm_config); + if (ret) { + LOG_ERR("failed to set %s", "discrete mode config"); + return ret; + } +#endif + +#if COMP_MCUX_ACMP_HAS_WINDOW_MODE + ret = comp_mcux_acmp_set_window_mode(dev, config->enable_window_mode); + if (ret) { + LOG_ERR("failed to set %s", "window mode"); + return ret; + } +#endif + + ACMP_DisableInterrupts(config->base, UINT32_MAX); + config->irq_init(); + + return pm_device_driver_init(dev, mcux_acmp_pm_callback); +} + +#define MCUX_ACMP_IRQ_HANDLER_SYM(inst) \ + _CONCAT(mcux_acmp_irq_init, inst) + +#define MCUX_ACMP_IRQ_HANDLER_DEFINE(inst) \ + static void MCUX_ACMP_IRQ_HANDLER_SYM(inst)(void) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(inst), \ + DT_INST_IRQ(inst, priority), \ + mcux_acmp_irq_handler, \ + DEVICE_DT_INST_GET(inst), \ + 0); \ + \ + irq_enable(DT_INST_IRQN(inst)); \ + } + +#define MCUX_ACMP_DEVICE(inst) \ + PINCTRL_DT_INST_DEFINE(inst); \ + \ + static struct mcux_acmp_data _CONCAT(data, inst); \ + \ + MCUX_ACMP_IRQ_HANDLER_DEFINE(inst) \ + \ + static const struct mcux_acmp_config _CONCAT(config, inst) = { \ + .base = (CMP_Type *)DT_INST_REG_ADDR(inst), \ + .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + .irq_init = MCUX_ACMP_IRQ_HANDLER_SYM(inst), \ + .mode_config = MCUX_ACMP_DT_INST_MODE_CONFIG_INIT(inst), \ + .input_config = MCUX_ACMP_DT_INST_INPUT_CONFIG_INIT(inst), \ + .filter_config = MCUX_ACMP_DT_INST_FILTER_CONFIG_INIT(inst), \ + .dac_config = MCUX_ACMP_DT_INST_DAC_CONFIG_INIT(inst), \ + IF_ENABLED(COMP_MCUX_ACMP_HAS_DISCRETE_MODE, \ + (.dm_config = MCUX_ACMP_DT_INST_DM_CONFIG_INIT(inst),)) \ + IF_ENABLED(COMP_MCUX_ACMP_HAS_WINDOW_MODE, \ + (.enable_window_mode = MCUX_ACMP_DT_INST_EN_WINDOW_MODE(inst),)) \ + }; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, mcux_acmp_pm_callback); \ + \ + DEVICE_DT_INST_DEFINE(inst, \ + mcux_acmp_init, \ + PM_DEVICE_DT_INST_GET(inst), \ + &_CONCAT(data, inst), \ + &_CONCAT(config, inst), \ + POST_KERNEL, \ + CONFIG_COMPARATOR_INIT_PRIORITY, \ + &mcux_acmp_comp_api); + +DT_INST_FOREACH_STATUS_OKAY(MCUX_ACMP_DEVICE) diff --git a/dts/bindings/comparator/nxp,kinetis-acmp.yaml b/dts/bindings/comparator/nxp,kinetis-acmp.yaml index 76ce24b5bba..21db936c2bf 100644 --- a/dts/bindings/comparator/nxp,kinetis-acmp.yaml +++ b/dts/bindings/comparator/nxp,kinetis-acmp.yaml @@ -2,7 +2,39 @@ # Copyright (c) 2024 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 -description: NXP Kinetis ACMP (Analog CoMParator) +description: | + NXP Kinetis ACMP (Analog CoMParator) + + The following example displays the minimum node layout: + + acmp0: acmp@deadbeef { + compatible = "nxp,kinetis-acmp"; + reg = <0xdeadbeef 0x1000>; + interrupts = <0 0>; + clocks = <&scg KINETIS_SCG_BUS_CLK>; + status = "disabled"; + }; + + Enabling the comparator node requires setting the minimum default + configuration of the comparator. This includes selecting the + positive and negative inputs, and routing them using pinctrl: + + &pinctrl { + acmp0_default: acmp0_default { + group0 { + ... + }; + }; + }; + + &acmp0 { + status = "okay"; + pinctrl-0 = <&acmp0_default>; + pinctrl-names = "default"; + + positive-mux-input = "IN0"; + negative-mux-input = "IN1"; + }; compatible: "nxp,kinetis-acmp" diff --git a/include/zephyr/drivers/comparator/mcux_acmp.h b/include/zephyr/drivers/comparator/mcux_acmp.h new file mode 100644 index 00000000000..3d06644f0e8 --- /dev/null +++ b/include/zephyr/drivers/comparator/mcux_acmp.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_COMP_MCUX_ACMP_H_ +#define ZEPHYR_INCLUDE_DRIVERS_COMP_MCUX_ACMP_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum comp_mcux_acmp_offset_mode { + COMP_MCUX_ACMP_OFFSET_MODE_LEVEL0 = 0, + COMP_MCUX_ACMP_OFFSET_MODE_LEVEL1, +}; + +enum comp_mcux_acmp_hysteresis_mode { + COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL0 = 0, + COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL1, + COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL2, + COMP_MCUX_ACMP_HYSTERESIS_MODE_LEVEL3, +}; + +struct comp_mcux_acmp_mode_config { + enum comp_mcux_acmp_offset_mode offset_mode; + enum comp_mcux_acmp_hysteresis_mode hysteresis_mode; + bool enable_high_speed_mode; + bool invert_output; + bool use_unfiltered_output; + bool enable_pin_output; +}; + +enum comp_mcux_acmp_mux_input { + COMP_MCUX_ACMP_MUX_INPUT_IN0 = 0, + COMP_MCUX_ACMP_MUX_INPUT_IN1, + COMP_MCUX_ACMP_MUX_INPUT_IN2, + COMP_MCUX_ACMP_MUX_INPUT_IN3, + COMP_MCUX_ACMP_MUX_INPUT_IN4, + COMP_MCUX_ACMP_MUX_INPUT_IN5, + COMP_MCUX_ACMP_MUX_INPUT_IN6, + COMP_MCUX_ACMP_MUX_INPUT_IN7, +}; + +enum comp_mcux_acmp_port_input { + COMP_MCUX_ACMP_PORT_INPUT_DAC = 0, + COMP_MCUX_ACMP_PORT_INPUT_MUX, +}; + +struct comp_mcux_acmp_input_config { + enum comp_mcux_acmp_mux_input positive_mux_input; + enum comp_mcux_acmp_mux_input negative_mux_input; + enum comp_mcux_acmp_port_input positive_port_input; + enum comp_mcux_acmp_port_input negative_port_input; +}; + +struct comp_mcux_acmp_filter_config { + bool enable_sample; + uint8_t filter_count; + uint8_t filter_period; +}; + +enum comp_mcux_acmp_dac_vref_source { + COMP_MCUX_ACMP_DAC_VREF_SOURCE_VIN1 = 0, + COMP_MCUX_ACMP_DAC_VREF_SOURCE_VIN2, +}; + +struct comp_mcux_acmp_dac_config { + enum comp_mcux_acmp_dac_vref_source vref_source; + uint8_t value; + bool enable_output; + bool enable_high_speed_mode; +}; + +enum comp_mcux_acmp_dm_clock { + COMP_MCUX_ACMP_DM_CLOCK_SLOW = 0, + COMP_MCUX_ACMP_DM_CLOCK_FAST, +}; + +enum comp_mcux_acmp_dm_sample_time { + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T1 = 0, + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T2, + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T4, + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T8, + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T16, + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T32, + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T64, + COMP_MCUX_ACMP_DM_SAMPLE_TIME_T256, +}; + +enum comp_mcux_acmp_dm_phase_time { + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT0 = 0, + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT1, + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT2, + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT3, + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT4, + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT5, + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT6, + COMP_MCUX_ACMP_DM_PHASE_TIME_ALT7, +}; + +struct comp_mcux_acmp_dm_config { + bool enable_positive_channel; + bool enable_negative_channel; + bool enable_resistor_divider; + enum comp_mcux_acmp_dm_clock clock_source; + enum comp_mcux_acmp_dm_sample_time sample_time; + enum comp_mcux_acmp_dm_phase_time phase1_time; + enum comp_mcux_acmp_dm_phase_time phase2_time; +}; + +int comp_mcux_acmp_set_mode_config(const struct device *dev, + const struct comp_mcux_acmp_mode_config *config); + +int comp_mcux_acmp_set_input_config(const struct device *dev, + const struct comp_mcux_acmp_input_config *config); + +int comp_mcux_acmp_set_filter_config(const struct device *dev, + const struct comp_mcux_acmp_filter_config *config); + +int comp_mcux_acmp_set_dac_config(const struct device *dev, + const struct comp_mcux_acmp_dac_config *config); + +int comp_mcux_acmp_set_dm_config(const struct device *dev, + const struct comp_mcux_acmp_dm_config *config); + +int comp_mcux_acmp_set_window_mode(const struct device *dev, bool enable); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_COMP_MCUX_ACMP_H_ */