/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_ADC_STM32_DMA #include #include #include #include #endif #define ADC_CONTEXT_USES_KERNEL_TIMER #define ADC_CONTEXT_ENABLE_ON_COMPLETE #include "adc_context.h" #include 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 DEVICE_API(adc, 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);