diff --git a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml index 4efcc7e0321e1..026853e941efa 100644 --- a/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml +++ b/boards/microchip/sam/sam_e54_xpro/sam_e54_xpro.yaml @@ -11,6 +11,7 @@ flash: 1024 ram: 256 supported: - clock_control + - comparator - dma - flash - gpio diff --git a/drivers/comparator/CMakeLists.txt b/drivers/comparator/CMakeLists.txt index 27546ae3d9231..77873a04e185b 100644 --- a/drivers/comparator/CMakeLists.txt +++ b/drivers/comparator/CMakeLists.txt @@ -9,6 +9,7 @@ zephyr_library_sources_ifdef(CONFIG_USERSPACE comparator_handlers.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_SILABS_ACMP comparator_silabs_acmp.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_FAKE_COMP comparator_fake_comp.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_IT51XXX_VCMP comparator_it51xxx_vcmp.c) +zephyr_library_sources_ifdef(CONFIG_COMPARATOR_MCHP_AC_G1 comparator_mchp_ac_g1.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_MCUX_ACMP comparator_mcux_acmp.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_NXP_CMP comparator_nxp_cmp.c) zephyr_library_sources_ifdef(CONFIG_COMPARATOR_NRF_COMP comparator_nrf_comp.c) diff --git a/drivers/comparator/Kconfig b/drivers/comparator/Kconfig index f16e6653f434a..2260ffb9637eb 100644 --- a/drivers/comparator/Kconfig +++ b/drivers/comparator/Kconfig @@ -21,6 +21,7 @@ config COMPARATOR_INIT_PRIORITY rsource "Kconfig.fake_comp" rsource "Kconfig.silabs_acmp" rsource "Kconfig.it51xxx_vcmp" +rsource "Kconfig.mchp" rsource "Kconfig.mcux_acmp" rsource "Kconfig.nxp_cmp" rsource "Kconfig.nrf_comp" diff --git a/drivers/comparator/Kconfig.mchp b/drivers/comparator/Kconfig.mchp new file mode 100644 index 0000000000000..e6ccbc918fb72 --- /dev/null +++ b/drivers/comparator/Kconfig.mchp @@ -0,0 +1,11 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +config COMPARATOR_MCHP_AC_G1 + bool "Microchip Comparator Driver for AC G1 Peripherals" + default y + depends on DT_HAS_MICROCHIP_AC_G1_COMPARATOR_ENABLED + select PINCTRL + help + Enable support for the Microchip Comparator driver + on G1 AC peripherals. diff --git a/drivers/comparator/comparator_mchp_ac_g1.c b/drivers/comparator/comparator_mchp_ac_g1.c new file mode 100644 index 0000000000000..b0619a6ae0877 --- /dev/null +++ b/drivers/comparator/comparator_mchp_ac_g1.c @@ -0,0 +1,578 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DT_DRV_COMPAT microchip_ac_g1_comparator + +LOG_MODULE_REGISTER(comparator_mchp_ac_g1, CONFIG_COMPARATOR_LOG_LEVEL); + +/* clang-format off */ +#define AC_REG (((const struct comparator_mchp_dev_config *)(dev)->config)->regs) +#define TIMEOUT_VALUE_US 1000 +#define DELAY_US 2 +#define COMPARATOR_INT_STATUS_NONE (-1) +/* clang-format on */ + +enum mchp_comp_pos_input { + MCHP_COMP_POS_INPUT_PIN0 = 0, + MCHP_COMP_POS_INPUT_PIN1, + MCHP_COMP_POS_INPUT_PIN2, + MCHP_COMP_POS_INPUT_PIN3, + MCHP_COMP_POS_INPUT_VSCALE, +}; + +enum mchp_comp_neg_input { + MCHP_COMP_NEG_INPUT_PIN0 = 0, + MCHP_COMP_NEG_INPUT_PIN1, + MCHP_COMP_NEG_INPUT_PIN2, + MCHP_COMP_NEG_INPUT_PIN3, + MCHP_COMP_NEG_INPUT_GND, + MCHP_COMP_NEG_INPUT_VSCALE, + MCHP_COMP_NEG_INPUT_BANDGAP, + MCHP_COMP_NEG_INPUT_DAC, +}; + +enum mchp_comp_output_mode { + MCHP_COMP_OUTPUT_OFF = 0, + MCHP_COMP_OUTPUT_ASYNC, + MCHP_COMP_OUTPUT_SYNC, +}; + +enum mchp_comp_filter { + MCHP_COMP_FILTER_OFF = 0, + MCHP_COMP_FILTER_MAJ3, + MCHP_COMP_FILTER_MAJ5, +}; + +enum mchp_comp_hysteresis { + MCHP_COMP_HYST_50MV = 0, + MCHP_COMP_HYST_100MV, + MCHP_COMP_HYST_150MV, +}; + +struct comparator_mchp_channel_cfg { + uint8_t channel_id; + + enum mchp_comp_pos_input pos_input; + enum mchp_comp_neg_input neg_input; + enum mchp_comp_output_mode output_mode; + enum mchp_comp_filter filter_length; + enum mchp_comp_hysteresis hysteresis_level; + + uint8_t vddana_scale_value; + bool single_shot_mode; + bool hysteresis_enable; + bool run_standby; + bool event_input_enable; + bool event_output_enable; + bool swap_inputs; +}; + +struct comparator_mchp_dev_data { + uint32_t interrupt_mask; /* Configured interrupt trigger type */ + uint32_t interrupt_status; /* Last interrupt status or edge detected */ + comparator_callback_t callback; + void *user_data; +}; + +struct comparator_mchp_clock { + const struct device *clock_dev; + clock_control_subsys_t mclk_sys; + clock_control_subsys_t gclk_sys; +}; + +struct comparator_mchp_dev_config { + ac_registers_t *regs; + const struct pinctrl_dev_config *pcfg; + struct comparator_mchp_clock comparator_clock; + void (*config_func)(const struct device *dev); + struct comparator_mchp_channel_cfg channel_config; +}; + +#if CONFIG_COMPARATOR_LOG_LEVEL_DBG +/* Print channel configuration */ +static void comparator_print_channel_cfg(const struct comparator_mchp_channel_cfg *cfg) +{ + static const char *const pos_input_names[] = {"PIN0", "PIN1", "PIN2", "PIN3", "VSCALE"}; + static const char *const neg_input_names[] = {"PIN0", "PIN1", "PIN2", "PIN3", + "GND", "VSCALE", "BANDGAP", "DAC"}; + static const char *const output_mode_names[] = {"OFF", "ASYNC", "SYNC"}; + static const char *const filter_names[] = {"OFF", "MAJ3", "MAJ5"}; + static const char *const hysteresis_names[] = {"HYST50", "HYST100", "HYST150"}; + + LOG_DBG("=== Comparator Channel Configuration ==="); + LOG_DBG("Channel ID : %u", cfg->channel_id); + LOG_DBG("Positive Input : %s", pos_input_names[cfg->pos_input]); + LOG_DBG("Negative Input : %s", neg_input_names[cfg->neg_input]); + LOG_DBG("Output Mode : %s", output_mode_names[cfg->output_mode]); + LOG_DBG("Filter Length : %s", filter_names[cfg->filter_length]); + LOG_DBG("Hysteresis Enabled : %s", cfg->hysteresis_enable ? "Yes" : "No"); + LOG_DBG("Hysteresis Level : %s", hysteresis_names[cfg->hysteresis_level]); + LOG_DBG("Single-shot Mode : %s", cfg->single_shot_mode ? "Yes" : "No"); + LOG_DBG("Run in Standby : %s", cfg->run_standby ? "Yes" : "No"); + LOG_DBG("Swap Inputs : %s", cfg->swap_inputs ? "Yes" : "No"); + LOG_DBG("Event Input Enabled : %s", cfg->event_input_enable ? "Yes" : "No"); + LOG_DBG("Event Output Enabled : %s", cfg->event_output_enable ? "Yes" : "No"); + LOG_DBG("========================================"); +} + +/* Print all the comparator register values */ +static void comparator_print_reg(const struct device *dev) +{ + LOG_DBG("=============== Comparator Registers ==============="); + LOG_DBG("%-20s: 0x%02x", "AC_CTRLA", AC_REG->AC_CTRLA); + LOG_DBG("%-20s: 0x%02x", "AC_CTRLB", AC_REG->AC_CTRLB); + LOG_DBG("%-20s: 0x%04x", "AC_EVCTRL", AC_REG->AC_EVCTRL); + LOG_DBG("%-20s: 0x%02x", "AC_INTENCLR", AC_REG->AC_INTENCLR); + LOG_DBG("%-20s: 0x%02x", "AC_INTENSET", AC_REG->AC_INTENSET); + LOG_DBG("%-20s: 0x%02x", "AC_INTFLAG", AC_REG->AC_INTFLAG); + LOG_DBG("%-20s: 0x%02x", "AC_STATUSA", AC_REG->AC_STATUSA); + LOG_DBG("%-20s: 0x%02x", "AC_STATUSB", AC_REG->AC_STATUSB); + LOG_DBG("%-20s: 0x%02x", "AC_DBGCTRL", AC_REG->AC_DBGCTRL); + LOG_DBG("%-20s: 0x%02x", "AC_WINCTRL", AC_REG->AC_WINCTRL); + LOG_DBG("%-20s: 0x%02x", "AC_SCALER[0]", AC_REG->AC_SCALER[0]); + LOG_DBG("%-20s: 0x%02x", "AC_SCALER[1]", AC_REG->AC_SCALER[1]); + LOG_DBG("%-20s: 0x%08x", "AC_COMPCTRL[0]", (uint32_t)AC_REG->AC_COMPCTRL[0]); + LOG_DBG("%-20s: 0x%08x", "AC_COMPCTRL[1]", (uint32_t)AC_REG->AC_COMPCTRL[1]); + LOG_DBG("%-20s: 0x%08x", "AC_SYNCBUSY", (uint32_t)AC_REG->AC_SYNCBUSY); + LOG_DBG("%-20s: 0x%04x", "AC_CALIB", AC_REG->AC_CALIB); + LOG_DBG("==================================================="); +} +#endif /* CONFIG_COMPARATOR_LOG_LEVEL_DBG */ + +/* Wait for synchronization */ +static void ac_wait_sync(ac_registers_t *ac_reg, uint32_t mask) +{ + if (WAIT_FOR(((ac_reg->AC_SYNCBUSY & mask) != mask), TIMEOUT_VALUE_US, + k_busy_wait(DELAY_US)) == false) { + LOG_ERR("Timeout waiting for AC_SYNCBUSY bits to clear (mask=0x%X)", mask); + } +} + +/* AC utility functions for enabling, configuring, and syncing the analog comparator */ +static void ac_enable(ac_registers_t *ac_reg, bool enable) +{ + if (enable == true) { + ac_reg->AC_CTRLA |= AC_CTRLA_ENABLE_Msk; + } else { + ac_reg->AC_CTRLA &= ~AC_CTRLA_ENABLE_Msk; + } + + ac_wait_sync(ac_reg, AC_SYNCBUSY_ENABLE_Msk); +} + +/* Enable a specific comparator channel and wait for sync */ +static void ac_channel_enable(ac_registers_t *ac_reg, uint8_t channel_id) +{ + ac_reg->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_ENABLE_Msk; + + /* Only 2 possible values for channel_id: 0 or 1 */ + if (channel_id == 0) { + ac_wait_sync(ac_reg, AC_SYNCBUSY_COMPCTRL0_Msk); + } else { + ac_wait_sync(ac_reg, AC_SYNCBUSY_COMPCTRL1_Msk); + } +} + +/* Enable interrupt for the given comparator channel */ +static inline void ac_enable_interrupt(ac_registers_t *ac_reg, uint8_t channel_id) +{ + if (channel_id == 0) { + ac_reg->AC_INTENSET = AC_INTENSET_COMP0_Msk; + } else { + ac_reg->AC_INTENSET = AC_INTENSET_COMP1_Msk; + } +} + +/* Disable interrupt for the given comparator channel */ +static inline void ac_disable_interrupt(ac_registers_t *ac_reg, uint8_t channel_id) +{ + if (channel_id == 0) { + ac_reg->AC_INTENCLR = AC_INTENCLR_COMP0_Msk; + } else { + ac_reg->AC_INTENCLR = AC_INTENCLR_COMP1_Msk; + } +} + +/* Trigger a single-shot comparison for the specified channel */ +static inline void ac_start_conversion(ac_registers_t *ac_reg, uint8_t channel_id) +{ + ac_reg->AC_CTRLB |= (AC_CTRLB_START0_Msk << channel_id); +} + +/* Wait until the comparator result for the specified channel is ready */ +static int ac_wait_for_conversion(ac_registers_t *ac_reg, uint8_t channel_id) +{ + uint32_t ready_mask = AC_STATUSB_READY0_Msk << channel_id; + + if (WAIT_FOR(((ac_reg->AC_STATUSB & ready_mask) == ready_mask), TIMEOUT_VALUE_US, + k_busy_wait(DELAY_US)) == false) { + LOG_ERR("Timeout waiting for AC_STATUSB ready (channel=%u)", channel_id); + return -ETIMEDOUT; + } + + return 0; +} + +/* Get the current comparator output state for the specified channel + * Returns true if output is HIGH, false if LOW + */ +static inline bool ac_get_result(ac_registers_t *ac_reg, uint8_t channel_id) +{ + uint32_t state_mask = AC_STATUSA_STATE0_Msk << channel_id; + + return (ac_reg->AC_STATUSA & state_mask) != 0; +} + +/* Configure comparator channel: inputs, mode, hysteresis, output, interrupt, scaler, etc. */ +static int ac_configure_channel(const struct device *dev) +{ + /* Get device config */ + const struct comparator_mchp_dev_config *const dev_cfg = dev->config; + const struct comparator_mchp_channel_cfg *channel_config = &dev_cfg->channel_config; + uint8_t channel_id = channel_config->channel_id; + + /* Reset COMPCTRL before new config */ + AC_REG->AC_COMPCTRL[channel_id] = 0; + + /* Set single-shot or continuous mode */ + AC_REG->AC_COMPCTRL[channel_id] = AC_COMPCTRL_SINGLE(channel_config->single_shot_mode); + + /* Set MUX inputs */ + AC_REG->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_MUXPOS(channel_config->pos_input); + AC_REG->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_MUXNEG(channel_config->neg_input); + + /* Configure VDDANA scaler if used */ + if ((channel_config->neg_input == MCHP_COMP_NEG_INPUT_VSCALE) || + (channel_config->pos_input == MCHP_COMP_POS_INPUT_VSCALE)) { + AC_REG->AC_SCALER[channel_id] = AC_SCALER_VALUE(channel_config->vddana_scale_value); + } + + /* Set Output mode */ + AC_REG->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_OUT(channel_config->output_mode); + + /* Set Filter length */ + AC_REG->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_FLEN(channel_config->filter_length); + + if (channel_config->single_shot_mode == false) { + /* Enable hysterisis */ + AC_REG->AC_COMPCTRL[channel_id] |= + AC_COMPCTRL_HYSTEN(channel_config->hysteresis_enable ? 1 : 0); + + /* Set hysterisis */ + if (channel_config->hysteresis_enable == true) { + AC_REG->AC_COMPCTRL[channel_id] |= + AC_COMPCTRL_HYST(channel_config->hysteresis_level); + } + } + + /* Set Comparator speed */ + AC_REG->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_SPEED(AC_COMPCTRL_SPEED_HIGH_Val); + + /* Run in standby if enabled */ + if (channel_config->run_standby == true) { + AC_REG->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_RUNSTDBY(1); + } + + LOG_DBG("Configuration done AC_REG->AC_COMPCTRL[0] : 0x%x", + AC_REG->AC_COMPCTRL[channel_id]); + + /* Interrupt Enable */ + ac_enable_interrupt(AC_REG, channel_id); + + return 0; +} + +static void comparator_mchp_isr(const struct device *dev) +{ + struct comparator_mchp_dev_data *const dev_data = dev->data; + + /* Store comparator status (latched value from AC_STATUSA) */ + dev_data->interrupt_status = AC_REG->AC_STATUSA; + + /* Clear interrupt flags (write 1 to clear) */ + AC_REG->AC_INTFLAG = AC_INTFLAG_Msk; + + if (dev_data->callback != NULL) { + dev_data->callback(dev, dev_data->user_data); + } +} + +static int comparator_mchp_get_output(const struct device *dev) +{ + const struct comparator_mchp_dev_config *const dev_cfg = dev->config; + const struct comparator_mchp_channel_cfg *channel_config = &dev_cfg->channel_config; + uint8_t channel_id = channel_config->channel_id; + int ret = 0; + bool result = false; + +#if CONFIG_COMPARATOR_LOG_LEVEL_DBG + /* Optional debug */ + comparator_print_channel_cfg(channel_config); +#endif /* CONFIG_COMPARATOR_LOG_LEVEL_DBG */ + + /* Trigger conversion in single-shot mode */ + if (channel_config->single_shot_mode == true) { + ac_start_conversion(AC_REG, channel_id); + } + + ret = ac_wait_for_conversion(AC_REG, channel_id); + if (ret == 0) { + +#if CONFIG_COMPARATOR_LOG_LEVEL_DBG + /* Optional debug */ + comparator_print_reg(dev); +#endif /* CONFIG_COMPARATOR_LOG_LEVEL_DBG */ + result = ac_get_result(AC_REG, channel_id); + ret = result ? 1 : 0; + LOG_DBG("AC comparator result: %s", result ? "HIGH" : "LOW"); + } + + return ret; +} + +static int comparator_mchp_set_trigger(const struct device *dev, enum comparator_trigger trigger) +{ + const struct comparator_mchp_dev_config *const dev_cfg = dev->config; + struct comparator_mchp_dev_data *const dev_data = dev->data; + const struct comparator_mchp_channel_cfg *channel_config = &dev_cfg->channel_config; + uint8_t channel_id = channel_config->channel_id; + uint8_t intsel_val; + + LOG_DBG("Setting comparator trigger mode: %d", trigger); + + /* Map trigger enum to hardware INTSEL value */ + switch (trigger) { + case COMPARATOR_TRIGGER_NONE: + intsel_val = AC_COMPCTRL_INTSEL_EOC_Val; + break; + case COMPARATOR_TRIGGER_RISING_EDGE: + intsel_val = AC_COMPCTRL_INTSEL_RISING_Val; + break; + case COMPARATOR_TRIGGER_FALLING_EDGE: + intsel_val = AC_COMPCTRL_INTSEL_FALLING_Val; + break; + case COMPARATOR_TRIGGER_BOTH_EDGES: + intsel_val = AC_COMPCTRL_INTSEL_TOGGLE_Val; + break; + default: + LOG_ERR("Invalid comparator trigger: %d, defaulting to NONE", trigger); + intsel_val = AC_COMPCTRL_INTSEL_EOC_Val; + trigger = COMPARATOR_TRIGGER_NONE; + break; + } + + /* Update INTSEL field */ + AC_REG->AC_COMPCTRL[channel_id] &= ~AC_COMPCTRL_INTSEL_Msk; + AC_REG->AC_COMPCTRL[channel_id] |= AC_COMPCTRL_INTSEL(intsel_val); + + if (trigger != COMPARATOR_TRIGGER_NONE) { + ac_enable_interrupt(AC_REG, channel_id); + } else { + ac_disable_interrupt(AC_REG, channel_id); + } + LOG_DBG("Trigger mode: %d, INTSEL set to: %d", trigger, intsel_val); + + /* Store trigger mode (not INTSEL value!) */ + dev_data->interrupt_mask = trigger; + dev_data->interrupt_status = COMPARATOR_INT_STATUS_NONE; + + return 0; +} + +static int comparator_mchp_set_trigger_callback(const struct device *dev, + comparator_callback_t callback, void *user_data) +{ + struct comparator_mchp_dev_data *const dev_data = dev->data; + const struct comparator_mchp_dev_config *const dev_cfg = dev->config; + const struct comparator_mchp_channel_cfg *channel_config = &dev_cfg->channel_config; + uint8_t channel_id = channel_config->channel_id; + + ac_disable_interrupt(AC_REG, channel_id); + + dev_data->callback = callback; + dev_data->user_data = user_data; + + ac_enable_interrupt(AC_REG, channel_id); + + return 0; +} + +static int comparator_mchp_trigger_is_pending(const struct device *dev) +{ + const struct comparator_mchp_dev_config *const dev_cfg = dev->config; + struct comparator_mchp_dev_data *const dev_data = dev->data; + const struct comparator_mchp_channel_cfg *channel_config = &dev_cfg->channel_config; + uint8_t channel_id = channel_config->channel_id; + int ret = 0; + uint32_t state_mask = AC_STATUSA_STATE0_Msk << channel_id; + + LOG_DBG("Checking if comparator trigger is pending..."); + + /* Only process in polling mode (no callback handler registered) */ + if (dev_data->callback == NULL && + dev_data->interrupt_status != COMPARATOR_INT_STATUS_NONE) { + switch (dev_data->interrupt_mask) { + case COMPARATOR_TRIGGER_RISING_EDGE: + if ((dev_data->interrupt_status & state_mask) != 0) { + LOG_DBG("Rising edge trigger is pending"); + ret = 1; + } + break; + case COMPARATOR_TRIGGER_FALLING_EDGE: + if ((dev_data->interrupt_status & state_mask) == 0) { + LOG_DBG("Falling edge trigger is pending"); + ret = 1; + } + break; + case COMPARATOR_TRIGGER_BOTH_EDGES: + LOG_DBG("Both edge trigger is pending"); + ret = 1; + break; + default: + break; + } + /* Mark as consumed */ + dev_data->interrupt_status = COMPARATOR_INT_STATUS_NONE; + } + + return ret; +} + +static int comparator_mchp_init(const struct device *dev) +{ + const struct comparator_mchp_dev_config *const dev_cfg = dev->config; + struct comparator_mchp_dev_data *const dev_data = dev->data; + int ret; + uint8_t channel_id = dev_cfg->channel_config.channel_id; + uint32_t sw0_reg; + + dev_data->interrupt_status = COMPARATOR_INT_STATUS_NONE; + + /* Turn on GCLK for AC */ + ret = clock_control_on(dev_cfg->comparator_clock.clock_dev, + dev_cfg->comparator_clock.gclk_sys); + if (ret < 0 && ret != -EALREADY) { + LOG_ERR("Failed to enable GCLK for COMP: %d", ret); + return ret; + } + + /* Turn on MCLK for AC */ + ret = clock_control_on(dev_cfg->comparator_clock.clock_dev, + dev_cfg->comparator_clock.mclk_sys); + if (ret < 0 && ret != -EALREADY) { + LOG_ERR("Failed to enable MCLK for COMP: %d", ret); + return ret; + } + + /* Apply pinctrl default state */ + ret = pinctrl_apply_state(dev_cfg->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("Failed to apply pinctrl state: %d", ret); + return ret; + } + + /* Reset the comparator peripheral */ + AC_REG->AC_CTRLA = AC_CTRLA_SWRST_Msk; + ac_wait_sync(AC_REG, AC_SYNCBUSY_SWRST_Msk); + + /* Configure calibration */ + sw0_reg = ((fuses_sw0_fuses_registers_t *)SW0_ADDR)->FUSES_SW0_WORD_0; + dev_cfg->regs->AC_CALIB = + (sw0_reg & FUSES_SW0_WORD_0_AC_BIAS0_Msk) >> FUSES_SW0_WORD_0_AC_BIAS0_Pos; + + /* Configure ISR */ + dev_cfg->config_func(dev); + ac_configure_channel(dev); + ac_channel_enable(AC_REG, channel_id); + +#if CONFIG_COMPARATOR_LOG_LEVEL_DBG + comparator_print_reg(dev); +#endif /* CONFIG_COMPARATOR_LOG_LEVEL_DBG */ + + ac_enable(AC_REG, true); + ac_wait_sync(AC_REG, AC_SYNCBUSY_ENABLE_Msk); + + /* If everything is OK but the clocks are already enabled, return 0. */ + return (ret == -EALREADY) ? 0 : ret; +} + +static DEVICE_API(comparator, comparator_mchp_api) = { + .get_output = comparator_mchp_get_output, + .set_trigger = comparator_mchp_set_trigger, + .set_trigger_callback = comparator_mchp_set_trigger_callback, + .trigger_is_pending = comparator_mchp_trigger_is_pending, +}; + +#define COMPARATOR_MCHP_DATA_DEFN(n) \ + static struct comparator_mchp_dev_data comparator_mchp_data_##n; + +/* Connect a single IRQ for instance n at index idx */ +#define COMPARATOR_MCHP_IRQ_CONNECT(idx, n) \ + IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, idx), \ + ( \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, idx, irq), \ + DT_INST_IRQ_BY_IDX(n, idx, priority), \ + comparator_mchp_isr, \ + DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(n, idx, irq)); \ + ) \ + ) + +/* Generate config function for instance n */ +#define COMPARATOR_MCHP_DEFINE_CONFIG_FUNC(n) \ + static void comparator_mchp_config_##n(const struct device *dev) \ + { \ + /* Connect all IRQs declared in devicetree */ \ + LISTIFY(DT_NUM_IRQS(DT_DRV_INST(n)), \ + COMPARATOR_MCHP_IRQ_CONNECT, \ + (), n); \ + } + +#define COMPARATOR_MCHP_CONFIG_DEFN(n) \ + static void comparator_mchp_config_##n(const struct device *dev); \ + static const struct comparator_mchp_dev_config comparator_mchp_cfg_##n = { \ + .regs = (ac_registers_t *)DT_INST_REG_ADDR(n), \ + .config_func = comparator_mchp_config_##n, \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .comparator_clock.clock_dev = DEVICE_DT_GET(DT_NODELABEL(clock)), \ + .comparator_clock.mclk_sys = \ + (void *)(DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, subsystem)), \ + .comparator_clock.gclk_sys = \ + (void *)(DT_INST_CLOCKS_CELL_BY_NAME(n, gclk, subsystem)), \ + .channel_config = { \ + .channel_id = DT_PROP_OR(DT_DRV_INST(n), comparator_channel, 0), \ + .pos_input = DT_ENUM_IDX_OR(DT_DRV_INST(n), positive_mux_input, 0), \ + .neg_input = DT_ENUM_IDX_OR(DT_DRV_INST(n), negative_mux_input, 0), \ + .output_mode = DT_ENUM_IDX_OR(DT_DRV_INST(n), output_mode, 0), \ + .filter_length = DT_ENUM_IDX_OR(DT_DRV_INST(n), filter_length, 0), \ + .hysteresis_level = DT_ENUM_IDX_OR(DT_DRV_INST(n), hysteresis_level, 0), \ + .vddana_scale_value = DT_PROP_OR(DT_DRV_INST(n), vddana_scale_value, 0), \ + .single_shot_mode = DT_PROP_OR(DT_DRV_INST(n), single_shot_mode, 0), \ + .hysteresis_enable = DT_PROP_OR(DT_DRV_INST(n), hysteresis_enable, 0), \ + .run_standby = DT_PROP_OR(DT_DRV_INST(n), run_standby, 0), \ + .event_input_enable = DT_PROP_OR(DT_DRV_INST(n), event_input_enable, 0), \ + .event_output_enable = DT_PROP_OR(DT_DRV_INST(n), event_output_enable, 0), \ + .swap_inputs = DT_PROP_OR(DT_DRV_INST(n), swap_inputs, 0), \ + }} + +#define COMPARATOR_MCHP_DEVICE_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + COMPARATOR_MCHP_CONFIG_DEFN(n); \ + COMPARATOR_MCHP_DATA_DEFN(n); \ + DEVICE_DT_INST_DEFINE(n, comparator_mchp_init, NULL, &comparator_mchp_data_##n, \ + &comparator_mchp_cfg_##n, POST_KERNEL, \ + CONFIG_COMPARATOR_INIT_PRIORITY, &comparator_mchp_api); \ + COMPARATOR_MCHP_DEFINE_CONFIG_FUNC(n); + +DT_INST_FOREACH_STATUS_OKAY(COMPARATOR_MCHP_DEVICE_INIT); diff --git a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi index c8dacf1ea4484..32377c05a0b1e 100644 --- a/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi +++ b/dts/arm/microchip/sam/sam_d5x_e5x/common/samd5xe5x.dtsi @@ -225,6 +225,16 @@ channels = <3>; }; + ac: ac@42002000 { + compatible = "microchip,ac-g1-comparator"; + status = "disabled"; + reg = <0x42002000 0x26>; + interrupts = <122 0>; + clocks = <&mclkperiph CLOCK_MCHP_MCLKPERIPH_ID_APBC_AC>, + <&gclkperiph CLOCK_MCHP_GCLKPERIPH_ID_AC>; + clock-names = "mclk", "gclk"; + }; + sercom4: sercom@43000000 { compatible = "microchip,sercom-g1"; status = "disabled"; diff --git a/dts/bindings/comparator/microchip,ac-g1-comparator.yaml b/dts/bindings/comparator/microchip,ac-g1-comparator.yaml new file mode 100644 index 0000000000000..e2b01eac2def7 --- /dev/null +++ b/dts/bindings/comparator/microchip,ac-g1-comparator.yaml @@ -0,0 +1,128 @@ +# Copyright (c) 2025 Microchip Technology Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: Microchip G1 Analog Comparator + +description: | + Binding for the Microchip Comparator driver supporting AC G1 peripherals. + +compatible: "microchip,ac-g1-comparator" + +include: + - name: base.yaml + - name: pinctrl-device.yaml + +properties: + reg: + required: true + description: | + Base address of AC peripheral and its size + + comparator-channel: + required: true + type: int + enum: [0, 1] + description: | + Comparator channel index (0 or 1). + + single-shot-mode: + type: boolean + description: | + Enable single-shot mode operation. When enabled, the comparator performs a single comparison + and disables itself. This must be disabled for continuous-mode features like hysteresis. + + positive-mux-input: + type: string + enum: ["pin0", "pin1", "pin2", "pin3", "vscale"] + description: | + Positive input source selection (MUXPOS): + - pin0: I/O pin 0 (0x0) + - pin1: I/O pin 1 (0x1) + - pin2: I/O pin 2 (0x2) + - pin3: I/O pin 3 (0x3) + - vscale: VDDANA scaler (0x4) + + negative-mux-input: + type: string + enum: ["pin0", "pin1", "pin2", "pin3", "gnd", "vscale", "bandgap", "dac"] + description: | + Negative input source selection (MUXNEG): + - pin0: I/O pin 0 (0x0) + - pin1: I/O pin 1 (0x1) + - pin2: I/O pin 2 (0x2) + - pin3: I/O pin 3 (0x3) + - gnd: Ground (0x4) + - vscale: VDDANA scaler (0x5) + - bandgap: Internal bandgap voltage (0x6) + - dac: DAC output (0x7) + + vddana-scale-value: + type: int + default: 0 + description: | + VSCALE = (VDDANA * (value + 1)) / 64 + Only used when either positive or negative input is configured as "vscale". + Valid range: 0 to 63 + + output-mode: + type: string + enum: ["off", "async", "sync"] + default: "off" + description: | + Comparator output routing mode: + - off: Output is not routed to COMPn I/O pin (0x0) + - async: Asynchronous output is routed (0x1) + - sync: Synchronous filtered output is routed (0x2) + + run-standby: + type: boolean + description: | + Keep the comparator running in standby mode (when the core is sleeping). + + hysteresis-enable: + type: boolean + description: | + Enable hysteresis on comparator output. + This is only valid when single-shot mode is disabled (i.e., in continuous mode). + + hysteresis-level: + type: string + enum: ["hyst50", "hyst100", "hyst150"] + default: "hyst50" + description: | + Hysteresis voltage level to apply when hysteresis is enabled: + - hyst50: 50 mV (0x0) + - hyst100: 100 mV (0x1) + - hyst150: 150 mV (0x2) + + This setting is effective only if hysteresis is enabled and single-shot mode is disabled. + + filter-length: + type: string + enum: ["off", "maj3", "maj5"] + default: "off" + description: | + Output filtering (debounce) mode: + - off: No filtering (0x0) + - maj3: 3-bit majority filter (2 of 3) (0x1) + - maj5: 5-bit majority filter (3 of 5) (0x2) + + swap-inputs: + type: boolean + description: | + Swap the comparator inputs and invert the output: + - false: MUXPOS → positive input, MUXNEG → negative input + - true: MUXNEG → positive input, MUXPOS → negative input (output inverted) + + This can be used for offset cancellation. + It must be configured before enabling the comparator. + + event-input-enable: + type: boolean + description: | + Enable the event input path for the comparator. + + event-output-enable: + type: boolean + description: | + Enable the event output path for the comparator. diff --git a/tests/drivers/comparator/gpio_loopback/boards/sam_e54_xpro.overlay b/tests/drivers/comparator/gpio_loopback/boards/sam_e54_xpro.overlay new file mode 100644 index 0000000000000..a32dba7a01c16 --- /dev/null +++ b/tests/drivers/comparator/gpio_loopback/boards/sam_e54_xpro.overlay @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Microchip Technology Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * PA4 looped back to PA5 + */ + +/ { + aliases { + test-comp = ∾ + }; + + zephyr,user { + test-gpios = <&porta 5 GPIO_ACTIVE_HIGH>; + }; +}; + +&porta { + status = "okay"; +}; + +&ac { + comparator-channel = <0>; + positive-mux-input = "pin0"; + negative-mux-input = "bandgap"; + run-standby; + hysteresis-enable; + hysteresis-level = "hyst150"; + filter-length = "maj5"; + // single-shot-mode; + + /* Assign pinctrl for PA04 */ + pinctrl-0 = <&pinctrl_ac>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pinctrl { + pinctrl_ac: ac_pins { + group1 { + pinmux = ; /* PA5B_AC_AIN1 */ + }; + }; +};