// Copyright 2024 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 "soc/soc_caps.h"
#include <inttypes.h>

#if SOC_RMT_SUPPORTED
#include "esp32-hal.h"
#include "driver/gpio.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "hal/rmt_ll.h"

#include "esp32-hal-rmt.h"
#include "esp32-hal-periman.h"
#include "esp_idf_version.h"

// Arduino Task Handle indicates if the Arduino Task has been started already
extern TaskHandle_t loopTaskHandle;

// RMT Events
#define RMT_FLAG_RX_DONE (1)
#define RMT_FLAG_TX_DONE (2)

/**
   Internal macros
*/

#if CONFIG_DISABLE_HAL_LOCKS
#define RMT_MUTEX_LOCK(busptr)
#define RMT_MUTEX_UNLOCK(busptr)
#else
#define RMT_MUTEX_LOCK(busptr) \
  do {                         \
  } while (xSemaphoreTake(busptr->g_rmt_objlocks, portMAX_DELAY) != pdPASS)
#define RMT_MUTEX_UNLOCK(busptr) xSemaphoreGive(busptr->g_rmt_objlocks)
#endif /* CONFIG_DISABLE_HAL_LOCKS */

/**
   Typedefs for internal structures, enums
*/

struct rmt_obj_s {
  // general RMT information
  rmt_channel_handle_t rmt_channel_h;       // IDF RMT channel handler
  rmt_encoder_handle_t rmt_copy_encoder_h;  // RMT simple copy encoder handle

  uint32_t signal_range_min_ns;  // RX Filter data - Low Pass pulse width
  uint32_t signal_range_max_ns;  // RX idle time that defines end of reading

  EventGroupHandle_t rmt_events;   // read/write done event RMT callback handle
  bool rmt_ch_is_looping;          // Is this RMT TX Channel in LOOPING MODE?
  size_t *num_symbols_read;        // Pointer to the number of RMT symbol read by IDF RMT RX Done
  rmt_reserve_memsize_t mem_size;  // RMT Memory size
  uint32_t frequency_Hz;           // RMT Frequency
  uint8_t rmt_EOT_Level;           // RMT End of Transmission Level - default is LOW

#if !CONFIG_DISABLE_HAL_LOCKS
  SemaphoreHandle_t g_rmt_objlocks;  // Channel Semaphore Lock
#endif                               /* CONFIG_DISABLE_HAL_LOCKS */
};

typedef struct rmt_obj_s *rmt_bus_handle_t;

/**
   Internal variables used in RMT API
*/
static SemaphoreHandle_t g_rmt_block_lock = NULL;

/**
   Internal method (private) declarations
*/

// This is called from an IDF ISR code, therefore this code is part of an ISR
static bool _rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *data, void *args) {
  BaseType_t high_task_wakeup = pdFALSE;
  rmt_bus_handle_t bus = (rmt_bus_handle_t)args;
  // sets the returning number of RMT symbols (32 bits) effectively read
  *bus->num_symbols_read = data->num_symbols;
  // set RX event group and signal the received RMT symbols of that channel
  xEventGroupSetBitsFromISR(bus->rmt_events, RMT_FLAG_RX_DONE, &high_task_wakeup);
  // A "need to yield" is returned in order to execute portYIELD_FROM_ISR() in the main IDF RX ISR
  return high_task_wakeup == pdTRUE;
}

// This is called from an IDF ISR code, therefore this code is part of an ISR
static bool _rmt_tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *data, void *args) {
  BaseType_t high_task_wakeup = pdFALSE;
  rmt_bus_handle_t bus = (rmt_bus_handle_t)args;
  // set RX event group and signal the received RMT symbols of that channel
  xEventGroupSetBitsFromISR(bus->rmt_events, RMT_FLAG_TX_DONE, &high_task_wakeup);
  // A "need to yield" is returned in order to execute portYIELD_FROM_ISR() in the main IDF RX ISR
  return high_task_wakeup == pdTRUE;
}

