// Copyright 2015-2016 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-spi.h"

#if SOC_GPSPI_SUPPORTED
#include "esp32-hal.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_attr.h"
#include "soc/spi_reg.h"
#include "soc/spi_struct.h"
#include "soc/periph_defs.h"
#include "soc/io_mux_reg.h"
#include "soc/gpio_sig_map.h"
#include "soc/rtc.h"
#if !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32C61)
#include "hal/clk_gate_ll.h"
#endif
#include "esp32-hal-periman.h"
#include "esp_private/periph_ctrl.h"
#if !CONFIG_IDF_TARGET_ESP32 && !CONFIG_IDF_TARGET_ESP32S2
#include "hal/spi_ll.h"
#endif

#include "esp_system.h"
#include "esp_intr_alloc.h"

#if CONFIG_IDF_TARGET_ESP32  // ESP32/PICO-D4
#include "soc/dport_reg.h"
#include "esp32/rom/ets_sys.h"
#include "esp32/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32S2
#include "soc/dport_reg.h"
#include "esp32s2/rom/ets_sys.h"
#include "esp32s2/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32S3
#include "soc/dport_reg.h"
#include "esp32s3/rom/ets_sys.h"
#include "esp32s3/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32C2
#include "esp32c2/rom/ets_sys.h"
#include "esp32c2/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32C3
#include "esp32c3/rom/ets_sys.h"
#include "esp32c3/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32C6
#include "esp32c6/rom/ets_sys.h"
#include "esp32c6/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32H2
#include "esp32h2/rom/ets_sys.h"
#include "esp32h2/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32P4
#include "esp32p4/rom/ets_sys.h"
#include "esp32p4/rom/gpio.h"
#include "hal/clk_tree_ll.h"

// ESP32P4 SPI clock source frequencies
#define SPI_P4_SPLL_FREQ_HZ (CLK_LL_PLL_480M_FREQ_MHZ * MHZ)  // System PLL base frequency (480 MHz)
#define SPI_P4_MAX_FREQ_HZ  80000000                          // SPI peripheral maximum frequency (80 MHz)
#elif CONFIG_IDF_TARGET_ESP32C5
#include "esp32c5/rom/ets_sys.h"
#include "esp32c5/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32C61
#include "esp32c61/rom/ets_sys.h"
#include "esp32c61/rom/gpio.h"
#else
#error Target CONFIG_IDF_TARGET is not supported
#endif

struct spi_struct_t {
  volatile spi_dev_t *dev;
#if !CONFIG_DISABLE_HAL_LOCKS
  SemaphoreHandle_t lock;
#endif
  uint8_t num;
  int8_t sck;
  int8_t miso;
  int8_t mosi;
  int8_t ss;
  bool ss_invert;
#if defined(CONFIG_IDF_TARGET_ESP32P4)
  uint8_t clk_src;          // Clock source: 0=XTAL, 1=SPLL
  uint32_t last_clock_div;  // Last clock divider calculated
  uint8_t last_clk_src;     // Last clock source selected (0=XTAL, 1=SPLL)
#endif
};

#if CONFIG_IDF_TARGET_ESP32S2
// ESP32S2
#define SPI_COUNT (2)

#define SPI_CLK_IDX(p)  ((p == 0) ? FSPICLK_OUT_MUX_IDX : ((p == 1) ? SPI3_CLK_OUT_MUX_IDX : 0))
#define SPI_MISO_IDX(p) ((p == 0) ? FSPIQ_OUT_IDX : ((p == 1) ? SPI3_Q_OUT_IDX : 0))
#define SPI_MOSI_IDX(p) ((p == 0) ? FSPID_IN_IDX : ((p == 1) ? SPI3_D_IN_IDX : 0))

#define SPI_HSPI_SS_IDX(n) ((n == 0) ? SPI3_CS0_OUT_IDX : ((n == 1) ? SPI3_CS1_OUT_IDX : ((n == 2) ? SPI3_CS2_OUT_IDX : 0)))
#define SPI_FSPI_SS_IDX(n) ((n == 0) ? FSPICS0_OUT_IDX : ((n == 1) ? FSPICS1_OUT_IDX : ((n == 2) ? FSPICS2_OUT_IDX : 0)))
#define SPI_SS_IDX(p, n)   ((p == 0) ? SPI_FSPI_SS_IDX(n) : ((p == 1) ? SPI_HSPI_SS_IDX(n) : 0))

#elif CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
#define SPI_COUNT (2)

#define SPI_CLK_IDX(p)  ((p == 0) ? FSPICLK_OUT_IDX : ((p == 1) ? SPI3_CLK_OUT_IDX : 0))
#define SPI_MISO_IDX(p) ((p == 0) ? FSPIQ_OUT_IDX : ((p == 1) ? SPI3_Q_OUT_IDX : 0))
#define SPI_MOSI_IDX(p) ((p == 0) ? FSPID_IN_IDX : ((p == 1) ? SPI3_D_IN_IDX : 0))

#define SPI_HSPI_SS_IDX(n) ((n == 0) ? SPI3_CS0_OUT_IDX : ((n == 1) ? SPI3_CS1_OUT_IDX : ((n == 2) ? SPI3_CS2_OUT_IDX : 0)))
#define SPI_FSPI_SS_IDX(n) ((n == 0) ? FSPICS0_OUT_IDX : ((n == 1) ? FSPICS1_OUT_IDX : ((n == 2) ? FSPICS2_OUT_IDX : 0)))
#define SPI_SS_IDX(p, n)   ((p == 0) ? SPI_FSPI_SS_IDX(n) : ((p == 1) ? SPI_HSPI_SS_IDX(n) : 0))

#elif CONFIG_IDF_TARGET_ESP32P4
// ESP32P4
#define SPI_COUNT (2)  // SPI2 and SPI3. SPI0 and SPI1 are reserved for flash and PSRAM

#define SPI_CLK_IDX(p)  ((p == 0) ? SPI2_CK_PAD_OUT_IDX : ((p == 1) ? SPI3_CK_PAD_OUT_IDX : 0))
#define SPI_MISO_IDX(p) ((p == 0) ? SPI2_Q_PAD_OUT_IDX : ((p == 1) ? SPI3_QO_PAD_OUT_IDX : 0))
#define SPI_MOSI_IDX(p) ((p == 0) ? SPI2_D_PAD_IN_IDX : ((p == 1) ? SPI3_D_PAD_IN_IDX : 0))

#define SPI_HSPI_SS_IDX(n) ((n == 0) ? SPI3_CS_PAD_OUT_IDX : ((n == 1) ? SPI3_CS1_PAD_OUT_IDX : ((n == 2) ? SPI3_CS2_PAD_OUT_IDX : 0)))

#define SPI_FSPI_SS_IDX(n)                                 \
  ((n == 0) ? SPI2_CS_PAD_OUT_IDX                          \
            : ((n == 1) ? SPI2_CS1_PAD_OUT_IDX             \
                        : ((n == 2) ? SPI2_CS2_PAD_OUT_IDX \
                                    : ((n == 3) ? SPI2_CS3_PAD_OUT_IDX : ((n == 4) ? SPI2_CS4_PAD_OUT_IDX : ((n == 5) ? SPI2_CS5_PAD_OUT_IDX : 0))))))

#define SPI_SS_IDX(p, n) ((p == 0) ? SPI_FSPI_SS_IDX(n) : ((p == 1) ? SPI_HSPI_SS_IDX(n) : 0))

#elif CONFIG_IDF_TARGET_ESP32
// ESP32
#define SPI_COUNT (4)

