/*
 * 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 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);
