// Copyright 2015-2023 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "esp32-hal-adc.h"

#if SOC_ADC_SUPPORTED
#include "esp32-hal.h"
#include "esp32-hal-periman.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_continuous.h"
#include "esp_adc/adc_cali_scheme.h"
#include "esp_heap_caps.h"

#if CONFIG_IDF_TARGET_ESP32P4 && CONFIG_ESP32P4_REV_MIN_FULL >= 300
// NOTE: These weak definitions allow successful linkage if the real efuse calibration functions are missing.
// This is a workaround for the ESP32P4 rev 3.0+, which is missing efuse calibration functions in the IDF.
__attribute__((weak)) uint32_t esp_efuse_rtc_calib_get_ver(void) {
  return 0;
}

__attribute__((weak)) uint32_t esp_efuse_rtc_calib_get_init_code(uint32_t atten, uint32_t *code) {
  if (code) {
    *code = 0;
  }
  return 0;  // 0 means success in ESP-IDF conventions
}

__attribute__((weak)) uint32_t esp_efuse_rtc_calib_get_chan_compens(uint32_t atten, uint32_t *comp) {
  if (comp) {
    *comp = 0;
  }
  return 0;
}

__attribute__((weak)) uint32_t esp_efuse_rtc_calib_get_cal_voltage(uint32_t atten, uint32_t *voltage) {
  if (voltage) {
    *voltage = 0;
  }
  return 0;
}
#endif

// ESP32-C2 does not define those two for some reason
#ifndef SOC_ADC_DIGI_RESULT_BYTES
#define SOC_ADC_DIGI_RESULT_BYTES (4)
#endif
#ifndef SOC_ADC_DIGI_DATA_BYTES_PER_CONV
#define SOC_ADC_DIGI_DATA_BYTES_PER_CONV (4)
#endif

static uint8_t __analogAttenuation = ADC_11db;
static uint8_t __analogWidth = SOC_ADC_RTC_MAX_BITWIDTH;
static uint8_t __analogReturnedWidth = SOC_ADC_RTC_MAX_BITWIDTH;

typedef struct {
  voidFuncPtr fn;
  void *arg;
} interrupt_config_t;

typedef struct {
  adc_oneshot_unit_handle_t adc_oneshot_handle;
  adc_continuous_handle_t adc_continuous_handle;
  interrupt_config_t adc_interrupt_handle;
  adc_cali_handle_t adc_cali_handle;
  uint32_t buffer_size;
  uint32_t conversion_frame_size;
} adc_handle_t;

adc_handle_t adc_handle[SOC_ADC_PERIPH_NUM];

static bool adcDetachBus(void *pin) {
  adc_channel_t adc_channel;
  adc_unit_t adc_unit;
  uint8_t used_channels = 0;

  adc_oneshot_io_to_channel((int)(pin - 1), &adc_unit, &adc_channel);
  for (uint8_t channel = 0; channel < SOC_ADC_CHANNEL_NUM(adc_unit); channel++) {
    int io_pin;
    adc_oneshot_channel_to_io(adc_unit, channel, &io_pin);
    if (perimanGetPinBusType(io_pin) == ESP32_BUS_TYPE_ADC_ONESHOT) {
      used_channels++;
    }
  }

  if (used_channels == 1) {  //only 1 channel is used
    esp_err_t err = adc_oneshot_del_unit(adc_handle[adc_unit].adc_oneshot_handle);
    if (err != ESP_OK) {
      return false;
    }
    adc_handle[adc_unit].adc_oneshot_handle = NULL;
    if (adc_handle[adc_unit].adc_cali_handle != NULL) {
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
      err = adc_cali_delete_scheme_curve_fitting(adc_handle[adc_unit].adc_cali_handle);
      if (err != ESP_OK) {
        return false;
      }
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
      err = adc_cali_delete_scheme_line_fitting(adc_handle[adc_unit].adc_cali_handle);
      if (err != ESP_OK) {
        return false;
      }
#else
      log_e("ADC Calibration scheme is not supported!");
      return false;
#endif
    }
    adc_handle[adc_unit].adc_cali_handle = NULL;
  }
  return true;
}