#define SPI_CLK_IDX(p)  ((p == 0) ? SPICLK_OUT_IDX : ((p == 1) ? SPICLK_OUT_IDX : ((p == 2) ? HSPICLK_OUT_IDX : ((p == 3) ? VSPICLK_OUT_IDX : 0))))
#define SPI_MISO_IDX(p) ((p == 0) ? SPIQ_OUT_IDX : ((p == 1) ? SPIQ_OUT_IDX : ((p == 2) ? HSPIQ_OUT_IDX : ((p == 3) ? VSPIQ_OUT_IDX : 0))))
#define SPI_MOSI_IDX(p) ((p == 0) ? SPID_IN_IDX : ((p == 1) ? SPID_IN_IDX : ((p == 2) ? HSPID_IN_IDX : ((p == 3) ? VSPID_IN_IDX : 0))))

#define SPI_SPI_SS_IDX(n)  ((n == 0) ? SPICS0_OUT_IDX : ((n == 1) ? SPICS1_OUT_IDX : ((n == 2) ? SPICS2_OUT_IDX : SPICS0_OUT_IDX)))
#define SPI_HSPI_SS_IDX(n) ((n == 0) ? HSPICS0_OUT_IDX : ((n == 1) ? HSPICS1_OUT_IDX : ((n == 2) ? HSPICS2_OUT_IDX : HSPICS0_OUT_IDX)))
#define SPI_VSPI_SS_IDX(n) ((n == 0) ? VSPICS0_OUT_IDX : ((n == 1) ? VSPICS1_OUT_IDX : ((n == 2) ? VSPICS2_OUT_IDX : VSPICS0_OUT_IDX)))
#define SPI_SS_IDX(p, n)   ((p == 0) ? SPI_SPI_SS_IDX(n) : ((p == 1) ? SPI_SPI_SS_IDX(n) : ((p == 2) ? SPI_HSPI_SS_IDX(n) : ((p == 3) ? SPI_VSPI_SS_IDX(n) : 0))))

#else
// ESP32C2, C3, C5, C6, C61, H2
#define SPI_COUNT (1)

#define SPI_CLK_IDX(p)  FSPICLK_OUT_IDX
#define SPI_MISO_IDX(p) FSPIQ_OUT_IDX
#define SPI_MOSI_IDX(p) FSPID_IN_IDX

#define SPI_SPI_SS_IDX(n) ((n == 0) ? FSPICS0_OUT_IDX : ((n == 1) ? FSPICS1_OUT_IDX : ((n == 2) ? FSPICS2_OUT_IDX : FSPICS0_OUT_IDX)))
#define SPI_SS_IDX(p, n)  SPI_SPI_SS_IDX(n)

#endif

#if CONFIG_DISABLE_HAL_LOCKS
#define SPI_MUTEX_LOCK()
#define SPI_MUTEX_UNLOCK()
// clang-format off
static spi_t _spi_bus_array[] = {
#if CONFIG_IDF_TARGET_ESP32S2 ||CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4
  {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 0, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), 1, -1, -1, -1, -1, false}
#elif CONFIG_IDF_TARGET_ESP32
  {(volatile spi_dev_t *)(DR_REG_SPI0_BASE), 0, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), 1, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 2, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), 3, -1, -1, -1, -1, false}
#else // ESP32C2, C3, C5, C6, C61, H2
  {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), 0, -1, -1, -1, -1, false}
#endif
};
// clang-format on
#else
#define SPI_MUTEX_LOCK() \
  do {                   \
  } while (xSemaphoreTake(spi->lock, portMAX_DELAY) != pdPASS)
#define SPI_MUTEX_UNLOCK() xSemaphoreGive(spi->lock)

// clang-format off
static spi_t _spi_bus_array[] = {
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4
  {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 0, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), NULL, 1, -1, -1, -1, -1, false}
#elif CONFIG_IDF_TARGET_ESP32
  {(volatile spi_dev_t *)(DR_REG_SPI0_BASE), NULL, 0, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI1_BASE), NULL, 1, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 2, -1, -1, -1, -1, false},
  {(volatile spi_dev_t *)(DR_REG_SPI3_BASE), NULL, 3, -1, -1, -1, -1, false}
#else // ESP32C2, C3, C5, C6, C61, H2
  {(volatile spi_dev_t *)(DR_REG_SPI2_BASE), NULL, 0, -1, -1, -1, -1, false}
#endif
};
// clang-format on
#endif

static bool spiDetachBus(void *bus) {
  uint8_t spi_num = (int)bus - 1;
  spi_t *spi = &_spi_bus_array[spi_num];

  if (spi->dev->clock.val == 0) {
    log_d("SPI bus already stopped");
    return true;
  } else if (spi->sck == -1 || (spi->miso == -1 && spi->mosi == -1)) {
    log_d("Stopping SPI bus");
    spiStopBus(spi);

    spiDetachSCK(spi);
    spiDetachMISO(spi);
    spiDetachMOSI(spi);
    spiDetachSS(spi);
    spi = NULL;
    return true;
  }
  return true;
}

static bool spiDetachBus_SCK(void *bus) {
  uint8_t spi_num = (int)bus - 1;
  spi_t *spi = &_spi_bus_array[spi_num];
  if (spi->sck != -1) {
    spiDetachSCK(spi);
    spiDetachBus(bus);
  }
  return true;
}

static bool spiDetachBus_MISO(void *bus) {
  uint8_t spi_num = (int)bus - 1;
  spi_t *spi = &_spi_bus_array[spi_num];
  if (spi->miso != -1) {
    spiDetachMISO(spi);
    spiDetachBus(bus);
  }
  return true;
}

static bool spiDetachBus_MOSI(void *bus) {
  uint8_t spi_num = (int)bus - 1;
  spi_t *spi = &_spi_bus_array[spi_num];
  if (spi->mosi != -1) {
    spiDetachMOSI(spi);
    spiDetachBus(bus);
  }
  return true;
}

static bool spiDetachBus_SS(void *bus) {
  uint8_t spi_num = (int)bus - 1;
  spi_t *spi = &_spi_bus_array[spi_num];
  if (spi->ss != -1) {
    spiDetachSS(spi);
    spiDetachBus(bus);
  }
  return true;
}

bool spiAttachSCK(spi_t *spi, int8_t sck) {
  if (!spi || sck < 0) {
    return false;
  }
  void *bus = perimanGetPinBus(sck, ESP32_BUS_TYPE_SPI_MASTER_SCK);
  if (bus != NULL && !perimanClearPinBus(sck)) {
    return false;
  }
  pinMode(sck, OUTPUT);
  pinMatrixOutAttach(sck, SPI_CLK_IDX(spi->num), false, false);
  spi->sck = sck;
  if (!perimanSetPinBus(sck, ESP32_BUS_TYPE_SPI_MASTER_SCK, (void *)(spi->num + 1), spi->num, -1)) {
    spiDetachBus_SCK((void *)(spi->num + 1));
    log_e("Failed to set pin bus to SPI for pin %d", sck);
    return false;
  }
  return true;
}

bool spiAttachMISO(spi_t *spi, int8_t miso) {
  if (!spi || miso < 0) {
    return false;
  }
  void *bus = perimanGetPinBus(miso, ESP32_BUS_TYPE_SPI_MASTER_MISO);
  if (bus != NULL && !perimanClearPinBus(miso)) {
    return false;
  }
  pinMode(miso, INPUT);
  pinMatrixInAttach(miso, SPI_MISO_IDX(spi->num), false);
  spi->miso = miso;
  if (!perimanSetPinBus(miso, ESP32_BUS_TYPE_SPI_MASTER_MISO, (void *)(spi->num + 1), spi->num, -1)) {
    spiDetachBus_MISO((void *)(spi->num + 1));
    log_e("Failed to set pin bus to SPI for pin %d", miso);
    return false;
  }
  return true;
}