// This function must be called only after checking the pin and its bus with _rmtGetBus()
static bool _rmtCheckDirection(uint8_t gpio_num, rmt_ch_dir_t rmt_dir, const char *labelFunc) {
  // gets bus RMT direction from the Peripheral Manager information
  rmt_ch_dir_t bus_rmt_dir = perimanGetPinBusType(gpio_num) == ESP32_BUS_TYPE_RMT_TX ? RMT_TX_MODE : RMT_RX_MODE;

  if (bus_rmt_dir == rmt_dir) {  // matches expected RX/TX channel
    return true;
  }

  // print error message
  if (rmt_dir == RMT_RX_MODE) {
    log_w("==>%s():Channel set as TX instead of RX.", labelFunc);
  } else {
    log_w("==>%s():Channel set as RX instead of TX.", labelFunc);
  }
  return false;  // mismatched
}

static rmt_bus_handle_t _rmtGetBus(int pin, const char *labelFunc) {
  // Is pin RX or TX? Let's find it out
  peripheral_bus_type_t rmt_bus_type = perimanGetPinBusType(pin);
  if (rmt_bus_type != ESP32_BUS_TYPE_RMT_TX && rmt_bus_type != ESP32_BUS_TYPE_RMT_RX) {
    log_e("==>%s():GPIO %d is not attached to an RMT channel.", labelFunc, pin);
    return NULL;
  }

  return (rmt_bus_handle_t)perimanGetPinBus(pin, rmt_bus_type);
}

// Peripheral Manager detach callback
static bool _rmtDetachBus(void *busptr) {
  // sanity check - it should never happen
  assert(busptr && "_rmtDetachBus bus NULL pointer.");

  bool retCode = true;
  rmt_bus_handle_t bus = (rmt_bus_handle_t)busptr;
  log_v("Detaching RMT GPIO Bus");

  // lock it
  while (xSemaphoreTake(g_rmt_block_lock, portMAX_DELAY) != pdPASS) {}

  // free Event Group
  if (bus->rmt_events != NULL) {
    vEventGroupDelete(bus->rmt_events);
    bus->rmt_events = NULL;
  }
  // deallocate the channel encoder
  if (bus->rmt_copy_encoder_h != NULL) {
    if (ESP_OK != rmt_del_encoder(bus->rmt_copy_encoder_h)) {
      log_w("RMT Encoder Deletion has failed.");
      retCode = false;
    }
  }
  // disable and deallocate RMT channel
  if (bus->rmt_channel_h != NULL) {
    // force stopping rmt TX/RX processing and unlock Power Management (APB Freq)
    rmt_disable(bus->rmt_channel_h);
    if (ESP_OK != rmt_del_channel(bus->rmt_channel_h)) {
      log_w("RMT Channel Deletion has failed.");
      retCode = false;
    }
  }
#if !CONFIG_DISABLE_HAL_LOCKS
  // deallocate channel semaphore
  if (bus->g_rmt_objlocks != NULL) {
    vSemaphoreDelete(bus->g_rmt_objlocks);
  }
#endif
  // free the allocated bus data structure
  free(bus);

  // release the mutex
  xSemaphoreGive(g_rmt_block_lock);
  return retCode;
}

/**
   Public method definitions
*/

bool rmtSetEOT(int pin, uint8_t EOT_Level) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }
  if (!_rmtCheckDirection(pin, RMT_TX_MODE, __FUNCTION__)) {
    return false;
  }

  bus->rmt_EOT_Level = EOT_Level > 0 ? 1 : 0;
  return true;
}

bool rmtSetCarrier(int pin, bool carrier_en, bool carrier_level, uint32_t frequency_Hz, float duty_percent) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }

  if (duty_percent > 1) {
    log_w("GPIO %d - RMT Carrier must be a float percentage from 0 to 1. Setting to 50%.", pin);
    duty_percent = 0.5;
  }
  rmt_carrier_config_t carrier_cfg;
  memset((void *)&carrier_cfg, 0, sizeof(rmt_carrier_config_t));
  carrier_cfg.duty_cycle = duty_percent;                     // duty cycle
  carrier_cfg.frequency_hz = carrier_en ? frequency_Hz : 0;  // carrier frequency in Hz
  carrier_cfg.flags.polarity_active_low = carrier_level;     // carrier modulation polarity level

  bool retCode = true;
  RMT_MUTEX_LOCK(bus);
  // modulate carrier to TX channel
  if (ESP_OK != rmt_apply_carrier(bus->rmt_channel_h, &carrier_cfg)) {
    log_w("GPIO %d - Error applying RMT carrier.", pin);
    retCode = false;
  }
  RMT_MUTEX_UNLOCK(bus);

  return retCode;
}