esp_err_t __analogChannelConfig(adc_bitwidth_t width, adc_attenuation_t atten, int8_t pin) {
  esp_err_t err = ESP_OK;
  adc_oneshot_chan_cfg_t config = {
    .bitwidth = width,
    .atten = (atten & 3),
  };
  if (pin == -1) {  //Reconfigure all used analog pins/channels
    for (int adc_unit = 0; adc_unit < SOC_ADC_PERIPH_NUM; adc_unit++) {
      if (adc_handle[adc_unit].adc_oneshot_handle != NULL) {
        for (uint8_t channel = 0; channel < SOC_ADC_CHANNEL_NUM(adc_unit); channel++) {
          int io_pin;
          adc_oneshot_channel_to_io(adc_unit, channel, &io_pin);
          if (perimanGetPinBusType(io_pin) == ESP32_BUS_TYPE_ADC_ONESHOT) {
            err = adc_oneshot_config_channel(adc_handle[adc_unit].adc_oneshot_handle, channel, &config);
            if (err != ESP_OK) {
              log_e("adc_oneshot_config_channel failed with error: %d", err);
              return err;
            }
          }
        }
        //ADC calibration reconfig only if all channels are updated
        if (adc_handle[adc_unit].adc_cali_handle != NULL) {
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
          log_d("Deleting ADC_UNIT_%d cali handle", adc_unit);
          err = adc_cali_delete_scheme_curve_fitting(adc_handle[adc_unit].adc_cali_handle);
          if (err != ESP_OK) {
            log_e("adc_cali_delete_scheme_curve_fitting failed with error: %d", err);
            return err;
          }
          adc_cali_curve_fitting_config_t cali_config = {
            .unit_id = adc_unit,
            .atten = atten,
            .bitwidth = width,
          };
          log_d("Creating ADC_UNIT_%d curve cali handle", adc_unit);
          err = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_handle[adc_unit].adc_cali_handle);
          if (err != ESP_OK) {
            log_e("adc_cali_create_scheme_curve_fitting failed with error: %d", err);
            return err;
          }
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
          log_d("Deleting ADC_UNIT_%d line cali handle", adc_unit);
          err = adc_cali_delete_scheme_line_fitting(adc_handle[adc_unit].adc_cali_handle);
          if (err != ESP_OK) {
            log_e("adc_cali_delete_scheme_line_fitting failed with error: %d", err);
            return err;
          }
          adc_cali_line_fitting_config_t cali_config = {
            .unit_id = adc_unit,
            .atten = atten,
            .bitwidth = width,
          };
          log_d("Creating ADC_UNIT_%d line cali handle", adc_unit);
          err = adc_cali_create_scheme_line_fitting(&cali_config, &adc_handle[adc_unit].adc_cali_handle);
          if (err != ESP_OK) {
            log_e("adc_cali_create_scheme_line_fitting failed with error: %d", err);
            return err;
          }
#else
          log_e("ADC Calibration scheme is not supported!");
          return ESP_ERR_NOT_SUPPORTED;
#endif
        }
      }
    }

    //make it default for next channels
    __analogWidth = width;
    __analogAttenuation = atten;
  } else {  //Reconfigure single channel
    if (perimanGetPinBusType(pin) == ESP32_BUS_TYPE_ADC_ONESHOT) {
      adc_channel_t channel;
      adc_unit_t adc_unit;

      adc_oneshot_io_to_channel(pin, &adc_unit, &channel);
      if (err != ESP_OK) {
        log_e("Pin %u is not ADC pin!", pin);
        return err;
      }
      err = adc_oneshot_config_channel(adc_handle[adc_unit].adc_oneshot_handle, channel, &config);
      if (err != ESP_OK) {
        log_e("adc_oneshot_config_channel failed with error: %d", err);
        return err;
      }
    } else {
      log_e("Pin is not configured as analog channel");
    }
  }
  return ESP_OK;
}

static inline uint16_t mapResolution(uint16_t value) {
  uint8_t from = __analogWidth;
  if (from == __analogReturnedWidth) {
    return value;
  }
  if (from > __analogReturnedWidth) {
    return value >> (from - __analogReturnedWidth);
  }
  return value << (__analogReturnedWidth - from);
}

void __analogSetAttenuation(adc_attenuation_t attenuation) {
  if (__analogChannelConfig(__analogWidth, attenuation, -1) != ESP_OK) {
    log_e("__analogChannelConfig failed!");
  }
}