bool spiAttachMOSI(spi_t *spi, int8_t mosi) {
  if (!spi || mosi < 0) {
    return false;
  }
  void *bus = perimanGetPinBus(mosi, ESP32_BUS_TYPE_SPI_MASTER_MOSI);
  if (bus != NULL && !perimanClearPinBus(mosi)) {
    return false;
  }
  pinMode(mosi, OUTPUT);
  pinMatrixOutAttach(mosi, SPI_MOSI_IDX(spi->num), false, false);
  spi->mosi = mosi;
  if (!perimanSetPinBus(mosi, ESP32_BUS_TYPE_SPI_MASTER_MOSI, (void *)(spi->num + 1), spi->num, -1)) {
    spiDetachBus_MOSI((void *)(spi->num + 1));
    log_e("Failed to set pin bus to SPI for pin %d", mosi);
    return false;
  }
  return true;
}

bool spiDetachSCK(spi_t *spi) {
  if (!spi) {
    return false;
  }
  int8_t sck = spi->sck;
  if (sck != -1) {
    pinMatrixOutDetach(sck, false, false);
    spi->sck = -1;
    perimanClearPinBus(sck);
  }
  return true;
}

bool spiDetachMISO(spi_t *spi) {
  if (!spi) {
    return false;
  }
  int8_t miso = spi->miso;
  if (miso != -1) {
    pinMatrixInDetach(SPI_MISO_IDX(spi->num), false, false);
    spi->miso = -1;
    perimanClearPinBus(miso);
  }
  return true;
}

bool spiDetachMOSI(spi_t *spi) {
  if (!spi) {
    return false;
  }
  int8_t mosi = spi->mosi;
  if (mosi != -1) {
    pinMatrixOutDetach(mosi, false, false);
    spi->mosi = -1;
    perimanClearPinBus(mosi);
  }
  return true;
}

bool spiAttachSS(spi_t *spi, uint8_t ss_num, int8_t ss) {
  if (!spi || ss < 0 || ss_num > 2) {
    return false;
  }
  void *bus = perimanGetPinBus(ss, ESP32_BUS_TYPE_SPI_MASTER_SS);
  if (bus != NULL && !perimanClearPinBus(ss)) {
    return false;
  }
  pinMode(ss, OUTPUT);
  pinMatrixOutAttach(ss, SPI_SS_IDX(spi->num, ss_num), spi->ss_invert, false);
  spiEnableSSPins(spi, (1 << ss_num));
  spi->ss = ss;
  if (!perimanSetPinBus(ss, ESP32_BUS_TYPE_SPI_MASTER_SS, (void *)(spi->num + 1), spi->num, -1)) {
    spiDetachBus_SS((void *)(spi->num + 1));
    log_e("Failed to set pin bus to SPI for pin %d", ss);
    return false;
  }
  return true;
}

bool spiDetachSS(spi_t *spi) {
  if (!spi) {
    return false;
  }
  int8_t ss = spi->ss;
  if (ss != -1) {
    pinMatrixOutDetach(ss, false, false);
    spi->ss = -1;
    perimanClearPinBus(ss);
  }
  return true;
}

void spiEnableSSPins(spi_t *spi, uint8_t ss_mask) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32
  spi->dev->pin.val &= ~(ss_mask & SPI_SS_MASK_ALL);
#else
  spi->dev->misc.val &= ~(ss_mask & SPI_SS_MASK_ALL);
#endif
  SPI_MUTEX_UNLOCK();
}

void spiDisableSSPins(spi_t *spi, uint8_t ss_mask) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32
  spi->dev->pin.val |= (ss_mask & SPI_SS_MASK_ALL);
#else
  spi->dev->misc.val |= (ss_mask & SPI_SS_MASK_ALL);
#endif
  SPI_MUTEX_UNLOCK();
}

void spiSSEnable(spi_t *spi) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  spi->dev->user.cs_setup = 1;
  spi->dev->user.cs_hold = 1;
  SPI_MUTEX_UNLOCK();
}

void spiSSDisable(spi_t *spi) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  spi->dev->user.cs_setup = 0;
  spi->dev->user.cs_hold = 0;
  SPI_MUTEX_UNLOCK();
}

void spiSSInvert(spi_t *spi, bool invert) {
  if (spi) {
    spi->ss_invert = invert;
  }
}

void spiSSSet(spi_t *spi) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32
  spi->dev->pin.cs_keep_active = 1;
#else
  spi->dev->misc.cs_keep_active = 1;
#endif
  SPI_MUTEX_UNLOCK();
}

void spiSSClear(spi_t *spi) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
#if CONFIG_IDF_TARGET_ESP32
  spi->dev->pin.cs_keep_active = 0;
#else
  spi->dev->misc.cs_keep_active = 0;
#endif
  SPI_MUTEX_UNLOCK();
}

uint32_t spiGetClockDiv(spi_t *spi) {
  if (!spi) {
    return 0;
  }
  return spi->dev->clock.val;
}

#if defined(CONFIG_IDF_TARGET_ESP32P4)
/**
 * @brief Calculate SPI frequency from divider value and source frequency
 *
 * @param divider The clock divider value (must be > 0)
 * @param source_freq The source clock frequency in Hz (e.g., 40MHz for XTAL, 480MHz for SPLL)
 * @return uint32_t The calculated SPI clock frequency in Hz, or 0 if divider is 0
 *
 * @note ESP32P4-specific helper function. Calculates: frequency = source_freq / divider
 */
static inline uint32_t _dividerToFreq(uint32_t divider, uint32_t source_freq) {
  if (divider == 0) {
    return 0;  // Safety check
  }
  return source_freq / divider;
}

/**
 * @brief Extract the divider value from a clockDiv register value
 *
 * @param clockDiv The SPI clock divider register value
 * @return uint32_t The calculated divider: (clkdiv_pre + 1) * (clkcnt_n + 1)
 *
 * @note ESP32P4-specific helper function. Extracts clkcnt_n (bits 12-17) and
 *       clkdiv_pre (bits 18-21) from the register value using bit shifts.
 *       For SPI_CLK_EQU_SYSCLK (0x80000000), this naturally returns 1 (no division).
 */
static inline uint32_t _clockDivToDivider(uint32_t clockDiv) {
  uint32_t clkcnt_n = (clockDiv >> 12) & 0x3F;
  uint32_t clkdiv_pre = (clockDiv >> 18) & 0xF;
  return ((clkdiv_pre + 1) * (clkcnt_n + 1));
}
#endif

/**
 * @brief Internal function to set SPI clock divider and handle ESP32P4 clock source switching
 *
 * @param spi Pointer to SPI bus structure
 * @param clockDiv The clock divider register value to set
 *
 * @note This function does NOT acquire the SPI mutex - it must be called from within
 *       a context that already holds the mutex.
 *
 * @note Callers (all properly acquire mutex before calling):
 *       - spiSetClockDiv() - acquires mutex via SPI_MUTEX_LOCK() before calling
 *       - spiTransaction() - acquires mutex via SPI_MUTEX_LOCK() before calling
 *       - _on_apb_change() - acquires mutex via SPI_MUTEX_LOCK() before calling (APB_AFTER_CHANGE case)
 *
 * @note ESP32P4-specific behavior:
 *       - Determines the appropriate clock source (XTAL or SPLL) based on the divider value
 *       - Uses stored per-instance clock source information if available (from last calculation)
 *       - Otherwise infers clock source by checking which gives a valid frequency:
 *         * XTAL is capped at 40MHz, so if calculated frequency > 40MHz, must use SPLL
 *         * For <= 40MHz frequencies, prefers XTAL if valid
 *       - Switches clock source if needed (with proper clock gating and delay)
 *       - Updates per-instance tracking variables (last_clock_div, last_clk_src)
 */