bool rmtSetRxMinThreshold(int pin, uint8_t filter_pulse_ticks) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }

  if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) {
    return false;
  }

  uint32_t filter_pulse_ns = (1000000000 / bus->frequency_Hz) * filter_pulse_ticks;
  // RMT_LL_MAX_FILTER_VALUE is 255 for ESP32, S2, S3, C3, C6 and H2;
  // filter_pulse_ticks is 8 bits, thus it will not exceed 255
#if 0  // for the future, in case some other SoC has different limit
  if (filter_pulse_ticks > RMT_LL_MAX_FILTER_VALUE) {
    log_e("filter_pulse_ticks is too big. Max = %d", RMT_LL_MAX_FILTER_VALUE);
    return false;
  }
#endif

  RMT_MUTEX_LOCK(bus);
  bus->signal_range_min_ns = filter_pulse_ns;  // set zero to disable it
  RMT_MUTEX_UNLOCK(bus);
  return true;
}

bool rmtSetRxMaxThreshold(int pin, uint16_t idle_thres_ticks) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }

  if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) {
    return false;
  }

  uint32_t idle_thres_ns = (1000000000 / bus->frequency_Hz) * idle_thres_ticks;
  // RMT_LL_MAX_IDLE_VALUE is 65535 for ESP32,S2 and 32767 for S3, C3, C6 and H2
#if RMT_LL_MAX_IDLE_VALUE < 65535  // idle_thres_ticks is 16 bits anyway - save some bytes
  if (idle_thres_ticks > RMT_LL_MAX_IDLE_VALUE) {
    log_e("idle_thres_ticks is too big. Max = %u", RMT_LL_MAX_IDLE_VALUE);
    return false;
  }
#endif

  RMT_MUTEX_LOCK(bus);
  bus->signal_range_max_ns = idle_thres_ns;
  RMT_MUTEX_UNLOCK(bus);
  return true;
}

bool rmtDeinit(int pin) {
  log_v("Deiniting RMT GPIO %d", pin);
  if (_rmtGetBus(pin, __FUNCTION__) != NULL) {
    // release all allocated data
    return perimanClearPinBus(pin);
  }
  log_e("GPIO %d - No RMT channel associated.", pin);
  return false;
}

static bool _rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, bool blocking, uint32_t loop, uint32_t timeout_ms) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }
  if (!_rmtCheckDirection(pin, RMT_TX_MODE, __FUNCTION__)) {
    return false;
  }
  bool loopCancel = false;  // user wants to cancel the writing loop mode
  if (data == NULL || num_rmt_symbols == 0) {
    if (!loop) {
      log_w("GPIO %d - RMT Write Data NULL pointer or size is zero.", pin);
      return false;
    } else {
      loopCancel = true;
    }
  }

#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
  log_v(
    "GPIO: %d - Request: %lu RMT Symbols - %s - Timeout: %" PRIu32, pin, (unsigned long)num_rmt_symbols, blocking ? "Blocking" : "Non-Blocking", timeout_ms
  );
  // loop parameter semantics:
  //   loop == 0: no looping (single transmission)
  //   loop == 1: infinite looping
  //   loop > 1: transmit the data 'loop' times
  {
    char buf[17];  // placeholder for up to maximum uint32_t value (4294967295) = 10 digits + " times" (6 chars) + null terminator (17 bytes)
    snprintf(buf, sizeof(buf), "%" PRIu32 " times", loop);
    log_v(
      "GPIO: %d - Currently in Loop Mode: [%s] | Loop Request: [%s], LoopCancel: [%s]", pin, bus->rmt_ch_is_looping ? "YES" : "NO",
      loop == 0 ? "NO" : (loop == 1 ? "FOREVER" : buf), loopCancel ? "YES" : "NO"
    );
  }