#if CONFIG_IDF_TARGET_ESP32
void __analogSetWidth(uint8_t bits) {
  if (bits < SOC_ADC_RTC_MIN_BITWIDTH) {
    bits = SOC_ADC_RTC_MIN_BITWIDTH;
  } else if (bits > SOC_ADC_RTC_MAX_BITWIDTH) {
    bits = SOC_ADC_RTC_MAX_BITWIDTH;
  }
  if (__analogChannelConfig(bits, __analogAttenuation, -1) != ESP_OK) {
    log_e("__analogChannelConfig failed!");
  }
}
#endif

esp_err_t __analogInit(uint8_t pin, adc_channel_t channel, adc_unit_t adc_unit) {
  esp_err_t err = ESP_OK;
  if (adc_handle[adc_unit].adc_oneshot_handle == NULL) {
    adc_oneshot_unit_init_cfg_t init_config1 = {
      .unit_id = adc_unit,
      .ulp_mode = ADC_ULP_MODE_DISABLE,
    };
    err = adc_oneshot_new_unit(&init_config1, &adc_handle[adc_unit].adc_oneshot_handle);

    if (err != ESP_OK) {
      log_e("adc_oneshot_new_unit failed with error: %d", err);
      return err;
    }
  }
  perimanSetBusDeinit(ESP32_BUS_TYPE_ADC_ONESHOT, adcDetachBus);

  if (!perimanSetPinBus(pin, ESP32_BUS_TYPE_ADC_ONESHOT, (void *)(pin + 1), adc_unit, channel)) {
    adcDetachBus((void *)(pin + 1));
    return err;
  }

  adc_oneshot_chan_cfg_t config = {
    .bitwidth = __analogWidth,
    .atten = __analogAttenuation,
  };

  err = adc_oneshot_config_channel(adc_handle[adc_unit].adc_oneshot_handle, channel, &config);
  if (err != ESP_OK) {
    log_e("adc_oneshot_config_channel failed with error: %d", err);
    return err;
  }
  return ESP_OK;
}

void __analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation) {
  if (__analogChannelConfig(__analogWidth, attenuation, pin) != ESP_OK) {
    log_e("__analogChannelConfig failed!");
  }
}

void __analogReadResolution(uint8_t bits) {
  if (!bits || bits > 16) {
    return;
  }
  __analogReturnedWidth = bits;

#if CONFIG_IDF_TARGET_ESP32
  __analogSetWidth(bits);  // hardware analog resolution from 9 to 12
#endif
}

uint16_t __analogRead(uint8_t pin) {
  int value = 0;
  adc_channel_t channel;
  adc_unit_t adc_unit;

  esp_err_t err = ESP_OK;
  err = adc_oneshot_io_to_channel(pin, &adc_unit, &channel);
  if (err != ESP_OK) {
    log_e("Pin %u is not ADC pin!", pin);
    return value;
  }

  if (perimanGetPinBus(pin, ESP32_BUS_TYPE_ADC_ONESHOT) == NULL) {
    log_d("Calling __analogInit! pin = %u", pin);
    err = __analogInit(pin, channel, adc_unit);
    if (err != ESP_OK) {
      log_e("Analog initialization failed!");
      return value;
    }
  }

  adc_oneshot_read(adc_handle[adc_unit].adc_oneshot_handle, channel, &value);
  return mapResolution(value);
}

uint32_t __analogReadMilliVolts(uint8_t pin) {
  int value = 0;
  adc_channel_t channel;
  adc_unit_t adc_unit;
  esp_err_t err = ESP_OK;

  adc_oneshot_io_to_channel(pin, &adc_unit, &channel);
  if (err != ESP_OK) {
    log_e("Pin %u is not ADC pin!", pin);
    return value;
  }

  if (perimanGetPinBus(pin, ESP32_BUS_TYPE_ADC_ONESHOT) == NULL) {
    err = __analogInit(pin, channel, adc_unit);
    if (err != ESP_OK) {
      log_e("Analog initialization failed!");
      return value;
    }
  }

  if (adc_handle[adc_unit].adc_cali_handle == NULL) {
    log_d("Creating cali handle for ADC_%d", adc_unit);
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
    adc_cali_curve_fitting_config_t cali_config = {
      .unit_id = adc_unit,
      .atten = __analogAttenuation,
      .bitwidth = __analogWidth,
    };
    err = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_handle[adc_unit].adc_cali_handle);
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
    adc_cali_line_fitting_config_t cali_config = {
      .unit_id = adc_unit,
      .bitwidth = __analogWidth,
      .atten = __analogAttenuation,
    };
    err = adc_cali_create_scheme_line_fitting(&cali_config, &adc_handle[adc_unit].adc_cali_handle);
#else
    log_e("ADC Calibration scheme is not supported!");
    return value;
#endif
    if (err != ESP_OK) {
      log_e("adc_cali_create_scheme_x failed!");
      return value;
    }
  }

  err = adc_oneshot_get_calibrated_result(adc_handle[adc_unit].adc_oneshot_handle, adc_handle[adc_unit].adc_cali_handle, channel, &value);
  if (err != ESP_OK) {
    log_e("adc_oneshot_get_calibrated_result failed!");
    return 0;
  }
  return value;
}

