| /* |
| * Copyright (c) 2024 STMicroelectronics |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * Terminology used in this file: |
| * - sampling: a single analog-to-digital conversion performed by the ADC |
| * - sequence: one or more sampling(s) performed one after the other by the |
| * ADC after a single programmation. This is the meaning used in the |
| * STM32WB0 ADC documentation. |
| * - round: all ADC operations needed to read all channels in the adc_sequence passed |
| * to adc_read. Zephyr calls this a "sampling", but we use the term "round" to |
| * prevent confusion with STM32 terminology. A single round may require multiple |
| * sequences to be performed by the ADC to be completed, due to hardware limitations. |
| * |
| * When Zephyr's "sequence" feature is used, the same round is repeated multiple times. |
| * |
| * - idle mode: clock & ADC configuration that minimizes power consumption |
| * - Only the ADC digital domain clock is turned on: |
| * - ADC is powered off (CTRL.ADC_CTRL_ADC_ON_OFF = 0) |
| * - ADC analog domain clock is turned off |
| * - If applicable: |
| * - ADC LDO is disabled |
| * - ADC I/O Booster clock is turned off |
| * - ADC I/O Booster is disabled |
| * - ADC-SMPS clock synchronization is disabled |
| */ |
| |
| #define DT_DRV_COMPAT st_stm32wb0_adc |
| |
| #include <errno.h> |
| #include <stdbool.h> |
| |
| #include <zephyr/drivers/adc.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/clock_control/stm32_clock_control.h> |
| #include <zephyr/device.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/init.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/pm/policy.h> |
| #include <zephyr/sys/check.h> |
| #include <zephyr/sys/util_macro.h> |
| #include <zephyr/sys/math_extras.h> |
| |
| #include <soc.h> |
| #include <stm32_ll_adc.h> |
| #include <stm32_ll_utils.h> |
| |
| #ifdef CONFIG_ADC_STM32_DMA |
| #include <zephyr/drivers/dma/dma_stm32.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/toolchain.h> |
| #include <stm32_ll_dma.h> |
| #endif |
| |
| #define ADC_CONTEXT_USES_KERNEL_TIMER |
| #define ADC_CONTEXT_ENABLE_ON_COMPLETE |
| #include "adc_context.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(adc_stm32wb0, CONFIG_ADC_LOG_LEVEL); |
| |
| /** |
| * Driver private definitions & assertions |
| */ |
| #define ADC_INSTANCE 0 |
| #define ADC_NODE DT_DRV_INST(ADC_INSTANCE) |
| #define ADC_USE_IO_BOOSTER DT_PROP_OR(ADC_NODE, io_booster, 0) |
| |
| #define LL_ADC_EXTERNAL_CHANNEL_NUM 12 /* must be a plain constant for LISTIFY */ |
| #define LL_ADC_EXTERNAL_CHANNEL_MAX (LL_ADC_CHANNEL_VINP3_VINM3 + 1U) |
| #define LL_ADC_CHANNEL_MAX (LL_ADC_CHANNEL_TEMPSENSOR + 1U) |
| #define LL_ADC_VIN_RANGE_INVALID ((uint8_t)0xFFU) |
| |
| #define NUM_CALIBRATION_POINTS 4 /* 4 calibration point registers (COMP_[0-3]) */ |
| |
| #if !defined(ADC_CONF_SAMPLE_RATE_MSB) |
| # define NUM_ADC_SAMPLE_RATES 4 /* SAMPLE_RATE on 2 bits */ |
| #else |
| # define NUM_ADC_SAMPLE_RATES 32 /* SAMPLE_RATE on 5 bits */ |
| #endif |
| |
| /* The STM32WB0 has a 12-bit ADC, but the resolution can be |
| * enhanced to 16-bit by oversampling (using the downsampler) |
| */ |
| #define ADC_MIN_RESOLUTION 12 |
| #define ADC_MAX_RESOLUTION 16 |
| |
| /* ADC channel type definitions are not provided by LL as |
| * it uses per-type functions instead. Bring our own. |
| */ |
| #define ADC_CHANNEL_TYPE_SINGLE_NEG (0x00U) /* Single-ended, positive */ |
| #define ADC_CHANNEL_TYPE_SINGLE_POS (0x01U) /* Single-ended, negative */ |
| #define ADC_CHANNEL_TYPE_DIFF (0x02U) /* Differential */ |
| #define ADC_CHANNEL_TYPE_INVALID (0xFFU) /* Invalid */ |
| |
| /** See RM0505 ยง6.2.1 "System clock details" */ |
| BUILD_ASSERT(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC >= (8 * 1000 * 1000), |
| "STM32WB0: system clock frequency must be at least 8MHz to use ADC"); |
| |
| /** |
| * Driver private structures |
| */ |
| struct adc_stm32wb0_data { |
| struct adc_context ctx; |
| const struct device *const dev; |
| |
| /** |
| * Bitmask of all channels requested as part of this round |
| * but not sampled yet. |
| */ |
| uint32_t unsampled_channels; |
| |
| /** |
| * Pointer in output buffer where the first data sample of the round |
| * is stored. This is used to reload next_sample_ptr when the user |
| * callback asks to repeat a round. |
| */ |
| uint16_t *round_buf_pointer; |
| |
| /** |
| * Pointer in output buffer where the next data sample from ADC should |
| * be stored. |
| */ |
| uint16_t *next_sample_ptr; |
| |
| #if defined(CONFIG_ADC_STM32_DMA) |
| /** Size of the sequence currently scheduled and executing */ |
| size_t sequence_length; |
| struct dma_config dmac_config; |
| struct dma_block_config dma_block_config; |
| #endif |
| |
| /** Channels configuration */ |
| struct adc_stm32wb0_channel_config { |
| /** Vinput range selection */ |
| uint8_t vinput_range; |
| } channel_config[LL_ADC_CHANNEL_MAX]; |
| }; |
| |
| struct adc_stm32wb0_config { |
| ADC_TypeDef *reg; |
| const struct pinctrl_dev_config *pinctrl_cfg; |
| /** ADC digital domain clock */ |
| struct stm32_pclken dig_clk; |
| /** ADC analog domain clock */ |
| struct stm32_pclken ana_clk; |
| #if defined(CONFIG_ADC_STM32_DMA) |
| const struct device *dmac; |
| uint32_t dma_channel; |
| #endif |
| }; |
| |
| /** |
| * Driver private utility functions |
| */ |
| |
| /** |
| * In STM32CubeWB0 v1.0.0, the LL_GetPackageType is buggy and returns wrong values. |
| * This bug is reported in the ST internal bugtracker under reference 185295. |
| * For now, implement the function ourselves. |
| */ |
| static inline uint32_t ll_get_package_type(void) |
| { |
| return sys_read32(PACKAGE_BASE); |
| } |
| |
| static inline struct adc_stm32wb0_data *drv_data_from_adc_ctx(struct adc_context *adc_ctx) |
| { |
| return CONTAINER_OF(adc_ctx, struct adc_stm32wb0_data, ctx); |
| } |
| |
| static inline uint8_t vinput_range_from_adc_ref(uint32_t reference) |
| { |
| switch (reference) { |
| case ADC_REF_INTERNAL: |
| case ADC_REF_VDD_1: |
| return LL_ADC_VIN_RANGE_3V6; |
| case ADC_REF_VDD_1_2: |
| return LL_ADC_VIN_RANGE_2V4; |
| case ADC_REF_VDD_1_3: |
| return LL_ADC_VIN_RANGE_1V2; |
| default: |
| return LL_ADC_VIN_RANGE_INVALID; |
| } |
| } |
| |
| static inline uint32_t ds_width_from_adc_res(uint32_t resolution) |
| { |
| /* |
| * 12 -> 0 (LL_ADC_DS_DATA_WIDTH_12_BIT) |
| * 13 -> 1 (LL_ADC_DS_DATA_WIDTH_13_BIT) |
| * 14 -> 2 (LL_ADC_DS_DATA_WIDTH_14_BIT) |
| * 15 -> 3 (LL_ADC_DS_DATA_WIDTH_15_BIT) |
| * 16 -> 4 (LL_ADC_DS_DATA_WIDTH_16_BIT) |
| */ |
| return resolution - 12; |
| } |
| |
| static inline uint8_t get_channel_type(uint32_t channel) |
| { |
| switch (channel) { |
| case LL_ADC_CHANNEL_VINM0: |
| case LL_ADC_CHANNEL_VINM1: |
| case LL_ADC_CHANNEL_VINM2: |
| case LL_ADC_CHANNEL_VINM3: |
| case LL_ADC_CHANNEL_VBAT: |
| return ADC_CHANNEL_TYPE_SINGLE_NEG; |
| case LL_ADC_CHANNEL_VINP0: |
| case LL_ADC_CHANNEL_VINP1: |
| case LL_ADC_CHANNEL_VINP2: |
| case LL_ADC_CHANNEL_VINP3: |
| case LL_ADC_CHANNEL_TEMPSENSOR: |
| return ADC_CHANNEL_TYPE_SINGLE_POS; |
| case LL_ADC_CHANNEL_VINP0_VINM0: |
| case LL_ADC_CHANNEL_VINP1_VINM1: |
| case LL_ADC_CHANNEL_VINP2_VINM2: |
| case LL_ADC_CHANNEL_VINP3_VINM3: |
| return ADC_CHANNEL_TYPE_DIFF; |
| default: |
| __ASSERT_NO_MSG(0); |
| return ADC_CHANNEL_TYPE_INVALID; |
| } |
| } |
| |
| /** |
| * @brief Checks all fields of the adc_sequence and asserts they are |
| * valid and all configuration options are supported by the driver. |
| * |
| * @param sequence adc_sequence to validate |
| * @return 0 if the adc_sequence is valid, negative value otherwise |
| */ |
| static int validate_adc_sequence(const struct adc_sequence *sequence) |
| { |
| const size_t round_size = sizeof(uint16_t) * POPCOUNT(sequence->channels); |
| size_t needed_buf_size; |
| |
| if (sequence->channels == 0 || |
| (sequence->channels & ~BIT_MASK(LL_ADC_CHANNEL_MAX)) != 0) { |
| LOG_ERR("invalid channels selection"); |
| return -EINVAL; |
| } |
| |
| CHECKIF(!sequence->buffer) { |
| LOG_ERR("storage buffer pointer is NULL"); |
| return -EINVAL; |
| } |
| |
| if (!IN_RANGE(sequence->resolution, ADC_MIN_RESOLUTION, ADC_MAX_RESOLUTION)) { |
| LOG_ERR("invalid resolution %u (must be between %u and %u)", |
| sequence->resolution, ADC_MIN_RESOLUTION, ADC_MAX_RESOLUTION); |
| return -EINVAL; |
| } |
| |
| /* N.B.: LL define is in the same log2(x) format as the Zephyr variable */ |
| if (sequence->oversampling > LL_ADC_DS_RATIO_128) { |
| LOG_ERR("oversampling unsupported by hardware (max: %lu)", LL_ADC_DS_RATIO_128); |
| return -ENOTSUP; |
| } |
| |
| if (sequence->options) { |
| const size_t samplings = (size_t)sequence->options->extra_samplings + 1; |
| |
| if (size_mul_overflow(round_size, samplings, &needed_buf_size)) { |
| return -ENOMEM; |
| } |
| } else { |
| needed_buf_size = round_size; |
| } |
| |
| if (needed_buf_size > sequence->buffer_size) { |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Set which channel is sampled during a given conversion of the sequence. |
| * |
| * @param ADCx ADC registers pointer |
| * @param Conversion Target conversion index (0~15) |
| * @param Channel Channel to sample during specified conversion |
| * |
| * @note This function is a more convenient implementation of LL_ADC_SetSequencerRanks |
| */ |
| static inline void ll_adc_set_conversion_channel(ADC_TypeDef *ADCx, |
| uint32_t Conversion, uint32_t Channel) |
| { |
| /** |
| * There are two registers to control the sequencer: |
| * - SEQ_1 holds channel selection for conversions 0~7 |
| * - SEQ_2 holds channel selection for conversions 8~15 |
| * |
| * Notice that all conversions in SEQ_2 have 3rd bit set, |
| * whereas all conversions in SEQ_1 have 3rd bit clear. |
| * |
| * In a SEQ_x register, each channel occupies 4 bits, so the |
| * field for conversion N is at bit offset (4 * (N % 7)). |
| */ |
| const uint32_t reg = (Conversion & 8) ? 1 : 0; |
| const uint32_t shift = 4 * (Conversion & 7); |
| |
| MODIFY_REG((&ADCx->SEQ_1)[reg], ADC_SEQ_1_SEQ0 << shift, Channel << shift); |
| } |
| |
| /** |
| * @brief Set the calibration point to use for a chosen channel type and Vinput range. |
| * |
| * @param ADCx ADC registers pointer |
| * @param Type Channel type |
| * @param Range Channel Vinput range |
| * @param Point Calibration point to use |
| * |
| * @note This is a generic version of the LL_ADC_SetCalibPointFor* functions. |
| */ |
| static inline void ll_adc_set_calib_point_for_any(ADC_TypeDef *ADCx, uint32_t Type, |
| uint32_t Range, uint32_t Point) |
| { |
| __ASSERT(Range == LL_ADC_VIN_RANGE_1V2 |
| || Range == LL_ADC_VIN_RANGE_2V4 |
| || Range == LL_ADC_VIN_RANGE_3V6, "Range is not valid"); |
| |
| __ASSERT(Type == ADC_CHANNEL_TYPE_SINGLE_NEG |
| || Type == ADC_CHANNEL_TYPE_SINGLE_POS |
| || Type == ADC_CHANNEL_TYPE_DIFF, "Type is not valid"); |
| |
| __ASSERT(Point == LL_ADC_CALIB_POINT_1 |
| || Point == LL_ADC_CALIB_POINT_2 |
| || Point == LL_ADC_CALIB_POINT_3 |
| || Point == LL_ADC_CALIB_POINT_4, "Point is not valid"); |
| |
| /* Register organization is as follows: |
| * |
| * - Group for 1.2V Vinput range |
| * - Group for 2.4V Vinput range |
| * - Group for 3.6V Vinput range |
| * |
| * where Group is organized as: |
| * - Select for Single Negative mode |
| * - Select for Single Positive mode |
| * - Select for Differential mode |
| * |
| * Each select is 2 bits, and each group is thus 6 bits. |
| */ |
| |
| uint32_t type_shift, group_shift; |
| |
| switch (Type) { |
| case ADC_CHANNEL_TYPE_SINGLE_NEG: |
| type_shift = 0 * 2; |
| break; |
| case ADC_CHANNEL_TYPE_SINGLE_POS: |
| type_shift = 1 * 2; |
| break; |
| case ADC_CHANNEL_TYPE_DIFF: |
| type_shift = 2 * 2; |
| break; |
| default: |
| CODE_UNREACHABLE; |
| } |
| |
| switch (Range) { |
| case LL_ADC_VIN_RANGE_1V2: |
| group_shift = 0 * 6; |
| break; |
| case LL_ADC_VIN_RANGE_2V4: |
| group_shift = 1 * 6; |
| break; |
| case LL_ADC_VIN_RANGE_3V6: |
| group_shift = 2 * 6; |
| break; |
| default: |
| CODE_UNREACHABLE; |
| } |
| |
| const uint32_t shift = (group_shift + type_shift); |
| |
| MODIFY_REG(ADCx->COMP_SEL, (ADC_COMP_SEL_OFFSET_GAIN0 << shift), (Point << shift)); |
| } |
| |
| static void adc_acquire_pm_locks(void) |
| { |
| pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| if (IS_ENABLED(CONFIG_PM_S2RAM)) { |
| pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES); |
| } |
| } |
| |
| static void adc_release_pm_locks(void) |
| { |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| if (IS_ENABLED(CONFIG_PM_S2RAM)) { |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_RAM, PM_ALL_SUBSTATES); |
| } |
| } |
| |
| /** |
| * Driver private functions |
| */ |
| |
| static void configure_tempsensor_calib_point(ADC_TypeDef *adc, uint32_t calib_point) |
| { |
| uint16_t gain; |
| #if defined(CONFIG_SOC_STM32WB09XX) || defined(CONFIG_SOC_STM32WB05XX) |
| /** RM0505/RM0529 ยง12.2.1 "Temperature sensor subsystem" */ |
| gain = 0xFFF; |
| #else |
| /** RM0530 ยง12.2.2 "Temperature sensor subsystem" */ |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINPX_1V2(); |
| #endif /* CONFIG_SOC_STM32WB09XX | CONFIG_SOC_STM32WB05XX */ |
| |
| LL_ADC_ConfigureCalibPoint(adc, calib_point, gain, 0x0); |
| } |
| |
| /** |
| * @brief Obtain calibration data for specified channel type and Vinput range |
| * from engineering flash, and write it to specified calibration point |
| * |
| * @param ADCx ADC registers pointer |
| * @param Point Calibration point to configure |
| * @param Type Target channel type |
| * @param Range Target channel Vinput range |
| */ |
| static void configure_calib_point_from_flash(ADC_TypeDef *ADCx, uint32_t Point, |
| uint32_t Type, uint32_t Range) |
| { |
| int8_t offset = 0; |
| uint16_t gain = 0; |
| |
| switch (Range) { |
| case LL_ADC_VIN_RANGE_1V2: |
| switch (Type) { |
| case ADC_CHANNEL_TYPE_SINGLE_POS: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINPX_1V2(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINPX_1V2(); |
| break; |
| case ADC_CHANNEL_TYPE_SINGLE_NEG: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINMX_1V2(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINMX_1V2(); |
| break; |
| case ADC_CHANNEL_TYPE_DIFF: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINDIFF_1V2(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINDIFF_1V2(); |
| break; |
| } |
| break; |
| case LL_ADC_VIN_RANGE_2V4: |
| switch (Type) { |
| case ADC_CHANNEL_TYPE_SINGLE_POS: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINPX_2V4(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINPX_2V4(); |
| break; |
| case ADC_CHANNEL_TYPE_SINGLE_NEG: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINMX_2V4(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINMX_2V4(); |
| break; |
| case ADC_CHANNEL_TYPE_DIFF: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINDIFF_2V4(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINDIFF_2V4(); |
| break; |
| } |
| break; |
| case LL_ADC_VIN_RANGE_3V6: |
| switch (Type) { |
| case ADC_CHANNEL_TYPE_SINGLE_POS: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINPX_3V6(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINPX_3V6(); |
| break; |
| case ADC_CHANNEL_TYPE_SINGLE_NEG: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINMX_3V6(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINMX_3V6(); |
| break; |
| case ADC_CHANNEL_TYPE_DIFF: |
| gain = LL_ADC_GET_CALIB_GAIN_FOR_VINDIFF_3V6(); |
| offset = LL_ADC_GET_CALIB_OFFSET_FOR_VINDIFF_3V6(); |
| break; |
| } |
| break; |
| } |
| |
| LL_ADC_ConfigureCalibPoint(ADCx, Point, gain, offset); |
| } |
| |
| static void adc_enter_idle_mode(ADC_TypeDef *adc, const struct stm32_pclken *ana_clk) |
| { |
| const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); |
| int err; |
| |
| /* Power down the ADC */ |
| LL_ADC_Disable(adc); |
| |
| #if SMPS_MODE != STM32WB0_SMPS_MODE_OFF |
| /* Disable SMPS synchronization */ |
| LL_ADC_SMPSSyncDisable(adc); |
| #endif /* SMPS_MODE != STM32WB0_SMPS_MODE_OFF */ |
| |
| #if ADC_USE_IO_BOOSTER |
| /* Disable ADC I/O booster */ |
| LL_RCC_IOBOOST_Disable(); |
| |
| # if defined(RCC_CFGR_IOBOOSTCLKEN) |
| /* Disable ADC I/O Booster clock if present */ |
| LL_RCC_IOBOOSTCLK_Disable(); |
| # endif |
| #endif /* ADC_USE_IO_BOOSTER */ |
| |
| #if defined(ADC_CTRL_ADC_LDO_ENA) |
| /* Disable ADC voltage regulator */ |
| LL_ADC_DisableInternalRegulator(adc); |
| #endif /* ADC_CTRL_ADC_LDO_ENA */ |
| |
| /* Turn off ADC analog domain clock */ |
| err = clock_control_off(clk, (clock_control_subsys_t)ana_clk); |
| if (err < 0) { |
| LOG_WRN("failed to turn off ADC analog clock (%d)", err); |
| } |
| |
| /* Release power management locks */ |
| adc_release_pm_locks(); |
| } |
| |
| static int adc_exit_idle_mode(ADC_TypeDef *adc, const struct stm32_pclken *ana_clk) |
| { |
| const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); |
| int err; |
| |
| /* Acquire power management locks */ |
| adc_acquire_pm_locks(); |
| |
| /* Turn on ADC analog domain clock */ |
| err = clock_control_on(clk, |
| (clock_control_subsys_t)ana_clk); |
| if (err < 0) { |
| LOG_ERR("failed to turn on ADC analog clock: %d", err); |
| adc_release_pm_locks(); |
| return err; |
| } |
| |
| #if defined(ADC_CTRL_ADC_LDO_ENA) |
| /* RM0479 ยง12.6.3: bit ADC_LDO_ENA must not be set on QFN32 packages. |
| * Using an equality check with supported package types ensures that |
| * we never accidentally set the bit on an unsupported MCU. |
| */ |
| const uint32_t package_type = ll_get_package_type(); |
| |
| if (package_type == LL_UTILS_PACKAGETYPE_QFN48 |
| || package_type == LL_UTILS_PACKAGETYPE_CSP49) { |
| LL_ADC_EnableInternalRegulator(adc); |
| } |
| #endif /* ADC_CTRL_ADC_LDO_ENA */ |
| |
| #if ADC_USE_IO_BOOSTER |
| # if defined(RCC_CFGR_IOBOOSTCLKEN) |
| /* Enable ADC I/O Booster clock if needed by hardware */ |
| LL_RCC_IOBOOSTCLK_Enable(); |
| # endif |
| |
| /* Enable ADC I/O Booster */ |
| LL_RCC_IOBOOST_Enable(); |
| #endif /* ADC_USE_IO_BOOSTER*/ |
| |
| #if SMPS_MODE != STM32WB0_SMPS_MODE_OFF |
| /* RM0505 ยง6.2.2 "Peripherals clock details": |
| * To avoid SNR degradation of the ADC, |
| * SMPS and ADC clocks must be synchronous. |
| */ |
| LL_ADC_SMPSSyncEnable(adc); |
| #endif /* SMPS_MODE != STM32WB0_SMPS_MODE_OFF */ |
| |
| /* Power up the ADC */ |
| LL_ADC_Enable(adc); |
| |
| return err; |
| } |
| |
| /** |
| * @brief Schedule as many samplings as possible in a sequence |
| * then start the ADC conversion. |
| */ |
| static void schedule_and_start_adc_sequence(ADC_TypeDef *adc, struct adc_stm32wb0_data *data) |
| { |
| uint32_t remaining_unsampled = data->unsampled_channels; |
| uint32_t allocated_calib_points = 0; |
| uint32_t sequence_length = 0; |
| bool temp_sensor_scheduled = false; |
| |
| /** |
| * These tables are used to keep track of which calibration |
| * point registers are used for what type of acquisition, in |
| * order to share the same calibration point for different |
| * channels if they use compatible configurations. |
| * |
| * Initialize only the first table with invalid values; since |
| * both tables are updated at the same time, this is sufficient |
| * to know when to stop programming calibration points. |
| */ |
| uint8_t calib_pt_ch_type[NUM_CALIBRATION_POINTS] = { |
| ADC_CHANNEL_TYPE_INVALID, ADC_CHANNEL_TYPE_INVALID, |
| ADC_CHANNEL_TYPE_INVALID, ADC_CHANNEL_TYPE_INVALID |
| }; |
| uint8_t calib_pt_vin_range[NUM_CALIBRATION_POINTS]; |
| |
| /* Schedule as many channels as possible for sampling */ |
| for (uint32_t channel = 0; |
| channel < LL_ADC_CHANNEL_MAX && remaining_unsampled != 0U; |
| channel++) { |
| const uint32_t ch_bit = BIT(channel); |
| |
| if ((remaining_unsampled & ch_bit) == 0) { |
| continue; |
| } |
| |
| /* Get channel information */ |
| const uint8_t ch_type = get_channel_type(channel); |
| const uint8_t ch_vin_range = data->channel_config[channel].vinput_range; |
| |
| /* Attempt to find a compatible calibration point */ |
| uint32_t calib_pt = 0; |
| |
| for (; calib_pt < allocated_calib_points; calib_pt++) { |
| if (calib_pt_ch_type[calib_pt] == ch_type |
| && calib_pt_vin_range[calib_pt] == ch_vin_range) { |
| break; |
| } |
| } |
| |
| if (calib_pt == allocated_calib_points) { |
| /* No compatible calibration point found. |
| * If an unallocated calibration point remains, use it. |
| * Otherwise, this channel cannot be scheduled; since we must |
| * perform samplings in order, exit the scheduling loop. |
| */ |
| if (allocated_calib_points < NUM_CALIBRATION_POINTS) { |
| allocated_calib_points++; |
| } else { |
| /* Exit scheduling loop */ |
| break; |
| } |
| } |
| |
| if (channel == LL_ADC_CHANNEL_TEMPSENSOR) { |
| if (calib_pt_ch_type[calib_pt] == ADC_CHANNEL_TYPE_INVALID) { |
| /** |
| * Temperature sensor is a special channel: it must be sampled |
| * with special gain/offset instead of the calibration values found |
| * in engineering flash. For this reason, it must NOT be scheduled |
| * with any other 1.2V Vinput range, single-ended positive channel. |
| * |
| * If this check succeeds, then no such channel is scheduled, and we |
| * can add the temperature sensor to this sequence. We're sure there |
| * won't be any conflict because the temperature sensor is the last |
| * channel. Otherwise, a channel with 1.2V Vinput range has been |
| * scheduled and we must delay the temperature sensor measurement to |
| * another sequence. |
| */ |
| temp_sensor_scheduled = true; |
| } else { |
| /* Exit scheduling loop before scheduling temperature sensor */ |
| break; |
| } |
| } |
| |
| /* Ensure calibration point tables are updated. |
| * This is unneeded if the entry was already filled up, |
| * but cheaper than checking for duplicate work. |
| */ |
| calib_pt_ch_type[calib_pt] = ch_type; |
| calib_pt_vin_range[calib_pt] = ch_vin_range; |
| |
| /* Remove channel from unscheduled list */ |
| remaining_unsampled &= ~ch_bit; |
| |
| /* Add channel to sequence */ |
| ll_adc_set_conversion_channel(adc, sequence_length, channel); |
| sequence_length++; |
| |
| /* Select the calibration point to use for channel */ |
| ll_adc_set_calib_point_for_any(adc, ch_type, ch_vin_range, calib_pt); |
| |
| /* Configure the channel Vinput range selection. |
| * This must not be done for internal channels, which |
| * use a hardwired Vinput range selection instead. |
| */ |
| if (channel < LL_ADC_EXTERNAL_CHANNEL_MAX) { |
| LL_ADC_SetChannelVoltageRange(adc, channel, ch_vin_range); |
| } |
| #if !defined(CONFIG_ADC_STM32_DMA) |
| /* If DMA is not enabled, only schedule one channel at a time. |
| * Otherwise, the ADC will overflow and everything will break. |
| */ |
| __ASSERT_NO_MSG(sequence_length == 1); |
| break; |
| #endif |
| } |
| |
| /* Configure all (used) calibration points */ |
| for (int i = 0; i < NUM_CALIBRATION_POINTS; i++) { |
| uint8_t type = calib_pt_ch_type[i]; |
| uint8_t range = calib_pt_vin_range[i]; |
| |
| if (type == ADC_CHANNEL_TYPE_INVALID) { |
| break; |
| } else if ((type == ADC_CHANNEL_TYPE_SINGLE_POS) |
| && (range == LL_ADC_VIN_RANGE_1V2) |
| && temp_sensor_scheduled) { |
| /* Configure special calibration point for temperature sensor */ |
| configure_tempsensor_calib_point(adc, i); |
| } else { |
| configure_calib_point_from_flash(adc, i, type, range); |
| } |
| } |
| |
| __ASSERT_NO_MSG(sequence_length > 0); |
| |
| /* Now that scheduling is done, we can set the sequence length */ |
| LL_ADC_SetSequenceLength(adc, sequence_length); |
| |
| /* Save unsampled channels (if any) for next sequence */ |
| data->unsampled_channels = remaining_unsampled; |
| |
| #if defined(CONFIG_ADC_STM32_DMA) |
| const struct adc_stm32wb0_config *config = data->dev->config; |
| int err; |
| |
| /* Save sequence length in driver data for later usage */ |
| data->sequence_length = sequence_length; |
| |
| /* Prepare the DMA controller for ADC->memory transfers */ |
| data->dma_block_config.source_address = (uint32_t)&adc->DS_DATAOUT; |
| data->dma_block_config.dest_address = (uint32_t)data->next_sample_ptr; |
| data->dma_block_config.block_size = data->sequence_length * sizeof(uint16_t); |
| |
| err = dma_config(config->dmac, config->dma_channel, &data->dmac_config); |
| if (err < 0) { |
| LOG_ERR("%s: FAIL - dma_config returns %d", __func__, err); |
| adc_context_complete(&data->ctx, err); |
| return; |
| } |
| |
| err = dma_start(config->dmac, config->dma_channel); |
| if (err < 0) { |
| LOG_ERR("%s: FAIL - dma_start returns %d", __func__, err); |
| adc_context_complete(&data->ctx, err); |
| return; |
| } |
| #endif |
| |
| /* Start conversion sequence */ |
| LL_ADC_StartConversion(adc); |
| } |
| |
| static inline void handle_end_of_sequence(ADC_TypeDef *adc, struct adc_stm32wb0_data *data) |
| { |
| if (data->unsampled_channels != 0) { |
| /* Some channels requested for this round have |
| * not been sampled yet. Schedule and start another |
| * acquisition sequence. |
| */ |
| schedule_and_start_adc_sequence(adc, data); |
| } else { |
| /* All channels sampled: round is complete. */ |
| adc_context_on_sampling_done(&data->ctx, data->dev); |
| } |
| } |
| |
| static int initiate_read_operation(const struct device *dev, |
| const struct adc_sequence *sequence) |
| { |
| const struct adc_stm32wb0_config *config = dev->config; |
| struct adc_stm32wb0_data *data = dev->data; |
| ADC_TypeDef *adc = (ADC_TypeDef *)config->reg; |
| int err; |
| |
| err = validate_adc_sequence(sequence); |
| if (err < 0) { |
| return err; |
| } |
| |
| /* Take ADC out of idle mode before getting to work */ |
| err = adc_exit_idle_mode(adc, &config->ana_clk); |
| if (err < 0) { |
| return err; |
| } |
| |
| /* Initialize output pointers to first byte of user buffer */ |
| data->next_sample_ptr = data->round_buf_pointer = sequence->buffer; |
| |
| /* Configure resolution */ |
| LL_ADC_SetDSDataOutputWidth(adc, ds_width_from_adc_res(sequence->resolution)); |
| |
| /* Configure oversampling */ |
| LL_ADC_SetDSDataOutputRatio(adc, sequence->oversampling); |
| |
| /* Start reading using the ADC */ |
| adc_context_start_read(&data->ctx, sequence); |
| |
| return 0; |
| } |
| |
| #if !defined(CONFIG_ADC_STM32_DMA) |
| void adc_stm32wb0_isr(const struct device *dev) |
| { |
| const struct adc_stm32wb0_config *config = dev->config; |
| struct adc_stm32wb0_data *data = dev->data; |
| ADC_TypeDef *adc = config->reg; |
| |
| /* Down sampler output data available */ |
| if (LL_ADC_IsActiveFlag_EODS(adc)) { |
| /* Clear pending interrupt flag */ |
| LL_ADC_ClearFlag_EODS(adc); |
| |
| /* Write ADC data to output buffer and update pointer */ |
| *data->next_sample_ptr++ = LL_ADC_DSGetOutputData(adc); |
| } |
| |
| /* Down sampler overflow detected - return error */ |
| if (LL_ADC_IsActiveFlag_OVRDS(adc)) { |
| LL_ADC_ClearFlag_OVRDS(adc); |
| |
| LOG_ERR("ADC overflow\n"); |
| |
| adc_context_complete(&data->ctx, -EIO); |
| return; |
| } |
| |
| if (!LL_ADC_IsActiveFlag_EOS(adc)) { |
| /* ADC sequence not finished yet */ |
| return; |
| } |
| |
| /* Clear pending interrupt flag */ |
| LL_ADC_ClearFlag_EOS(adc); |
| |
| /* Execute end-of-sequence logic */ |
| handle_end_of_sequence(adc, data); |
| } |
| #else /* CONFIG_ADC_STM32_DMA */ |
| static void adc_stm32wb0_dma_callback(const struct device *dma, void *user_data, |
| uint32_t dma_channel, int dma_status) |
| { |
| struct adc_stm32wb0_data *data = user_data; |
| const struct device *dev = data->dev; |
| const struct adc_stm32wb0_config *config = dev->config; |
| ADC_TypeDef *adc = config->reg; |
| int err; |
| |
| /* N.B.: some of this code is borrowed from existing ADC driver, |
| * but may be not applicable to STM32WB0 series' ADC. |
| */ |
| if (dma_channel == config->dma_channel) { |
| if (LL_ADC_IsActiveFlag_OVRDS(adc) || (dma_status >= 0)) { |
| /* Sequence finished - update driver data accordingly */ |
| data->next_sample_ptr += data->sequence_length; |
| |
| /* Stop the DMA controller */ |
| err = dma_stop(config->dmac, config->dma_channel); |
| LOG_DBG("%s: dma_stop returns %d", __func__, err); |
| |
| LL_ADC_ClearFlag_OVRDS(adc); |
| |
| /* Execute the common end-of-sequence logic */ |
| handle_end_of_sequence(adc, data); |
| } else { /* dma_status < 0 */ |
| LOG_ERR("%s: dma error %d", __func__, dma_status); |
| LL_ADC_StopConversion(adc); |
| |
| err = dma_stop(config->dmac, config->dma_channel); |
| |
| LOG_DBG("dma_stop returns %d", err); |
| |
| adc_context_complete(&data->ctx, dma_status); |
| } |
| } else { |
| LOG_DBG("dma_channel 0x%08X != config->dma_channel 0x%08X", |
| dma_channel, config->dma_channel); |
| } |
| } |
| #endif /* !CONFIG_ADC_STM32_DMA */ |
| |
| /** |
| * adc_context API implementation |
| */ |
| static void adc_context_start_sampling(struct adc_context *ctx) |
| { |
| struct adc_stm32wb0_data *data = drv_data_from_adc_ctx(ctx); |
| const struct adc_stm32wb0_config *config = data->dev->config; |
| |
| /* Mark all channels of this round as unsampled */ |
| data->unsampled_channels = data->ctx.sequence.channels; |
| |
| /* Schedule and start first sequence of this round */ |
| schedule_and_start_adc_sequence(config->reg, data); |
| } |
| |
| static void adc_context_update_buffer_pointer( |
| struct adc_context *ctx, bool repeat_sampling) |
| { |
| struct adc_stm32wb0_data *data = drv_data_from_adc_ctx(ctx); |
| |
| if (repeat_sampling) { |
| /* Roll back output pointer to address of first sample in round */ |
| data->next_sample_ptr = data->round_buf_pointer; |
| } else /* a new round is starting: */ { |
| /* Save address of first sample in round in case we have to repeat it */ |
| data->round_buf_pointer = data->next_sample_ptr; |
| } |
| } |
| |
| static void adc_context_on_complete(struct adc_context *ctx, int status) |
| { |
| struct adc_stm32wb0_data *data = drv_data_from_adc_ctx(ctx); |
| const struct adc_stm32wb0_config *config = data->dev->config; |
| |
| ARG_UNUSED(status); |
| |
| /** |
| * All ADC operations are complete. |
| * Save power by placing ADC in idle mode. |
| */ |
| adc_enter_idle_mode(config->reg, &config->ana_clk); |
| |
| /* Prevent data corruption if something goes wrong. */ |
| data->next_sample_ptr = NULL; |
| } |
| |
| /** |
| * Driver subsystem API implementation |
| */ |
| int adc_stm32wb0_channel_setup(const struct device *dev, |
| const struct adc_channel_cfg *channel_cfg) |
| { |
| CHECKIF(dev == NULL) { return -ENODEV; } |
| CHECKIF(channel_cfg == NULL) { return -EINVAL; } |
| const bool is_diff_channel = |
| (channel_cfg->channel_id == LL_ADC_CHANNEL_VINP0_VINM0 |
| || channel_cfg->channel_id == LL_ADC_CHANNEL_VINP1_VINM1 |
| || channel_cfg->channel_id == LL_ADC_CHANNEL_VINP2_VINM2 |
| || channel_cfg->channel_id == LL_ADC_CHANNEL_VINP3_VINM3); |
| const uint8_t vin_range = vinput_range_from_adc_ref(channel_cfg->reference); |
| const uint32_t channel_id = channel_cfg->channel_id; |
| struct adc_stm32wb0_data *data = dev->data; |
| int res; |
| |
| /* Forbid reconfiguration while operation in progress */ |
| res = k_sem_take(&data->ctx.lock, K_NO_WAIT); |
| if (res < 0) { |
| return res; |
| } |
| |
| /* Validate channel configuration parameters */ |
| if (channel_cfg->gain != ADC_GAIN_1) { |
| LOG_ERR("gain unsupported by hardware"); |
| res = -ENOTSUP; |
| goto unlock_and_return; |
| } |
| |
| if (vin_range == LL_ADC_VIN_RANGE_INVALID) { |
| LOG_ERR("invalid channel voltage reference"); |
| res = -EINVAL; |
| goto unlock_and_return; |
| } |
| |
| if (channel_id >= LL_ADC_CHANNEL_MAX) { |
| LOG_ERR("invalid channel id %d", channel_cfg->channel_id); |
| res = -EINVAL; |
| goto unlock_and_return; |
| } else if (is_diff_channel != channel_cfg->differential) { |
| /* channel_cfg->differential flag does not match |
| * with the selected channel's type |
| */ |
| LOG_ERR("differential flag does not match channel type"); |
| res = -EINVAL; |
| goto unlock_and_return; |
| } |
| |
| if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { |
| LOG_ERR("acquisition time unsupported by hardware"); |
| res = -ENOTSUP; |
| goto unlock_and_return; |
| } |
| |
| /* Verify that the correct reference is selected for special channels */ |
| if (channel_id == LL_ADC_CHANNEL_VBAT && vin_range != LL_ADC_VIN_RANGE_3V6) { |
| LOG_ERR("invalid reference for Vbat channel"); |
| res = -EINVAL; |
| goto unlock_and_return; |
| } else if (channel_id == LL_ADC_CHANNEL_TEMPSENSOR && vin_range != LL_ADC_VIN_RANGE_1V2) { |
| LOG_ERR("invalid reference for temperature sensor channel"); |
| res = -EINVAL; |
| goto unlock_and_return; |
| } |
| |
| /* Save the channel configuration in driver data. |
| * Note that the only configuration option available |
| * is the ADC channel reference (= Vinput range). |
| */ |
| data->channel_config[channel_id].vinput_range = vin_range; |
| |
| unlock_and_return: |
| /* Unlock the instance after updating configuration */ |
| k_sem_give(&data->ctx.lock); |
| |
| return res; |
| } |
| |
| int adc_stm32wb0_read(const struct device *dev, |
| const struct adc_sequence *sequence) |
| { |
| CHECKIF(dev == NULL) { return -ENODEV; } |
| struct adc_stm32wb0_data *data = dev->data; |
| int err; |
| |
| adc_context_lock(&data->ctx, false, NULL); |
| |
| /* When context is locked in synchronous mode, this |
| * function blocks until the whole operation is complete. |
| */ |
| err = initiate_read_operation(dev, sequence); |
| |
| if (err >= 0) { |
| err = adc_context_wait_for_completion(&data->ctx); |
| } else { |
| adc_release_pm_locks(); |
| } |
| |
| adc_context_release(&data->ctx, err); |
| |
| return err; |
| } |
| |
| #if defined(CONFIG_ADC_ASYNC) |
| int adc_stm32wb0_read_async(const struct device *dev, |
| const struct adc_sequence *sequence, struct k_poll_signal *async) |
| { |
| CHECKIF(dev == NULL) { return -ENODEV; } |
| struct adc_stm32wb0_data *data = dev->data; |
| int err; |
| |
| adc_context_lock(&data->ctx, true, async); |
| |
| /* When context is locked in synchronous mode, this |
| * function blocks until the whole operation is complete. |
| */ |
| err = initiate_read_operation(dev, sequence); |
| if (err < 0) { |
| adc_release_pm_locks(); |
| } |
| |
| adc_context_release(&data->ctx, err); |
| |
| return err; |
| } |
| #endif /* CONFIG_ADC_ASYNC */ |
| |
| static const struct adc_driver_api api_stm32wb0_driver_api = { |
| .channel_setup = adc_stm32wb0_channel_setup, |
| .read = adc_stm32wb0_read, |
| #if defined(CONFIG_ADC_ASYNC) |
| .read_async = adc_stm32wb0_read_async, |
| #endif /* CONFIG_ADC_ASYNC */ |
| .ref_internal = 3600U /* ADC_REF_INTERNAL is mapped to Vinput 3.6V range */ |
| }; |
| |
| int adc_stm32wb0_init(const struct device *dev) |
| { |
| const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); |
| const struct adc_stm32wb0_config *config = dev->config; |
| struct adc_stm32wb0_data *data = dev->data; |
| ADC_TypeDef *adc = config->reg; |
| int err; |
| |
| if (!device_is_ready(clk)) { |
| LOG_ERR("clock control device not ready"); |
| return -ENODEV; |
| } |
| |
| /* Turn on ADC digital clock (always on) */ |
| err = clock_control_on(clk, |
| (clock_control_subsys_t)&config->dig_clk); |
| if (err < 0) { |
| LOG_ERR("failed to turn on ADC digital clock (%d)", err); |
| return err; |
| } |
| |
| /* Configure DT-provided signals when available */ |
| err = pinctrl_apply_state(config->pinctrl_cfg, PINCTRL_STATE_DEFAULT); |
| if (err < 0 && err != -ENOENT) { |
| /* ENOENT indicates no entry - should not be treated as failure */ |
| LOG_ERR("fail to apply ADC pinctrl state (%d)", err); |
| return err; |
| } |
| |
| #if defined(ADC_SUPPORT_AUDIO_FEATURES) |
| /* Configure ADC for analog sampling */ |
| LL_ADC_SetADCMode(adc, LL_ADC_OP_MODE_ADC); |
| #endif |
| |
| #if defined(PWR_CR2_ENTS) |
| /* Enable on-die temperature sensor */ |
| LL_PWR_EnableTempSens(); |
| #endif |
| |
| /* Set ADC sample rate to 1 Msps (maximum speed) */ |
| LL_ADC_SetSampleRate(adc, LL_ADC_SAMPLE_RATE_16); |
| |
| /* Keep new data on overrun, if it ever happens */ |
| LL_ADC_SetOverrunDS(adc, LL_ADC_NEW_DATA_IS_KEPT); |
| |
| #if !defined(CONFIG_ADC_STM32_DMA) |
| /* Attach ISR and enable ADC interrupt in NVIC */ |
| IRQ_CONNECT(DT_IRQN(ADC_NODE), DT_IRQ(ADC_NODE, priority), |
| adc_stm32wb0_isr, DEVICE_DT_GET(ADC_NODE), 0); |
| irq_enable(DT_IRQN(ADC_NODE)); |
| |
| /* Enable ADC interrupt after each sampling. |
| * NOTE: enabling EOS interrupt is not necessary because |
| * the EODS interrupt flag is also set to high when the |
| * EOS flag is being set to high. |
| */ |
| LL_ADC_EnableIT_EODS(adc); |
| #else |
| /* Check that DMA controller exists and is ready to be used */ |
| if (!config->dmac) { |
| LOG_ERR("no DMA assigned to ADC in DMA driver mode!"); |
| return -ENODEV; |
| } |
| |
| if (!device_is_ready(config->dmac)) { |
| LOG_ERR("DMA controller '%s' for ADC not ready", config->dmac->name); |
| return -ENODEV; |
| } |
| |
| /* Finalize DMA configuration structure in driver data */ |
| data->dmac_config.head_block = &data->dma_block_config; |
| data->dmac_config.user_data = data; |
| |
| /* Enable DMA datapath in ADC */ |
| LL_ADC_DMAModeDSEnable(adc); |
| #endif /* !CONFIG_ADC_STM32_DMA */ |
| |
| /* Unlock the ADC context to mark ADC as ready to use */ |
| adc_context_unlock_unconditionally(&data->ctx); |
| |
| /* Keep ADC powered down ("idle mode"). |
| * It will be awakened on-demand when a call to the ADC API |
| * is performed by the application. |
| */ |
| return 0; |
| } |
| |
| /** |
| * Driver power management implementation |
| */ |
| #ifdef CONFIG_PM_DEVICE |
| static int adc_stm32wb0_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| const struct adc_stm32wb0_config *config = dev->config; |
| ADC_TypeDef *adc = config->reg; |
| int res; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| return adc_stm32wb0_init(dev); |
| case PM_DEVICE_ACTION_SUSPEND: |
| adc_enter_idle_mode(adc, &config->ana_clk); |
| |
| /* Move pins to sleep state */ |
| res = pinctrl_apply_state(config->pinctrl_cfg, PINCTRL_STATE_SLEEP); |
| |
| /** |
| * -ENOENT is returned if there are no pins defined in DTS for sleep mode. |
| * This is fine and should not block PM from suspending the device. |
| * Silently ignore the error by returning 0 instead. |
| */ |
| if (res >= 0 || res == -ENOENT) { |
| return 0; |
| } else { |
| return res; |
| } |
| default: |
| return -ENOTSUP; |
| } |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| /** |
| * Driver device instantiation |
| */ |
| PINCTRL_DT_DEFINE(ADC_NODE); |
| |
| static const struct adc_stm32wb0_config adc_config = { |
| .reg = (ADC_TypeDef *)DT_REG_ADDR(ADC_NODE), |
| .pinctrl_cfg = PINCTRL_DT_DEV_CONFIG_GET(ADC_NODE), |
| .dig_clk = STM32_CLOCK_INFO(0, ADC_NODE), |
| .ana_clk = STM32_CLOCK_INFO(1, ADC_NODE), |
| #if defined(CONFIG_ADC_STM32_DMA) |
| .dmac = DEVICE_DT_GET(DT_DMAS_CTLR_BY_IDX(ADC_NODE, 0)), |
| .dma_channel = DT_DMAS_CELL_BY_IDX(ADC_NODE, 0, channel), |
| #endif |
| }; |
| |
| static struct adc_stm32wb0_data adc_data = { |
| ADC_CONTEXT_INIT_TIMER(adc_data, ctx), |
| ADC_CONTEXT_INIT_LOCK(adc_data, ctx), |
| ADC_CONTEXT_INIT_SYNC(adc_data, ctx), |
| .dev = DEVICE_DT_GET(ADC_NODE), |
| .channel_config = { |
| /* Internal channels selection is hardwired */ |
| [LL_ADC_CHANNEL_VBAT] = { |
| .vinput_range = LL_ADC_VIN_RANGE_3V6 |
| }, |
| [LL_ADC_CHANNEL_TEMPSENSOR] = { |
| .vinput_range = LL_ADC_VIN_RANGE_1V2 |
| } |
| }, |
| #if defined(CONFIG_ADC_STM32_DMA) |
| .dmac_config = { |
| .dma_slot = DT_INST_DMAS_CELL_BY_IDX(ADC_INSTANCE, 0, slot), |
| .channel_direction = STM32_DMA_CONFIG_DIRECTION( |
| STM32_DMA_CHANNEL_CONFIG_BY_IDX(ADC_INSTANCE, 0)), |
| .channel_priority = STM32_DMA_CONFIG_PRIORITY( |
| STM32_DMA_CHANNEL_CONFIG_BY_IDX(ADC_INSTANCE, 0)), |
| .source_data_size = STM32_DMA_CONFIG_PERIPHERAL_DATA_SIZE( |
| STM32_DMA_CHANNEL_CONFIG_BY_IDX(ADC_INSTANCE, 0)), |
| .dest_data_size = STM32_DMA_CONFIG_MEMORY_DATA_SIZE( |
| STM32_DMA_CHANNEL_CONFIG_BY_IDX(ADC_INSTANCE, 0)), |
| .source_burst_length = 1, /* SINGLE transfer */ |
| .dest_burst_length = 1, /* SINGLE transfer */ |
| .block_count = 1, |
| .dma_callback = adc_stm32wb0_dma_callback, |
| /* head_block and user_data are initialized at runtime */ |
| }, |
| .dma_block_config = { |
| .source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE, |
| .source_reload_en = 0, |
| .dest_addr_adj = DMA_ADDR_ADJ_INCREMENT, |
| .dest_reload_en = 0, |
| } |
| #endif |
| }; |
| |
| PM_DEVICE_DT_DEFINE(ADC_NODE, adc_stm32wb0_pm_action); |
| |
| DEVICE_DT_DEFINE(ADC_NODE, &adc_stm32wb0_init, PM_DEVICE_DT_GET(ADC_NODE), |
| &adc_data, &adc_config, POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, |
| &api_stm32wb0_driver_api); |