static void _spiSetClockDivInternal(spi_t *spi, uint32_t clockDiv) {
  if (!spi) {
    return;
  }
#if defined(CONFIG_IDF_TARGET_ESP32P4)
  // ESP32P4: Determine clock source from divider
  // The divider was calculated by spiFrequencyToClockDiv() which picks the best match
  // We store which source was selected per SPI instance, so use that if available.
  // Otherwise, infer from the divider by checking which gives a "more reasonable" frequency.
  uint32_t xtal_freq = getXtalFrequencyMhz() * 1000000;  // Actual XTAL frequency (typically 40 MHz)
  uint32_t spll_freq = SPI_P4_SPLL_FREQ_HZ;

  uint8_t new_clk_src;
  if (clockDiv == spi->last_clock_div && spi->last_clock_div != 0) {
    new_clk_src = spi->last_clk_src;
  } else {
    uint32_t divider = _clockDivToDivider(clockDiv);
    uint32_t freq_with_xtal = _dividerToFreq(divider, xtal_freq);
    uint32_t freq_with_spll = _dividerToFreq(divider, spll_freq);

    // Infer: Prefer XTAL whenever it yields a valid <= 40MHz SPI clock,
    // and fall back to SPLL only when XTAL cannot produce a valid frequency.
    if (freq_with_xtal > 0 && freq_with_xtal <= xtal_freq) {
      new_clk_src = 0;  // XTAL
    } else if (freq_with_spll > 0) {
      new_clk_src = 1;  // SPLL
    } else {
      // Both inferred frequencies are invalid; keep current source to avoid unnecessary switching.
      new_clk_src = spi->clk_src;
    }
  }

  // Store the divider and source for this SPI instance
  spi->last_clock_div = clockDiv;
  spi->last_clk_src = new_clk_src;

  if (spi->clk_src != new_clk_src) {
    // Determine SPI host ID once to avoid duplicate conditionals
    int host = (spi->num == FSPI) ? SPI2_HOST : SPI3_HOST;

    PERIPH_RCC_ATOMIC() {
      spi_ll_enable_clock(host, false);
      spi_ll_set_clk_source(spi->dev, new_clk_src ? SPI_CLK_SRC_SPLL : SPI_CLK_SRC_XTAL);
      spi_ll_enable_clock(host, true);
    }
    spi->clk_src = new_clk_src;
    ets_delay_us(10);
  }
#endif
  spi->dev->clock.val = clockDiv;
}

void spiSetClockDiv(spi_t *spi, uint32_t clockDiv) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  _spiSetClockDivInternal(spi, clockDiv);
  SPI_MUTEX_UNLOCK();
}

uint8_t spiGetDataMode(spi_t *spi) {
  if (!spi) {
    return 0;
  }
#if CONFIG_IDF_TARGET_ESP32
  bool idleEdge = spi->dev->pin.ck_idle_edge;
#else
  bool idleEdge = spi->dev->misc.ck_idle_edge;
#endif
  bool outEdge = spi->dev->user.ck_out_edge;
  if (idleEdge) {
    if (outEdge) {
      return SPI_MODE2;
    }
    return SPI_MODE3;
  }
  if (outEdge) {
    return SPI_MODE1;
  }
  return SPI_MODE0;
}

void spiSetDataMode(spi_t *spi, uint8_t dataMode) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  switch (dataMode) {
    case SPI_MODE1:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 0;
#else
      spi->dev->misc.ck_idle_edge = 0;
#endif
      spi->dev->user.ck_out_edge = 1;
      break;
    case SPI_MODE2:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 1;
#else
      spi->dev->misc.ck_idle_edge = 1;
#endif
      spi->dev->user.ck_out_edge = 1;
      break;
    case SPI_MODE3:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 1;
#else
      spi->dev->misc.ck_idle_edge = 1;
#endif
      spi->dev->user.ck_out_edge = 0;
      break;
    case SPI_MODE0:
    default:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 0;
#else
      spi->dev->misc.ck_idle_edge = 0;
#endif
      spi->dev->user.ck_out_edge = 0;
      break;
  }
  SPI_MUTEX_UNLOCK();
}

uint8_t spiGetBitOrder(spi_t *spi) {
  if (!spi) {
    return 0;
  }
  return (spi->dev->ctrl.wr_bit_order | spi->dev->ctrl.rd_bit_order) == 0;
}

void spiSetBitOrder(spi_t *spi, uint8_t bitOrder) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  if (SPI_MSBFIRST == bitOrder) {
    spi->dev->ctrl.wr_bit_order = 0;
    spi->dev->ctrl.rd_bit_order = 0;
  } else if (SPI_LSBFIRST == bitOrder) {
    spi->dev->ctrl.wr_bit_order = 1;
    spi->dev->ctrl.rd_bit_order = 1;
  }
  SPI_MUTEX_UNLOCK();
}

static void _on_apb_change(void *arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb) {
  spi_t *spi = (spi_t *)arg;
  if (ev_type == APB_BEFORE_CHANGE) {
    SPI_MUTEX_LOCK();
    while (spi->dev->cmd.usr);
  } else {
#if defined(CONFIG_IDF_TARGET_ESP32P4)
    // ESP32P4: Use the stored clock source to determine base frequency
    uint32_t base_freq = (spi->clk_src == 1) ? SPI_P4_SPLL_FREQ_HZ : (getXtalFrequencyMhz() * 1000000);
    uint32_t current_freq = base_freq / ((spi->dev->clock.clkdiv_pre + 1) * (spi->dev->clock.clkcnt_n + 1));
    uint32_t new_clockDiv = spiFrequencyToClockDiv(spi, current_freq);
    // Use _spiSetClockDivInternal to ensure clock source is updated if needed
    _spiSetClockDivInternal(spi, new_clockDiv);
#else
    spi->dev->clock.val = spiFrequencyToClockDiv(spi, old_apb / ((spi->dev->clock.clkdiv_pre + 1) * (spi->dev->clock.clkcnt_n + 1)));
#endif
    SPI_MUTEX_UNLOCK();
  }
}

static void spiInitBus(spi_t *spi) {
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->slave.trans_done = 0;
#endif
  spi->dev->slave.val = 0;
#if CONFIG_IDF_TARGET_ESP32
  spi->dev->pin.val = 0;
#else
  spi->dev->misc.val = 0;
#endif
  spi->dev->user.val = 0;
  spi->dev->user1.val = 0;
  spi->dev->ctrl.val = 0;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->ctrl1.val = 0;
  spi->dev->ctrl2.val = 0;
#else
  spi->dev->clk_gate.val = 0;
  spi->dev->dma_conf.val = 0;
  spi->dev->dma_conf.rx_afifo_rst = 1;
  spi->dev->dma_conf.buf_afifo_rst = 1;
#endif
  spi->dev->clock.val = 0;
}

void spiStopBus(spi_t *spi) {
  if (!spi) {
    return;
  }

  removeApbChangeCallback(spi, _on_apb_change);

  SPI_MUTEX_LOCK();
  spiInitBus(spi);
  SPI_MUTEX_UNLOCK();
}

spi_t *spiStartBus(uint8_t spi_num, uint32_t clockDiv, uint8_t dataMode, uint8_t bitOrder) {
  if (spi_num >= SPI_COUNT) {
    log_e("SPI bus index %u is out of range", spi_num);
    return NULL;
  }

  perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_SCK, spiDetachBus_SCK);
  perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_MISO, spiDetachBus_MISO);
  perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_MOSI, spiDetachBus_MOSI);
  perimanSetBusDeinit(ESP32_BUS_TYPE_SPI_MASTER_SS, spiDetachBus_SS);

  spi_t *spi = &_spi_bus_array[spi_num];