extern uint16_t analogRead(uint8_t pin) __attribute__((weak, alias("__analogRead")));
extern uint32_t analogReadMilliVolts(uint8_t pin) __attribute__((weak, alias("__analogReadMilliVolts")));
extern void analogReadResolution(uint8_t bits) __attribute__((weak, alias("__analogReadResolution")));
extern void analogSetAttenuation(adc_attenuation_t attenuation) __attribute__((weak, alias("__analogSetAttenuation")));
extern void analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation) __attribute__((weak, alias("__analogSetPinAttenuation")));

#if CONFIG_IDF_TARGET_ESP32
extern void analogSetWidth(uint8_t bits) __attribute__((weak, alias("__analogSetWidth")));
#endif

/*
 *  ADC Continuous mode
 */

#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define ADC_OUTPUT_TYPE         ADC_DIGI_OUTPUT_FORMAT_TYPE1
#define ADC_GET_CHANNEL(p_data) ((p_data)->type1.channel)
#define ADC_GET_DATA(p_data)    ((p_data)->type1.data)
#else
#define ADC_OUTPUT_TYPE         ADC_DIGI_OUTPUT_FORMAT_TYPE2
#define ADC_GET_CHANNEL(p_data) ((p_data)->type2.channel)
#define ADC_GET_DATA(p_data)    ((p_data)->type2.data)
#endif

static uint8_t __adcContinuousAtten = ADC_11db;
static uint8_t __adcContinuousWidth = SOC_ADC_DIGI_MAX_BITWIDTH;

static uint8_t used_adc_channels = 0;
adc_continuous_result_t *adc_result = NULL;

static bool adcContinuousDetachBus(void *adc_unit_number) {
  adc_unit_t adc_unit = (adc_unit_t)adc_unit_number - 1;

  // Guard against double-cleanup: check if already cleaned up
  if (adc_handle[adc_unit].adc_continuous_handle == NULL) {
    return true;
  }

  // Clean up ADC driver
  esp_err_t err = adc_continuous_deinit(adc_handle[adc_unit].adc_continuous_handle);
  if (err != ESP_OK) {
    return false;
  }
  adc_handle[adc_unit].adc_continuous_handle = NULL;

  // Clean up calibration handle if exists
  if (adc_handle[adc_unit].adc_cali_handle != NULL) {
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
    err = adc_cali_delete_scheme_curve_fitting(adc_handle[adc_unit].adc_cali_handle);
    if (err != ESP_OK) {
      return false;
    }
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
    err = adc_cali_delete_scheme_line_fitting(adc_handle[adc_unit].adc_cali_handle);
    if (err != ESP_OK) {
      return false;
    }
#else
    log_e("ADC Calibration scheme is not supported!");
    return false;
#endif
    adc_handle[adc_unit].adc_cali_handle = NULL;
  }

  // Don't call perimanClearPinBus() here - the peripheral manager already handles it.
  // This callback is only responsible for cleaning up the IDF's ADC driver and calibration handles.
  // It does NOT free the adc_result buffer. The caller is responsible for freeing adc_result.
  return true;
}

bool IRAM_ATTR adcFnWrapper(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *args) {
  interrupt_config_t *isr = (interrupt_config_t *)args;
  //Check if edata->size matches conversion_frame_size, else just return from ISR
  if (edata->size == adc_handle[0].conversion_frame_size) {
    if (isr->fn) {
      if (isr->arg) {
        ((voidFuncPtrArg)isr->fn)(isr->arg);
      } else {
        isr->fn();
      }
    }
  }
  return false;
}