#endif

  if ((xEventGroupGetBits(bus->rmt_events) & RMT_FLAG_TX_DONE) == 0) {
    log_v("GPIO %d - RMT Write still pending to be completed.", pin);
    return false;
  }

  rmt_transmit_config_t transmit_cfg;  // loop mode disabled
  memset((void *)&transmit_cfg, 0, sizeof(rmt_transmit_config_t));
  bool retCode = true;

  RMT_MUTEX_LOCK(bus);
  // wants to start in writing or looping over a previous looping --> resets the channel
  if (bus->rmt_ch_is_looping == true) {
    // must force stopping a previous loop transmission first
    rmt_disable(bus->rmt_channel_h);
    // enable it again for looping or writing
    rmt_enable(bus->rmt_channel_h);
    bus->rmt_ch_is_looping = false;  // not looping anymore
  }
  // sets the End of Transmission level to HIGH if the user has requested so
  if (bus->rmt_EOT_Level) {
    transmit_cfg.flags.eot_level = 1;  // EOT is HIGH
  }
  if (loopCancel) {
    // just resets and releases the channel, maybe, already done above, then exits
    bus->rmt_ch_is_looping = false;
  } else {  // new writing | looping request
    // looping | Writing over a previous looping state is valid
    if (loop > 0) {
      transmit_cfg.loop_count = (loop == 1) ? -1 : loop;
      // keeps RMT_FLAG_TX_DONE set - it never changes
    } else {
      // looping mode never sets this flag (IDF 5.1) in the callback
      xEventGroupClearBits(bus->rmt_events, RMT_FLAG_TX_DONE);
    }
    // transmits just once or looping data
    if (ESP_OK != rmt_transmit(bus->rmt_channel_h, bus->rmt_copy_encoder_h, (const void *)data, num_rmt_symbols * sizeof(rmt_data_t), &transmit_cfg)) {
      retCode = false;
      log_w("GPIO %d - RMT Transmission failed.", pin);
    } else {  // transmit OK
      if (loop > 0) {
        // rmt_ch_is_looping is used as a flag to indicate that RMT is in looping execution in order to
        // be canceled whenever a new _rmtWrite() is executed while it is looping
        bus->rmt_ch_is_looping = true;
      } else {
        if (blocking) {
          // wait for transmission confirmation | timeout
          retCode = (xEventGroupWaitBits(bus->rmt_events, RMT_FLAG_TX_DONE, pdFALSE /* do not clear on exit */, pdFALSE /* wait for all bits */, timeout_ms)
                     & RMT_FLAG_TX_DONE)
                    != 0;
        }
      }
    }
  }
  RMT_MUTEX_UNLOCK(bus);
  return retCode;
}

static bool _rmtRead(int pin, rmt_data_t *data, size_t *num_rmt_symbols, bool waitForData, uint32_t timeout_ms) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }
  if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) {
    return false;
  }
  if (data == NULL || num_rmt_symbols == NULL) {
    log_w("GPIO %d - RMT Read Data and/or Size NULL pointer.", pin);
    return false;
  }
  log_v(
    "GPIO: %d - Request: %lu RMT Symbols - %s - Timeout: %" PRIu32, pin, (unsigned long)*num_rmt_symbols, waitForData ? "Blocking" : "Non-Blocking", timeout_ms
  );
  bool retCode = true;
  RMT_MUTEX_LOCK(bus);

  // request reading RMT Channel Data
  rmt_receive_config_t receive_config;
  memset((void *)&receive_config, 0, sizeof(rmt_receive_config_t));
  receive_config.signal_range_min_ns = bus->signal_range_min_ns;
  receive_config.signal_range_max_ns = bus->signal_range_max_ns;

  xEventGroupClearBits(bus->rmt_events, RMT_FLAG_RX_DONE);
  bus->num_symbols_read = num_rmt_symbols;

  if (waitForData) {
    // resets the reading channel to start fresh
    rmt_disable(bus->rmt_channel_h);
    rmt_enable(bus->rmt_channel_h);
  }

  rmt_receive(bus->rmt_channel_h, data, *num_rmt_symbols * sizeof(rmt_data_t), &receive_config);
  // wait for data if requested
  if (waitForData) {
    retCode = (xEventGroupWaitBits(bus->rmt_events, RMT_FLAG_RX_DONE, pdFALSE /* do not clear on exit */, pdFALSE /* wait for all bits */, timeout_ms)
               & RMT_FLAG_RX_DONE)
              != 0;
  }

  RMT_MUTEX_UNLOCK(bus);
  return retCode;
}