#if !CONFIG_DISABLE_HAL_LOCKS
  if (spi->lock == NULL) {
    spi->lock = xSemaphoreCreateMutex();
    if (spi->lock == NULL) {
      return NULL;
    }
  }
#endif

#if CONFIG_IDF_TARGET_ESP32S2
#ifndef DPORT_PERIP_RST_EN_REG
#define DPORT_PERIP_RST_EN_REG DPORT_PERIP_RST_EN0_REG
#endif
  if (spi_num == FSPI) {
    DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN);
    DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST);
  } else if (spi_num == HSPI) {
    DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN);
    DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST);
  }
#elif CONFIG_IDF_TARGET_ESP32S3
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
  if (spi_num == FSPI) {
    PERIPH_RCC_ATOMIC() {
      spi_ll_enable_bus_clock(SPI2_HOST, true);
      spi_ll_reset_register(SPI2_HOST);
      spi_ll_enable_clock(SPI2_HOST, true);
    }
  } else if (spi_num == HSPI) {
    PERIPH_RCC_ATOMIC() {
      spi_ll_enable_bus_clock(SPI3_HOST, true);
      spi_ll_reset_register(SPI3_HOST);
      spi_ll_enable_clock(SPI3_HOST, true);
    }
  }
#else
  if (spi_num == FSPI) {
    periph_ll_reset(PERIPH_SPI2_MODULE);
    periph_ll_enable_clk_clear_rst(PERIPH_SPI2_MODULE);
  } else if (spi_num == HSPI) {
    periph_ll_reset(PERIPH_SPI3_MODULE);
    periph_ll_enable_clk_clear_rst(PERIPH_SPI3_MODULE);
  }
#endif
#elif CONFIG_IDF_TARGET_ESP32
  if (spi_num == HSPI) {
    DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN);
    DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST);
  } else if (spi_num == VSPI) {
    DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN);
    DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST);
  } else {
    DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI01_CLK_EN);
    DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI01_RST);
  }
#elif CONFIG_IDF_TARGET_ESP32P4
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
  if (spi_num == FSPI) {
    PERIPH_RCC_ACQUIRE_ATOMIC(PERIPH_GPSPI2_MODULE, ref_count) {
      if (ref_count == 0) {
        PERIPH_RCC_ATOMIC() {
          spi_ll_enable_bus_clock(SPI2_HOST, true);
          spi_ll_reset_register(SPI2_HOST);
          spi_ll_enable_clock(SPI2_HOST, true);
        }
      }
    }
  } else if (spi_num == HSPI) {
    PERIPH_RCC_ACQUIRE_ATOMIC(PERIPH_GPSPI3_MODULE, ref_count) {
      if (ref_count == 0) {
        PERIPH_RCC_ATOMIC() {
          spi_ll_enable_bus_clock(SPI3_HOST, true);
          spi_ll_reset_register(SPI3_HOST);
          spi_ll_enable_clock(SPI3_HOST, true);
        }
      }
    }
  }
#else
  if (spi_num == FSPI) {
    PERIPH_RCC_ATOMIC() {
      spi_ll_enable_bus_clock(SPI2_HOST, true);
      spi_ll_reset_register(SPI2_HOST);
      spi_ll_enable_clock(SPI2_HOST, true);
    }
  } else if (spi_num == HSPI) {
    PERIPH_RCC_ATOMIC() {
      spi_ll_enable_bus_clock(SPI3_HOST, true);
      spi_ll_reset_register(SPI3_HOST);
      spi_ll_enable_clock(SPI3_HOST, true);
    }
  }
#endif
#pragma GCC diagnostic pop
#elif defined(__PERIPH_CTRL_ALLOW_LEGACY_API) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
  periph_ll_reset(PERIPH_SPI2_MODULE);
  periph_ll_enable_clk_clear_rst(PERIPH_SPI2_MODULE);
#else
  PERIPH_RCC_ATOMIC() {
    spi_ll_enable_bus_clock(SPI2_HOST, true);
    spi_ll_reset_register(SPI2_HOST);
    spi_ll_enable_clock(SPI2_HOST, true);
  }
#endif

  SPI_MUTEX_LOCK();
  spiInitBus(spi);
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->clk_gate.clk_en = 1;
  spi->dev->clk_gate.mst_clk_sel = 1;
  spi->dev->clk_gate.mst_clk_active = 1;
#if defined(CONFIG_IDF_TARGET_ESP32P4)
  // Initialize clock source to XTAL (will be changed to SPLL if needed)
  spi->clk_src = 0;         // 0 = XTAL, 1 = SPLL
  spi->last_clock_div = 0;  // Initialize per-instance storage
  spi->last_clk_src = 0;    // Initialize per-instance storage
#endif
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
  spi->dev->dma_conf.tx_seg_trans_clr_en = 1;
  spi->dev->dma_conf.rx_seg_trans_clr_en = 1;
  spi->dev->dma_conf.dma_seg_trans_en = 0;
#endif
#endif
  spi->dev->user.usr_mosi = 1;
  spi->dev->user.usr_miso = 1;
  spi->dev->user.doutdin = 1;
  int i;
  for (i = 0; i < 16; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
    spi->dev->data_buf[i] = 0x00000000;
#else
    spi->dev->data_buf[i].val = 0x00000000;
#endif
  }
  SPI_MUTEX_UNLOCK();

  spiSetDataMode(spi, dataMode);
  spiSetBitOrder(spi, bitOrder);
  spiSetClockDiv(spi, clockDiv);

  addApbChangeCallback(spi, _on_apb_change);

  return spi;
}

void spiWaitReady(spi_t *spi) {
  if (!spi) {
    return;
  }
  while (spi->dev->cmd.usr);
}

#if CONFIG_IDF_TARGET_ESP32S2
#define usr_mosi_dbitlen usr_mosi_bit_len
#define usr_miso_dbitlen usr_miso_bit_len
#elif !defined(CONFIG_IDF_TARGET_ESP32)
#define usr_mosi_dbitlen ms_data_bitlen
#define usr_miso_dbitlen ms_data_bitlen
#define mosi_dlen        ms_dlen
#define miso_dlen        ms_dlen
#endif