esp_err_t __analogContinuousInit(adc_channel_t *channel, uint8_t channel_num, adc_unit_t adc_unit, uint32_t sampling_freq_hz) {
  //Create new ADC continuous handle
  adc_continuous_handle_cfg_t adc_config = {
    .max_store_buf_size = adc_handle[adc_unit].buffer_size,
    .conv_frame_size = adc_handle[adc_unit].conversion_frame_size,
  };

  esp_err_t err = adc_continuous_new_handle(&adc_config, &adc_handle[adc_unit].adc_continuous_handle);
  if (err != ESP_OK) {
    log_e("adc_continuous_new_handle failed with error: %d", err);
    return ESP_FAIL;
  }

  //Configure adc pins
  adc_continuous_config_t dig_cfg = {
    .sample_freq_hz = sampling_freq_hz,
    .conv_mode = ADC_CONV_SINGLE_UNIT_1,
    .format = ADC_OUTPUT_TYPE,
  };
  adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};
  dig_cfg.pattern_num = channel_num;
  for (int i = 0; i < channel_num; i++) {
    adc_pattern[i].atten = __adcContinuousAtten;
    adc_pattern[i].channel = channel[i];
    adc_pattern[i].unit = ADC_UNIT_1;
    adc_pattern[i].bit_width = __adcContinuousWidth;
  }
  dig_cfg.adc_pattern = adc_pattern;
  err = adc_continuous_config(adc_handle[adc_unit].adc_continuous_handle, &dig_cfg);

  if (err != ESP_OK) {
    log_e("adc_continuous_config failed with error: %d", err);
    return ESP_FAIL;
  }

  used_adc_channels = channel_num;
  return ESP_OK;
}