bool rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, uint32_t timeout_ms) {
  return _rmtWrite(pin, data, num_rmt_symbols, true /*blocks*/, 0 /*looping*/, timeout_ms);
}

bool rmtWriteAsync(int pin, rmt_data_t *data, size_t num_rmt_symbols) {
  return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, 0 /*looping*/, 0 /*N/A*/);
}

bool rmtWriteLooping(int pin, rmt_data_t *data, size_t num_rmt_symbols) {
  return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, 1 /*looping*/, 0 /*N/A*/);
}

// Same as rmtWriteLooping(...) but it transmits the data a fixed number of times ("loop_count").
// loop_count == 0 is invalid (no transmission); loop_count == 1 transmits once (no looping); loop_count > 1 transmits the data repeatedly (looping).
bool rmtWriteRepeated(int pin, rmt_data_t *data, size_t num_rmt_symbols, uint32_t loop_count) {
  if (loop_count == 0) {
    log_e("RMT TX GPIO %d : Invalid loop_count (%" PRIu32 "). Must be at least 1.", pin, loop_count);
    return false;
  }
  if (loop_count == 1) {
    // send the RMT symbols once using non-blocking write (single non-looping transmission)
    return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, 0 /*looping*/, 0 /*N/A*/);
  } else {
    // write the RMT symbols for loop_count times
#if SOC_RMT_SUPPORT_TX_LOOP_COUNT
    return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, loop_count /*looping*/, 0 /*N/A*/);
#else
    log_e("RMT TX GPIO %d : Loop Count is not supported. Writing failed.", pin);
    return false;
#endif
  }
}

bool rmtTransmitCompleted(int pin) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }
  if (!_rmtCheckDirection(pin, RMT_TX_MODE, __FUNCTION__)) {
    return false;
  }

  bool retCode = true;
  RMT_MUTEX_LOCK(bus);
  retCode = (xEventGroupGetBits(bus->rmt_events) & RMT_FLAG_TX_DONE) != 0;
  RMT_MUTEX_UNLOCK(bus);
  return retCode;
}

bool rmtRead(int pin, rmt_data_t *data, size_t *num_rmt_symbols, uint32_t timeout_ms) {
  return _rmtRead(pin, data, num_rmt_symbols, true /* blocking */, timeout_ms);
}

bool rmtReadAsync(int pin, rmt_data_t *data, size_t *num_rmt_symbols) {
  return _rmtRead(pin, data, num_rmt_symbols, false /* non-blocking */, 0 /* N/A */);
}

bool rmtReceiveCompleted(int pin) {
  rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
  if (bus == NULL) {
    return false;
  }
  if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) {
    return false;
  }

  bool retCode = true;
  RMT_MUTEX_LOCK(bus);
  retCode = (xEventGroupGetBits(bus->rmt_events) & RMT_FLAG_RX_DONE) != 0;
  RMT_MUTEX_UNLOCK(bus);
  return retCode;
}

bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t mem_size, uint32_t frequency_Hz) {
  log_v(
    "GPIO %d - %s - MemSize[%u] - Freq=%" PRIu32 "Hz", pin, channel_direction == RMT_RX_MODE ? "RX MODE" : "TX MODE", mem_size * RMT_SYMBOLS_PER_CHANNEL_BLOCK,
    frequency_Hz
  );

  // create common block mutex for protecting allocs from multiple threads allocating RMT channels
  if (!g_rmt_block_lock) {
    g_rmt_block_lock = xSemaphoreCreateMutex();
    if (g_rmt_block_lock == NULL) {
      log_e("GPIO %d - Failed creating RMT Mutex.", pin);
      return false;
    }
  }

  // check if the RMT peripheral is already initialized with the same parameters
  rmt_bus_handle_t bus = NULL;
  peripheral_bus_type_t rmt_bus_type = perimanGetPinBusType(pin);
  if (rmt_bus_type == ESP32_BUS_TYPE_RMT_TX || rmt_bus_type == ESP32_BUS_TYPE_RMT_RX) {
    rmt_ch_dir_t bus_rmt_dir = rmt_bus_type == ESP32_BUS_TYPE_RMT_TX ? RMT_TX_MODE : RMT_RX_MODE;
    bus = (rmt_bus_handle_t)perimanGetPinBus(pin, rmt_bus_type);
    if (bus->frequency_Hz == frequency_Hz && bus_rmt_dir == channel_direction && bus->mem_size == mem_size) {
      return true;  // already initialized with the same parameters
    }
  }

  // set Peripheral Manager deInit Callback
  perimanSetBusDeinit(ESP32_BUS_TYPE_RMT_TX, _rmtDetachBus);
  perimanSetBusDeinit(ESP32_BUS_TYPE_RMT_RX, _rmtDetachBus);

  // check is pin is valid and in the right direction
  if ((channel_direction == RMT_TX_MODE && !GPIO_IS_VALID_OUTPUT_GPIO(pin)) || (!GPIO_IS_VALID_GPIO(pin))) {
    log_e("GPIO %d is not valid or can't be used for output in TX mode.", pin);
    return false;
  }

  // validate the RMT ticks by the requested frequency
  // Based on 80Mhz using a divider of 8 bits (calculated as 1..256)
  if (frequency_Hz > 80000000 || frequency_Hz < 312500) {
    log_e("GPIO %d - Bad RMT frequency resolution. Must be between 312.5KHz to 80MHz.", pin);
    return false;
  }

  // Try to detach any (Tx|Rx|Whatever) previous bus or just keep it as not attached
  if (!perimanClearPinBus(pin)) {
    log_w("GPIO %d - Can't detach previous peripheral.", pin);
    return false;
  }

  // lock it
  while (xSemaphoreTake(g_rmt_block_lock, portMAX_DELAY) != pdPASS) {}

  // allocate the rmt bus object and sets all fields to NULL
  bus = (rmt_bus_handle_t)heap_caps_calloc(1, sizeof(struct rmt_obj_s), MALLOC_CAP_DEFAULT);
  if (bus == NULL) {
    log_e("GPIO %d - Bus Memory allocation fault.", pin);
    goto Err;
  }

  // store the RMT Freq and mem_size to check Initialization, Filter and Idle valid values in the RMT API
  bus->frequency_Hz = frequency_Hz;
  bus->mem_size = mem_size;
  // pulses with width smaller than min_ns will be ignored (as a glitch)
  //bus->signal_range_min_ns = 0; // disabled  --> not necessary CALLOC set all to ZERO.
  // RMT stops reading if the input stays idle for longer than max_ns
  bus->signal_range_max_ns = (1000000000 / frequency_Hz) * RMT_LL_MAX_IDLE_VALUE;  // maximum possible
  // creates the event group to control read_done and write_done
  bus->rmt_events = xEventGroupCreate();
  if (bus->rmt_events == NULL) {
    log_e("GPIO %d - RMT Group Event allocation fault.", pin);
    goto Err;
  }

  // Starting with Receive|Transmit DONE bits set, for allowing a new request from user
  xEventGroupSetBits(bus->rmt_events, RMT_FLAG_RX_DONE | RMT_FLAG_TX_DONE);

  // channel particular configuration
  if (channel_direction == RMT_TX_MODE) {
    // TX Channel
    rmt_tx_channel_config_t tx_cfg;
    memset((void *)&tx_cfg, 0, sizeof(rmt_tx_channel_config_t));
    tx_cfg.gpio_num = pin;
    // CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2
    tx_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
    tx_cfg.resolution_hz = frequency_Hz;
    tx_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL * mem_size;
    tx_cfg.trans_queue_depth = 10;  // maximum allowed
    tx_cfg.flags.invert_out = 0;
    tx_cfg.flags.with_dma = 0;
    tx_cfg.flags.io_loop_back = 0;
    tx_cfg.flags.io_od_mode = 0;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
    tx_cfg.intr_priority = 0;
#endif

    if (rmt_new_tx_channel(&tx_cfg, &bus->rmt_channel_h) != ESP_OK) {
      log_e("GPIO %d - RMT TX Initialization error.", pin);
      goto Err;
    }

    // set TX Callback
    rmt_tx_event_callbacks_t cbs = {.on_trans_done = _rmt_tx_done_callback};
    if (ESP_OK != rmt_tx_register_event_callbacks(bus->rmt_channel_h, &cbs, bus)) {
      log_e("GPIO %d RMT - Error registering TX Callback.", pin);
      goto Err;
    }

  } else {
    // RX Channel
    rmt_rx_channel_config_t rx_cfg;
    memset((void *)&rx_cfg, 0, sizeof(rmt_rx_channel_config_t));
    rx_cfg.gpio_num = pin;
    // CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2
    rx_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
    rx_cfg.resolution_hz = frequency_Hz;
    rx_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL * mem_size;
    rx_cfg.flags.invert_in = 0;
    rx_cfg.flags.with_dma = 0;
    rx_cfg.flags.io_loop_back = 0;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
    rx_cfg.intr_priority = 0;
#endif
    // try to allocate the RMT Channel
    if (ESP_OK != rmt_new_rx_channel(&rx_cfg, &bus->rmt_channel_h)) {
      log_e("GPIO %d RMT - RX Initialization error.", pin);
      goto Err;
    }

    // set RX Callback
    rmt_rx_event_callbacks_t cbs = {.on_recv_done = _rmt_rx_done_callback};
    if (ESP_OK != rmt_rx_register_event_callbacks(bus->rmt_channel_h, &cbs, bus)) {
      log_e("GPIO %d RMT - Error registering RX Callback.", pin);
      goto Err;
    }
  }

  // allocate memory for the RMT Copy encoder
  rmt_copy_encoder_config_t copy_encoder_config;
  memset((void *)&copy_encoder_config, 0, sizeof(rmt_copy_encoder_config_t));
  if (rmt_new_copy_encoder(&copy_encoder_config, &bus->rmt_copy_encoder_h) != ESP_OK) {
    log_e("GPIO %d - RMT Encoder Memory Allocation error.", pin);
    goto Err;
  }

  // create each channel Mutex for multi thread operations
#if !CONFIG_DISABLE_HAL_LOCKS
  bus->g_rmt_objlocks = xSemaphoreCreateMutex();
  if (bus->g_rmt_objlocks == NULL) {
    log_e("GPIO %d - Failed creating RMT Channel Mutex.", pin);
    goto Err;
  }
#endif

  rmt_enable(bus->rmt_channel_h);  // starts/enables the channel

  // Finally, allocate Peripheral Manager RMT bus and associate it to its GPIO
  peripheral_bus_type_t pinBusType = channel_direction == RMT_TX_MODE ? ESP32_BUS_TYPE_RMT_TX : ESP32_BUS_TYPE_RMT_RX;
  if (!perimanSetPinBus(pin, pinBusType, (void *)bus, -1, -1)) {
    log_e("Can't allocate the GPIO %d in the Peripheral Manager.", pin);
    goto Err;
  }

  // this delay is necessary when CPU frequency changes, but internal RMT setup is "old/wrong"
  // The use case is related to the RMT_CPUFreq_Test example. The very first RMT Write
  // goes in the wrong pace (frequency). The delay allows other IDF tasks to run to fix it.
  if (loopTaskHandle != NULL) {
    // it can only run when Arduino task has been already started.
    delay(1);
  }  // prevent panic when rmtInit() is executed within an C++ object constructor
  // release the mutex
  xSemaphoreGive(g_rmt_block_lock);
  return true;

Err:
  // release LOCK and the RMT object
  xSemaphoreGive(g_rmt_block_lock);
  _rmtDetachBus((void *)bus);
  return false;
}

#endif /* SOC_RMT_SUPPORTED */