void spiWrite(spi_t *spi, const uint32_t *data, uint8_t len) {
  if (!spi) {
    return;
  }
  int i;
  if (len > 16) {
    len = 16;
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = (len * 32) - 1;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
  for (i = 0; i < len; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
    spi->dev->data_buf[i] = data[i];
#else
    spi->dev->data_buf[i].val = data[i];
#endif
  }
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
  SPI_MUTEX_UNLOCK();
}

void spiTransfer(spi_t *spi, uint32_t *data, uint8_t len) {
  if (!spi) {
    return;
  }
  int i;
  if (len > 16) {
    len = 16;
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = (len * 32) - 1;
  spi->dev->miso_dlen.usr_miso_dbitlen = (len * 32) - 1;
  for (i = 0; i < len; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
    spi->dev->data_buf[i] = data[i];
#else
    spi->dev->data_buf[i].val = data[i];
#endif
  }
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
  for (i = 0; i < len; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
    data[i] = spi->dev->data_buf[i];
#else
    data[i] = spi->dev->data_buf[i].val;
#endif
  }
  SPI_MUTEX_UNLOCK();
}

void spiWriteByte(spi_t *spi, uint8_t data) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif

#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
  SPI_MUTEX_UNLOCK();
}

uint8_t spiTransferByte(spi_t *spi, uint8_t data) {
  if (!spi) {
    return 0;
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
  spi->dev->miso_dlen.usr_miso_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  data = spi->dev->data_buf[0] & 0xFF;
#else
  data = spi->dev->data_buf[0].val & 0xFF;
#endif
  SPI_MUTEX_UNLOCK();
  return data;
}

static uint32_t __spiTranslate32(uint32_t data) {
  union {
    uint32_t l;
    uint8_t b[4];
  } out;
  out.l = data;
  return out.b[3] | (out.b[2] << 8) | (out.b[1] << 16) | (out.b[0] << 24);
}

void spiWriteWord(spi_t *spi, uint16_t data) {
  if (!spi) {
    return;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    data = (data >> 8) | (data << 8);
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
  SPI_MUTEX_UNLOCK();
}

uint16_t spiTransferWord(spi_t *spi, uint16_t data) {
  if (!spi) {
    return 0;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    data = (data >> 8) | (data << 8);
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
  spi->dev->miso_dlen.usr_miso_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  data = spi->dev->data_buf[0];
#else
  data = spi->dev->data_buf[0].val;
#endif
  SPI_MUTEX_UNLOCK();
  if (!spi->dev->ctrl.rd_bit_order) {
    data = (data >> 8) | (data << 8);
  }
  return data;
}

void spiWriteLong(spi_t *spi, uint32_t data) {
  if (!spi) {
    return;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    data = __spiTranslate32(data);
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
  SPI_MUTEX_UNLOCK();
}

uint32_t spiTransferLong(spi_t *spi, uint32_t data) {
  if (!spi) {
    return 0;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    data = __spiTranslate32(data);
  }
  SPI_MUTEX_LOCK();
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
  spi->dev->miso_dlen.usr_miso_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  data = spi->dev->data_buf[0];
#else
  data = spi->dev->data_buf[0].val;
#endif
  SPI_MUTEX_UNLOCK();
  if (!spi->dev->ctrl.rd_bit_order) {
    data = __spiTranslate32(data);
  }
  return data;
}

static void __spiTransferBytes(spi_t *spi, const uint8_t *data, uint8_t *out, uint32_t bytes) {
  if (!spi) {
    return;
  }
  uint32_t i;

  if (bytes > 64) {
    bytes = 64;
  }

  uint32_t words = (bytes + 3) / 4;  //16 max

  uint32_t wordsBuf[16] = {
    0,
  };
  uint8_t *bytesBuf = (uint8_t *)wordsBuf;

  if (data) {
    memcpy(bytesBuf, data, bytes);  //copy data to buffer
  } else {
    memset(bytesBuf, 0xFF, bytes);
  }

  spi->dev->mosi_dlen.usr_mosi_dbitlen = ((bytes * 8) - 1);
  spi->dev->miso_dlen.usr_miso_dbitlen = ((bytes * 8) - 1);

  for (i = 0; i < words; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
    spi->dev->data_buf[i] = wordsBuf[i];  //copy buffer to spi fifo
#else
    spi->dev->data_buf[i].val = wordsBuf[i];  //copy buffer to spi fifo
#endif
  }

#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;

  while (spi->dev->cmd.usr);

  if (out) {
    for (i = 0; i < words; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
      wordsBuf[i] = spi->dev->data_buf[i];  //copy spi fifo to buffer
#else
      wordsBuf[i] = spi->dev->data_buf[i].val;  //copy spi fifo to buffer
#endif
    }
    memcpy(out, bytesBuf, bytes);  //copy buffer to output
  }
}

void spiTransferBytes(spi_t *spi, const uint8_t *data, uint8_t *out, uint32_t size) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  while (size) {
    if (size > 64) {
      __spiTransferBytes(spi, data, out, 64);
      size -= 64;
      if (data) {
        data += 64;
      }
      if (out) {
        out += 64;
      }
    } else {
      __spiTransferBytes(spi, data, out, size);
      size = 0;
    }
  }
  SPI_MUTEX_UNLOCK();
}

void spiTransferBits(spi_t *spi, uint32_t data, uint32_t *out, uint8_t bits) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  spiTransferBitsNL(spi, data, out, bits);
  SPI_MUTEX_UNLOCK();
}

/*
 * Manual Lock Management
 * */

#define MSB_32_SET(var, val)                                  \
  {                                                           \
    uint8_t *d = (uint8_t *)&(val);                           \
    (var) = d[3] | (d[2] << 8) | (d[1] << 16) | (d[0] << 24); \
  }
#define MSB_24_SET(var, val)                   \
  {                                            \
    uint8_t *d = (uint8_t *)&(val);            \
    (var) = d[2] | (d[1] << 8) | (d[0] << 16); \
  }
#define MSB_16_SET(var, val) \
  { (var) = (((val) & 0xFF00) >> 8) | (((val) & 0xFF) << 8); }
#define MSB_PIX_SET(var, val)                                 \
  {                                                           \
    uint8_t *d = (uint8_t *)&(val);                           \
    (var) = d[1] | (d[0] << 8) | (d[3] << 16) | (d[2] << 24); \
  }

void spiTransaction(spi_t *spi, uint32_t clockDiv, uint8_t dataMode, uint8_t bitOrder) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
  // Set clock divider (handles ESP32P4 clock source selection if needed)
  _spiSetClockDivInternal(spi, clockDiv);
  switch (dataMode) {
    case SPI_MODE1:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 0;
#else
      spi->dev->misc.ck_idle_edge = 0;
#endif
      spi->dev->user.ck_out_edge = 1;
      break;
    case SPI_MODE2:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 1;
#else
      spi->dev->misc.ck_idle_edge = 1;
#endif
      spi->dev->user.ck_out_edge = 1;
      break;
    case SPI_MODE3:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 1;
#else
      spi->dev->misc.ck_idle_edge = 1;
#endif
      spi->dev->user.ck_out_edge = 0;
      break;
    case SPI_MODE0:
    default:
#if CONFIG_IDF_TARGET_ESP32
      spi->dev->pin.ck_idle_edge = 0;
#else
      spi->dev->misc.ck_idle_edge = 0;
#endif
      spi->dev->user.ck_out_edge = 0;
      break;
  }
  if (SPI_MSBFIRST == bitOrder) {
    spi->dev->ctrl.wr_bit_order = 0;
    spi->dev->ctrl.rd_bit_order = 0;
  } else if (SPI_LSBFIRST == bitOrder) {
    spi->dev->ctrl.wr_bit_order = 1;
    spi->dev->ctrl.rd_bit_order = 1;
  }
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  // Sync new config with hardware, fixes https://github.com/espressif/arduino-esp32/issues/9221
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
}

void spiSimpleTransaction(spi_t *spi) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_LOCK();
}

void spiEndTransaction(spi_t *spi) {
  if (!spi) {
    return;
  }
  SPI_MUTEX_UNLOCK();
}

void ARDUINO_ISR_ATTR spiWriteByteNL(spi_t *spi, uint8_t data) {
  if (!spi) {
    return;
  }
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
}

uint8_t spiTransferByteNL(spi_t *spi, uint8_t data) {
  if (!spi) {
    return 0;
  }
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 7;
  spi->dev->miso_dlen.usr_miso_dbitlen = 7;
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  data = spi->dev->data_buf[0] & 0xFF;
#else
  data = spi->dev->data_buf[0].val & 0xFF;
#endif
  return data;
}

void ARDUINO_ISR_ATTR spiWriteShortNL(spi_t *spi, uint16_t data) {
  if (!spi) {
    return;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    MSB_16_SET(data, data);
  }
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
}

uint16_t spiTransferShortNL(spi_t *spi, uint16_t data) {
  if (!spi) {
    return 0;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    MSB_16_SET(data, data);
  }
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 15;
  spi->dev->miso_dlen.usr_miso_dbitlen = 15;
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  data = spi->dev->data_buf[0] & 0xFFFF;
#else
  data = spi->dev->data_buf[0].val & 0xFFFF;
#endif
  if (!spi->dev->ctrl.rd_bit_order) {
    MSB_16_SET(data, data);
  }
  return data;
}

void ARDUINO_ISR_ATTR spiWriteLongNL(spi_t *spi, uint32_t data) {
  if (!spi) {
    return;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    MSB_32_SET(data, data);
  }
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
  spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
}

uint32_t spiTransferLongNL(spi_t *spi, uint32_t data) {
  if (!spi) {
    return 0;
  }
  if (!spi->dev->ctrl.wr_bit_order) {
    MSB_32_SET(data, data);
  }
  spi->dev->mosi_dlen.usr_mosi_dbitlen = 31;
  spi->dev->miso_dlen.usr_miso_dbitlen = 31;
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  data = spi->dev->data_buf[0];
#else
  data = spi->dev->data_buf[0].val;
#endif
  if (!spi->dev->ctrl.rd_bit_order) {
    MSB_32_SET(data, data);
  }
  return data;
}

void spiWriteNL(spi_t *spi, const void *data_in, uint32_t len) {
  if (!spi) {
    return;
  }
  size_t longs = len >> 2;
  if (len & 3) {
    longs++;
  }
  uint32_t *data = (uint32_t *)data_in;
  size_t c_len = 0, c_longs = 0;

  while (len) {
    c_len = (len > 64) ? 64 : len;
    c_longs = (longs > 16) ? 16 : longs;

    spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len * 8) - 1;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
    for (size_t i = 0; i < c_longs; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
      spi->dev->data_buf[i] = data[i];
#else
      spi->dev->data_buf[i].val = data[i];
#endif
    }
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while (spi->dev->cmd.usr);

    data += c_longs;
    longs -= c_longs;
    len -= c_len;
  }
}