bool analogContinuous(const uint8_t pins[], size_t pins_count, uint32_t conversions_per_pin, uint32_t sampling_freq_hz, void (*userFunc)(void)) {
  adc_channel_t channel[pins_count];
  adc_unit_t adc_unit = ADC_UNIT_1;
  esp_err_t err = ESP_OK;

  //Convert pins to channels and check if all are ADC1s unit
  for (int i = 0; i < pins_count; i++) {
    err = adc_continuous_io_to_channel(pins[i], &adc_unit, &channel[i]);
    if (err != ESP_OK) {
      log_e("Pin %u is not ADC pin!", pins[i]);
      return false;
    }
    if (adc_unit != 0) {
      log_e("Only ADC1 pins are supported in continuous mode!");
      return false;
    }
  }

  //Check if Oneshot and Continuous handle exists
  if (adc_handle[adc_unit].adc_oneshot_handle != NULL) {
    log_e("ADC%d is running in oneshot mode. Aborting.", adc_unit + 1);
    return false;
  }
  if (adc_handle[adc_unit].adc_continuous_handle != NULL) {
    log_e("ADC%d continuous is already initialized. To reconfigure call analogContinuousDeinit() first.", adc_unit + 1);
    return false;
  }

  //Check sampling frequency
  if ((sampling_freq_hz < SOC_ADC_SAMPLE_FREQ_THRES_LOW) || (sampling_freq_hz > SOC_ADC_SAMPLE_FREQ_THRES_HIGH)) {
    log_e("Sampling frequency is out of range. Supported sampling frequencies are %d - %d", SOC_ADC_SAMPLE_FREQ_THRES_LOW, SOC_ADC_SAMPLE_FREQ_THRES_HIGH);
    return false;
  }

  //Set periman deinit function and reset all pins to init state.
  perimanSetBusDeinit(ESP32_BUS_TYPE_ADC_CONT, adcContinuousDetachBus);
  for (int j = 0; j < pins_count; j++) {
    if (!perimanClearPinBus(pins[j])) {
      return false;
    }
  }

  //Set conversion frame and buffer size (conversion frame must be in multiples of SOC_ADC_DIGI_DATA_BYTES_PER_CONV)
  adc_handle[adc_unit].conversion_frame_size = conversions_per_pin * pins_count * SOC_ADC_DIGI_RESULT_BYTES;

#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
  uint8_t calc_multiple = adc_handle[adc_unit].conversion_frame_size % SOC_ADC_DIGI_DATA_BYTES_PER_CONV;
  if (calc_multiple != 0) {
    adc_handle[adc_unit].conversion_frame_size = (adc_handle[adc_unit].conversion_frame_size + calc_multiple);
  }
#endif

#if CONFIG_IDF_TARGET_ESP32P4
  // Align conversion frame size to cache line size (required for DMA on targets with cache)
  uint32_t alignment_remainder = adc_handle[adc_unit].conversion_frame_size % CONFIG_CACHE_L1_CACHE_LINE_SIZE;
  if (alignment_remainder != 0) {
    adc_handle[adc_unit].conversion_frame_size += (CONFIG_CACHE_L1_CACHE_LINE_SIZE - alignment_remainder);
  }
#endif

  adc_handle[adc_unit].buffer_size = adc_handle[adc_unit].conversion_frame_size * 2;

  //Conversion frame size buffer cant be bigger than 4092 bytes
  if (adc_handle[adc_unit].conversion_frame_size > 4092) {
    log_e("Buffers are too big. Please set lower conversions per pin.");
    return false;
  }

  //Initialize continuous handle and pins
  err = __analogContinuousInit(channel, sizeof(channel) / sizeof(adc_channel_t), adc_unit, sampling_freq_hz);
  if (err != ESP_OK) {
    log_e("Analog initialization failed!");
    return false;
  }

  //Setup callbacks for complete event
  adc_continuous_evt_cbs_t cbs = {
    .on_conv_done = adcFnWrapper,
    //.on_pool_ovf can be used in future
  };
  adc_handle[adc_unit].adc_interrupt_handle.fn = (voidFuncPtr)userFunc;
  err = adc_continuous_register_event_callbacks(adc_handle[adc_unit].adc_continuous_handle, &cbs, &adc_handle[adc_unit].adc_interrupt_handle);
  if (err != ESP_OK) {
    log_e("adc_continuous_register_event_callbacks failed!");
    return false;
  }

  //Allocate and prepare result structure for adc readings
  adc_result = malloc(pins_count * sizeof(adc_continuous_result_t));
  for (int k = 0; k < pins_count; k++) {
    adc_result[k].pin = pins[k];
    adc_result[k].channel = channel[k];
  }

  //Initialize ADC calibration handle
  if (adc_handle[adc_unit].adc_cali_handle == NULL) {
    log_d("Creating cali handle for ADC_%d", adc_unit);
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
    adc_cali_curve_fitting_config_t cali_config = {
      .unit_id = adc_unit,
      .atten = __adcContinuousAtten,
      .bitwidth = __adcContinuousWidth,
    };
    err = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_handle[adc_unit].adc_cali_handle);
#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
    adc_cali_line_fitting_config_t cali_config = {
      .unit_id = adc_unit,
      .bitwidth = __adcContinuousWidth,
      .atten = __adcContinuousAtten,
    };
    err = adc_cali_create_scheme_line_fitting(&cali_config, &adc_handle[adc_unit].adc_cali_handle);
#else
    log_e("ADC Calibration scheme is not supported!");
    return false;
#endif
    if (err != ESP_OK) {
      log_e("adc_cali_create_scheme_x failed!");
      return false;
    }
  }

  for (int k = 0; k < pins_count; k++) {
    if (!perimanSetPinBus(pins[k], ESP32_BUS_TYPE_ADC_CONT, (void *)(adc_unit + 1), adc_unit, channel[k])) {
      log_e("perimanSetPinBus to ADC Continuous failed!");
      adcContinuousDetachBus((void *)(adc_unit + 1));
      return false;
    }
  }

  return true;
}

bool analogContinuousRead(adc_continuous_result_t **buffer, uint32_t timeout_ms) {
  if (adc_handle[ADC_UNIT_1].adc_continuous_handle != NULL) {
    uint32_t bytes_read = 0;
    uint32_t read_raw[used_adc_channels];
    uint32_t read_count[used_adc_channels];

    // Allocate DMA buffer with cache line alignment (required for ESP32-P4 and other targets with cache)
    size_t buffer_size = adc_handle[ADC_UNIT_1].conversion_frame_size;
#if CONFIG_IDF_TARGET_ESP32P4
    uint8_t *adc_read = (uint8_t *)heap_caps_aligned_alloc(CONFIG_CACHE_L1_CACHE_LINE_SIZE, buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
#else
    uint8_t *adc_read = (uint8_t *)heap_caps_malloc(buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
#endif
    if (adc_read == NULL) {
      log_e("Failed to allocate DMA buffer");
      *buffer = NULL;
      return false;
    }

    memset(adc_read, 0xcc, buffer_size);
    memset(read_raw, 0, sizeof(read_raw));
    memset(read_count, 0, sizeof(read_count));

    esp_err_t err = adc_continuous_read(adc_handle[ADC_UNIT_1].adc_continuous_handle, adc_read, adc_handle[0].conversion_frame_size, &bytes_read, timeout_ms);
    if (err != ESP_OK) {
      if (err == ESP_ERR_TIMEOUT) {
        log_e("Reading data failed: No data, increase timeout");
      } else {
        log_e("Reading data failed with error: %X", err);
      }
      free(adc_read);
      adc_read = NULL;
      *buffer = NULL;
      return false;
    }

    for (int i = 0; i < bytes_read; i += SOC_ADC_DIGI_RESULT_BYTES) {
      adc_digi_output_data_t *p = (adc_digi_output_data_t *)&adc_read[i];
      uint32_t chan_num = ADC_GET_CHANNEL(p);
      uint32_t data = ADC_GET_DATA(p);

      /* Check the channel number validation, the data is invalid if the channel num exceed the maximum channel */
      if (chan_num >= SOC_ADC_CHANNEL_NUM(0)) {
        log_e("Invalid data [%" PRIu32 "_%" PRIu32 "]", chan_num, data);
        *buffer = NULL;
        return false;
      }
      if (data >= (1 << SOC_ADC_DIGI_MAX_BITWIDTH)) {
        data = 0;
        log_e("Invalid data");
      }

      for (int j = 0; j < used_adc_channels; j++) {
        if (adc_result[j].channel == chan_num) {
          read_raw[j] += data;
          read_count[j] += 1;
          break;
        }
      }
    }

    for (int j = 0; j < used_adc_channels; j++) {
      if (read_count[j] != 0) {
        adc_result[j].avg_read_raw = read_raw[j] / read_count[j];
        adc_cali_raw_to_voltage(adc_handle[ADC_UNIT_1].adc_cali_handle, adc_result[j].avg_read_raw, &adc_result[j].avg_read_mvolts);
      } else {
        log_w("No data read for pin %u", adc_result[j].pin);
      }
    }

    free(adc_read);
    adc_read = NULL;
    *buffer = adc_result;
    return true;

  } else {
    log_e("ADC Continuous is not initialized!");
    return false;
  }
}

bool analogContinuousStart() {
  if (adc_handle[ADC_UNIT_1].adc_continuous_handle != NULL) {
    if (adc_continuous_start(adc_handle[ADC_UNIT_1].adc_continuous_handle) == ESP_OK) {
      return true;
    }
  } else {
    log_e("ADC Continuous is not initialized!");
  }
  return false;
}

bool analogContinuousStop() {
  if (adc_handle[ADC_UNIT_1].adc_continuous_handle != NULL) {
    if (adc_continuous_stop(adc_handle[ADC_UNIT_1].adc_continuous_handle) == ESP_OK) {
      return true;
    }
  } else {
    log_e("ADC Continuous is not initialized!");
  }
  return false;
}

bool analogContinuousDeinit() {
  if (adc_handle[ADC_UNIT_1].adc_continuous_handle == NULL) {
    log_i("ADC Continuous was not initialized");
    return true;
  }

  // Clear all used pins from peripheral manager
  // This will trigger adcContinuousDetachBus() callback which cleans up the ADC driver
  for (uint8_t channel = 0; channel < SOC_ADC_CHANNEL_NUM(ADC_UNIT_1); channel++) {
    int io_pin;
    adc_oneshot_channel_to_io(ADC_UNIT_1, channel, &io_pin);
    if (perimanGetPinBusType(io_pin) == ESP32_BUS_TYPE_ADC_CONT) {
      if (!perimanClearPinBus(io_pin)) {
        return false;
      }
    }
  }

  // Free the result buffer (callback doesn't do this)
  if (adc_result != NULL) {
    free(adc_result);
    adc_result = NULL;
  }

  return true;
}

void analogContinuousSetAtten(adc_attenuation_t attenuation) {
  __adcContinuousAtten = attenuation;
}

void analogContinuousSetWidth(uint8_t bits) {
  if ((bits < SOC_ADC_DIGI_MIN_BITWIDTH) || (bits > SOC_ADC_DIGI_MAX_BITWIDTH)) {
    log_e("Selected width cannot be set. Range is from %u to %u", SOC_ADC_DIGI_MIN_BITWIDTH, SOC_ADC_DIGI_MAX_BITWIDTH);
    return;
  }
  __adcContinuousWidth = bits;
}

#endif