void spiTransferBytesNL(spi_t *spi, const void *data_in, uint8_t *data_out, uint32_t len) {
  if (!spi) {
    return;
  }
  size_t longs = len >> 2;
  if (len & 3) {
    longs++;
  }
  uint32_t *data = (uint32_t *)data_in;
  uint32_t *result = (uint32_t *)data_out;
  size_t c_len = 0, c_longs = 0;

  while (len) {
    c_len = (len > 64) ? 64 : len;
    c_longs = (longs > 16) ? 16 : longs;

    spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len * 8) - 1;
    spi->dev->miso_dlen.usr_miso_dbitlen = (c_len * 8) - 1;
    if (data) {
      for (size_t i = 0; i < c_longs; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
        spi->dev->data_buf[i] = data[i];
#else
        spi->dev->data_buf[i].val = data[i];
#endif
      }
    } else {
      for (size_t i = 0; i < c_longs; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
        spi->dev->data_buf[i] = 0xFFFFFFFF;
#else
        spi->dev->data_buf[i].val = 0xFFFFFFFF;
#endif
      }
    }
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while (spi->dev->cmd.usr);
    if (result) {
      if (c_len & 3) {
        for (size_t i = 0; i < (c_longs - 1); i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
          result[i] = spi->dev->data_buf[i];
#else
          result[i] = spi->dev->data_buf[i].val;
#endif
        }
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
        uint32_t last_data = spi->dev->data_buf[c_longs - 1];
#else
        uint32_t last_data = spi->dev->data_buf[c_longs - 1].val;
#endif
        uint8_t *last_out8 = (uint8_t *)&result[c_longs - 1];
        uint8_t *last_data8 = (uint8_t *)&last_data;
        for (size_t i = 0; i < (c_len & 3); i++) {
          last_out8[i] = last_data8[i];
        }
      } else {
        for (size_t i = 0; i < c_longs; i++) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
          result[i] = spi->dev->data_buf[i];
#else
          result[i] = spi->dev->data_buf[i].val;
#endif
        }
      }
    }
    if (data) {
      data += c_longs;
    }
    if (result) {
      result += c_longs;
    }
    longs -= c_longs;
    len -= c_len;
  }
}

void spiTransferBitsNL(spi_t *spi, uint32_t data, uint32_t *out, uint8_t bits) {
  if (!spi) {
    return;
  }

  if (bits > 32) {
    bits = 32;
  }
  uint32_t bytes = (bits + 7) / 8;  //64 max
  uint32_t mask = (((uint64_t)1 << bits) - 1) & 0xFFFFFFFF;
  data = data & mask;
  if (!spi->dev->ctrl.wr_bit_order) {
    if (bytes == 2) {
      MSB_16_SET(data, data);
    } else if (bytes == 3) {
      MSB_24_SET(data, data);
    } else {
      MSB_32_SET(data, data);
    }
  }

  spi->dev->mosi_dlen.usr_mosi_dbitlen = (bits - 1);
  spi->dev->miso_dlen.usr_miso_dbitlen = (bits - 1);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  spi->dev->data_buf[0] = data;
#else
  spi->dev->data_buf[0].val = data;
#endif
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
  spi->dev->cmd.update = 1;
  while (spi->dev->cmd.update);
#endif
  spi->dev->cmd.usr = 1;
  while (spi->dev->cmd.usr);
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
  data = spi->dev->data_buf[0];
#else
  data = spi->dev->data_buf[0].val;
#endif
  if (out) {
    *out = data;
    if (!spi->dev->ctrl.rd_bit_order) {
      if (bytes == 2) {
        MSB_16_SET(*out, data);
      } else if (bytes == 3) {
        MSB_24_SET(*out, data);
      } else {
        MSB_32_SET(*out, data);
      }
    }
  }
}

void ARDUINO_ISR_ATTR spiWritePixelsNL(spi_t *spi, const void *data_in, uint32_t len) {
  size_t longs = len >> 2;
  if (len & 3) {
    longs++;
  }
  bool msb = !spi->dev->ctrl.wr_bit_order;
  uint32_t *data = (uint32_t *)data_in;
  size_t c_len = 0, c_longs = 0, l_bytes = 0;

  while (len) {
    c_len = (len > 64) ? 64 : len;
    c_longs = (longs > 16) ? 16 : longs;
    l_bytes = (c_len & 3);

    spi->dev->mosi_dlen.usr_mosi_dbitlen = (c_len * 8) - 1;
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32
    spi->dev->miso_dlen.usr_miso_dbitlen = 0;
#endif
    for (size_t i = 0; i < c_longs; i++) {
      if (msb) {
        if (l_bytes && i == (c_longs - 1)) {
          if (l_bytes == 2) {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
            MSB_16_SET(spi->dev->data_buf[i], data[i]);
#else
            MSB_16_SET(spi->dev->data_buf[i].val, data[i]);
#endif
          } else {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
            spi->dev->data_buf[i] = data[i] & 0xFF;
#else
            spi->dev->data_buf[i].val = data[i] & 0xFF;
#endif
          }
        } else {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
          MSB_PIX_SET(spi->dev->data_buf[i], data[i]);
#else
          MSB_PIX_SET(spi->dev->data_buf[i].val, data[i]);
#endif
        }
      } else {
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
        spi->dev->data_buf[i] = data[i];
#else
        spi->dev->data_buf[i].val = data[i];
#endif
      }
    }
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
    spi->dev->cmd.update = 1;
    while (spi->dev->cmd.update);
#endif
    spi->dev->cmd.usr = 1;
    while (spi->dev->cmd.usr);

    data += c_longs;
    longs -= c_longs;
    len -= c_len;
  }
}

/*
 * Clock Calculators
 *
 * */

typedef union {
  uint32_t value;
  struct {
    uint32_t clkcnt_l : 6; /*it must be equal to spi_clkcnt_N.*/
    uint32_t clkcnt_h : 6; /*it must be floor((spi_clkcnt_N+1)/2-1).*/
    uint32_t clkcnt_n : 6; /*it is the divider of spi_clk. So spi_clk frequency is system/(spi_clkdiv_pre+1)/(spi_clkcnt_N+1)*/
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
    uint32_t clkdiv_pre : 4; /*it is pre-divider of spi_clk.*/
    uint32_t reserved   : 9; /*reserved*/
#else
    uint32_t clkdiv_pre : 13; /*it is pre-divider of spi_clk.*/
#endif
    uint32_t clk_equ_sysclk : 1; /*1: spi_clk is equal to system 0: spi_clk is divided from system clock.*/
  };
} spiClk_t;

#define ClkRegToFreq(reg) (apb_freq / (((reg)->clkdiv_pre + 1) * ((reg)->clkcnt_n + 1)))

uint32_t spiClockDivToFrequency(spi_t *spi, uint32_t clockDiv) {
  uint32_t apb_freq = getApbFrequency();
#if defined(CONFIG_IDF_TARGET_ESP32P4)
  // ESP32P4: Use the actual clock source being used by this SPI instance
  if (spi && spi->clk_src == 1) {
    // SPLL is being used
    apb_freq = SPI_P4_SPLL_FREQ_HZ;
  } else {
    // XTAL is being used (default or if spi is NULL)
    apb_freq = getXtalFrequencyMhz() * 1000000;
  }
#else
  // For non-ESP32P4 targets, ignore spi parameter; always use system APB frequency.
  (void)spi;
#endif
  spiClk_t reg = {clockDiv};
  return ClkRegToFreq(&reg);
}

/**
 * @brief Calculate SPI clock divider register value for a given frequency and clock source
 *
 * @param freq Desired SPI clock frequency in Hz
 * @param source_freq Source clock frequency in Hz (e.g., 40MHz for XTAL, 480MHz for SPLL)
 * @return uint32_t Clock divider register value, or SPI_CLK_EQU_SYSCLK if freq >= source_freq
 *
 * @note This function calculates the optimal divider values (clkdiv_pre and clkcnt_n) to achieve
 *       the desired frequency from the given source. It searches for the best match that produces
 *       a frequency <= the desired frequency (never exceeding it).
 *
 * @note If the desired frequency is >= source_freq, returns SPI_CLK_EQU_SYSCLK (0x80000000)
 *       which indicates the clock should equal the source without division.
 *
 * @note If the desired frequency is below the minimum achievable, returns the minimum divider
 *       register value (0x7FFFF000).
 *
 * @note Used by spiFrequencyToClockDiv() to calculate dividers for both XTAL and SPLL sources
 *       on ESP32P4, allowing selection of the source that gives the closest match.
 */
static uint32_t _spiFrequencyToClockDivWithSource(uint32_t freq, uint32_t source_freq) {
  if (freq >= source_freq) {
    return SPI_CLK_EQU_SYSCLK;
  }

  const spiClk_t minFreqReg = {0x7FFFF000};
  // Calculate minFreq using the provided source frequency
  uint32_t minFreq = source_freq / (((minFreqReg.clkdiv_pre + 1) * (minFreqReg.clkcnt_n + 1)));
  if (freq < minFreq) {
    return minFreqReg.value;
  }

  uint8_t calN = 1;
  spiClk_t bestReg = {0};
  uint32_t bestFreq = 0;

  while (calN <= 0x3F) {
    spiClk_t reg = {0};
    uint32_t calFreq;
    int32_t calPre;
    int8_t calPreVari = -2;

    reg.clkcnt_n = calN;

    while (calPreVari++ <= 1) {
      calPre = (((source_freq / (reg.clkcnt_n + 1)) / freq) - 1) + calPreVari;
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2)
      if (calPre > 0xF) {
        reg.clkdiv_pre = 0xF;
#else
      if (calPre > 0x1FFF) {
        reg.clkdiv_pre = 0x1FFF;
#endif
      } else if (calPre <= 0) {
        reg.clkdiv_pre = 0;
      } else {
        reg.clkdiv_pre = calPre;
      }
      reg.clkcnt_l = ((reg.clkcnt_n + 1) / 2);
      // Calculate frequency directly using source_freq instead of ClkRegToFreq macro
      calFreq = source_freq / (((reg.clkdiv_pre + 1) * (reg.clkcnt_n + 1)));
      if (calFreq == freq) {
        memcpy(&bestReg, &reg, sizeof(bestReg));
        break;
      } else if (calFreq < freq) {
        if (bestFreq == 0 || (freq - calFreq) < (freq - bestFreq)) {
          bestFreq = calFreq;
          memcpy(&bestReg, &reg, sizeof(bestReg));
        }
      }
    }
    if (calFreq == (int32_t)freq) {
      break;
    }
    calN++;
  }
  return bestReg.value;
}

uint32_t spiFrequencyToClockDiv(spi_t *spi, uint32_t freq) {
#if defined(CONFIG_IDF_TARGET_ESP32P4)
  // ESP32P4: Limit frequency to SPI peripheral maximum
  if (freq > SPI_P4_MAX_FREQ_HZ) {
    freq = SPI_P4_MAX_FREQ_HZ;
  }

  // Try both clock sources and pick the one that gives frequency closest to desired
  uint32_t xtal_freq = getXtalFrequencyMhz() * 1000000;  // Actual XTAL frequency (typically 40 MHz)
  uint32_t spll_freq = SPI_P4_SPLL_FREQ_HZ;

  // Calculate dividers for both sources
  uint32_t div_xtal = _spiFrequencyToClockDivWithSource(freq, xtal_freq);
  uint32_t div_spll = _spiFrequencyToClockDivWithSource(freq, spll_freq);

  // Calculate actual frequencies each divider would produce
  uint32_t divider_xtal = _clockDivToDivider(div_xtal);
  uint32_t divider_spll = _clockDivToDivider(div_spll);
  uint32_t freq_xtal = _dividerToFreq(divider_xtal, xtal_freq);
  uint32_t freq_spll = _dividerToFreq(divider_spll, spll_freq);

  // Pick the one closest to desired frequency
  uint32_t diff_xtal = (freq > freq_xtal) ? (freq - freq_xtal) : (freq_xtal - freq);
  uint32_t diff_spll = (freq > freq_spll) ? (freq - freq_spll) : (freq_spll - freq);

  // Pick the one with closest difference to desired frequency
  // If both are valid (XTAL capped at its actual frequency) and differences are equal, prefer XTAL
  uint8_t best_is_spll;
  if (diff_spll < diff_xtal) {
    best_is_spll = 1;  // SPLL is closer
  } else if (diff_xtal < diff_spll) {
    best_is_spll = 0;  // XTAL is closer
  } else {
    // Equal differences: prefer XTAL if it's valid (freq_xtal <= xtal_freq), otherwise use SPLL
    best_is_spll = (freq_xtal <= xtal_freq) ? 0 : 1;
  }
  uint32_t best_div = best_is_spll ? div_spll : div_xtal;

  // Store the divider and source for this SPI instance (if spi is provided)
  if (spi) {
    spi->last_clock_div = best_div;
    spi->last_clk_src = best_is_spll;
  }

  // Return divider for the clock source that gives closest match
  return best_div;
#else
  // Non-ESP32P4: Only use APB clock, spi parameter unused.
  (void)spi;  // Suppress unused parameter warning
  return _spiFrequencyToClockDivWithSource(freq, getApbFrequency());
#endif
}

#endif /* SOC_GPSPI_SUPPORTED */
