diff --git a/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_common.c b/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_common.c index 53df267c4ed..e41e75cbd79 100644 --- a/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_common.c +++ b/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_common.c @@ -15,7 +15,9 @@ LOG_MODULE_REGISTER(spi_lpspi, CONFIG_SPI_LOG_LEVEL); #include "spi_nxp_lpspi_priv.h" -#include + +/* simple macro for readability of the equations used in the clock configuring */ +#define TWO_EXP(power) BIT(power) #if defined(LPSPI_RSTS) || defined(LPSPI_CLOCKS) static LPSPI_Type *const lpspi_bases[] = LPSPI_BASE_PTRS; @@ -120,6 +122,142 @@ static inline int lpspi_validate_xfer_args(const struct spi_config *spi_cfg) return 0; } +static uint8_t lpspi_calc_delay_scaler(uint32_t desired_delay_ns, + uint32_t prescaled_clock, + uint32_t min_cycles) +{ + uint64_t delay_cycles; + + /* calculates the number of functional clock cycles needed to achieve delay */ + delay_cycles = (uint64_t)prescaled_clock * desired_delay_ns; + delay_cycles = DIV_ROUND_UP(delay_cycles, NSEC_PER_SEC); + + /* what the min_cycles parameter is about is that + * PCSSCK and SCKPSC are +1 cycles of the programmed value, + * while DBT is +2 cycles of the programmed value. + * So this calculates the value to program to the register. + */ + delay_cycles -= min_cycles; + + /* Don't overflow */ + delay_cycles = MIN(delay_cycles, UINT8_MAX); + + return (uint8_t)delay_cycles; +} + +/* returns CCR mask of the bits 8-31 */ +static inline uint32_t lpspi_set_delays(const struct device *dev, uint32_t prescaled_clock) +{ + const struct lpspi_config *config = dev->config; + + return LPSPI_CCR_PCSSCK(lpspi_calc_delay_scaler(config->pcs_sck_delay, + prescaled_clock, 1)) | + LPSPI_CCR_SCKPCS(lpspi_calc_delay_scaler(config->sck_pcs_delay, + prescaled_clock, 1)) | + LPSPI_CCR_DBT(lpspi_calc_delay_scaler(config->transfer_delay, + prescaled_clock, 2)); +} + +/* This is the equation for the sck frequency given a div and prescaler. */ +static uint32_t lpspi_calc_sck_freq(uint32_t src_clk_hz, uint16_t sckdiv, uint8_t prescaler) +{ + return (uint32_t)(src_clk_hz / (TWO_EXP(prescaler) * (sckdiv + 2))); +} + +static inline uint8_t lpspi_calc_best_div_for_prescaler(uint32_t src_clk_hz, + uint8_t prescaler, + uint32_t req_freq) +{ + uint64_t prescaled_req_freq = TWO_EXP(prescaler) * req_freq; + uint64_t ratio; + + if (prescaled_req_freq == 0) { + ratio = UINT8_MAX + 2; + } else { + ratio = DIV_ROUND_UP(src_clk_hz, prescaled_req_freq); + } + + ratio = MAX(ratio, 2); + ratio -= 2; + ratio = MIN(ratio, UINT8_MAX); + + return (uint8_t)ratio; +} + +/* This function configures the clock control register (CCR) for the desired frequency + * It does a binary search for the optimal CCR divider and TCR prescaler. + * The prescale_value parameter is changed to the best value of the prescaler, + * for use in setting the TCR outside this function. + * The return value is the mask of the CCR (bits 0-7) required to set SCKDIV for best result. + */ +static inline uint32_t lpspi_set_sckdiv(uint32_t desired_freq, + uint32_t clock_freq, uint8_t *prescale_value) +{ + uint8_t best_prescaler = 0, best_div = 0; + uint32_t best_freq = 0; + + for (int8_t prescaler = 7U; prescaler >= 0; prescaler--) { + /* if maximum freq (div = 0) won't get better than what we got with + * previous prescaler, then we can fast path exit this loop. + */ + if (lpspi_calc_sck_freq(clock_freq, 0, prescaler) < best_freq) { + break; + } + + /* the algorithm approaches the desired freq from below intentionally, + * therefore the min is our previous best and the max is the desired. + */ + uint8_t new_div = lpspi_calc_best_div_for_prescaler(clock_freq, prescaler, + desired_freq); + uint32_t new_freq = lpspi_calc_sck_freq(clock_freq, new_div, prescaler); + + if (new_freq >= best_freq && new_freq <= desired_freq) { + best_div = new_div; + best_freq = new_freq; + best_prescaler = prescaler; + } + } + + *prescale_value = best_prescaler; + + return LPSPI_CCR_SCKDIV(best_div); +} + +/* This function configures everything except the TCR and the clock scaler */ +static void lpspi_basic_config(const struct device *dev, const struct spi_config *spi_cfg) +{ + const struct lpspi_config *config = dev->config; + LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base); + uint32_t pcs_control_bit = 1 << (LPSPI_CFGR1_PCSPOL_SHIFT + spi_cfg->slave); + uint32_t cfgr1_val = 0; + + if (spi_cfg->operation & SPI_CS_ACTIVE_HIGH) { + cfgr1_val |= pcs_control_bit; + } else { + cfgr1_val &= ~pcs_control_bit; + } + + if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_MASTER) { + cfgr1_val |= LPSPI_CFGR1_MASTER_MASK; + } + + if (config->tristate_output) { + cfgr1_val |= LPSPI_CFGR1_OUTCFG_MASK; + } + + cfgr1_val |= config->data_pin_config << LPSPI_CFGR1_PINCFG_SHIFT; + + base->CFGR1 = cfgr1_val; + + if (IS_ENABLED(CONFIG_DEBUG)) { + /* DEBUG mode makes it so the lpspi does not keep + * running while debugger has halted the chip. + * This makes debugging spi transfers easier. + */ + base->CR |= LPSPI_CR_DBGEN_MASK; + } +} + int spi_mcux_configure(const struct device *dev, const struct spi_config *spi_cfg) { const struct lpspi_config *config = dev->config; @@ -128,9 +266,9 @@ int spi_mcux_configure(const struct device *dev, const struct spi_config *spi_cf bool already_configured = spi_context_configured(ctx, spi_cfg); LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base); uint32_t word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); - lpspi_master_config_t master_config; - uint32_t clock_freq; - int ret; + uint32_t clock_freq = 0; + uint8_t prescaler = 0; + int ret = 0; /* fast path to avoid reconfigure */ /* TODO: S32K3 errata ERR050456 requiring module reset before every transfer, @@ -145,10 +283,8 @@ int spi_mcux_configure(const struct device *dev, const struct spi_config *spi_cf return ret; } - ret = clock_control_get_rate(config->clock_dev, config->clock_subsys, &clock_freq); - if (ret) { - return ret; - } + /* For the purpose of configuring the LPSPI, 8 is the minimum frame size for the hardware */ + word_size = MAX(word_size, 8); /* specific driver implementation should set up watermarks and interrupts. * we reset them here to avoid any unexpected events during configuring. @@ -168,35 +304,34 @@ int spi_mcux_configure(const struct device *dev, const struct spi_config *spi_cf data->ctx.config = spi_cfg; - LPSPI_MasterGetDefaultConfig(&master_config); - - master_config.bitsPerFrame = word_size < 8 ? 8 : word_size; /* minimum FRAMSZ is 8 */ - master_config.cpol = (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) - ? kLPSPI_ClockPolarityActiveLow - : kLPSPI_ClockPolarityActiveHigh; - master_config.cpha = (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) - ? kLPSPI_ClockPhaseSecondEdge - : kLPSPI_ClockPhaseFirstEdge; - master_config.direction = - (spi_cfg->operation & SPI_TRANSFER_LSB) ? kLPSPI_LsbFirst : kLPSPI_MsbFirst; - master_config.baudRate = spi_cfg->frequency; - master_config.pcsToSckDelayInNanoSec = config->pcs_sck_delay; - master_config.lastSckToPcsDelayInNanoSec = config->sck_pcs_delay; - master_config.betweenTransferDelayInNanoSec = config->transfer_delay; - master_config.whichPcs = spi_cfg->slave + kLPSPI_Pcs0; - master_config.pcsActiveHighOrLow = (spi_cfg->operation & SPI_CS_ACTIVE_HIGH) - ? kLPSPI_PcsActiveHigh : kLPSPI_PcsActiveLow; - master_config.pinCfg = config->data_pin_config; - master_config.dataOutConfig = config->tristate_output ? kLpspiDataOutTristate : - kLpspiDataOutRetained; - - LPSPI_MasterInit(base, &master_config, clock_freq); - LPSPI_SetDummyData(base, 0); + lpspi_basic_config(dev, spi_cfg); - if (IS_ENABLED(CONFIG_DEBUG)) { - base->CR |= LPSPI_CR_DBGEN_MASK; + ret = clock_control_get_rate(config->clock_dev, config->clock_subsys, &clock_freq); + if (ret) { + return ret; } + if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_MASTER) { + uint32_t ccr = 0; + + /* sckdiv algorithm must run *before* delays are set in order to know prescaler */ + ccr |= lpspi_set_sckdiv(spi_cfg->frequency, clock_freq, &prescaler); + ccr |= lpspi_set_delays(dev, clock_freq / TWO_EXP(prescaler)); + + /* note that not all bits of the register are readable on some platform, + * that's why we update it on one write + */ + base->CCR = ccr; + } + + base->CR |= LPSPI_CR_MEN_MASK; + + base->TCR = LPSPI_TCR_CPOL(!!(spi_cfg->operation & SPI_MODE_CPOL)) | + LPSPI_TCR_CPHA(!!(spi_cfg->operation & SPI_MODE_CPHA)) | + LPSPI_TCR_LSBF(!!(spi_cfg->operation & SPI_TRANSFER_LSB)) | + LPSPI_TCR_FRAMESZ(word_size - 1) | + LPSPI_TCR_PRESCALE(prescaler) | LPSPI_TCR_PCS(spi_cfg->slave); + return lpspi_wait_tx_fifo_empty(dev); } @@ -239,7 +374,10 @@ int spi_nxp_init_common(const struct device *dev) return err; } - LPSPI_Reset(base); + /* Full software reset */ + base->CR |= LPSPI_CR_RST_MASK; + base->CR |= LPSPI_CR_RRF_MASK | LPSPI_CR_RTF_MASK; + base->CR = 0x00U; config->irq_config_func(dev); diff --git a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake index d6c47f15535..3386692f7ec 100644 --- a/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake +++ b/modules/hal_nxp/mcux/mcux-sdk-ng/drivers/drivers.cmake @@ -24,12 +24,9 @@ if(CONFIG_NXP_LP_FLEXCOMM) set_variable_ifdef(CONFIG_I2C_MCUX_LPI2C CONFIG_MCUX_COMPONENT_driver.lpflexcomm_lpi2c) set_variable_ifdef(CONFIG_UART_MCUX_LPUART CONFIG_MCUX_COMPONENT_driver.lpflexcomm) set_variable_ifdef(CONFIG_UART_MCUX_LPUART CONFIG_MCUX_COMPONENT_driver.lpflexcomm_lpuart) - set_variable_ifdef(CONFIG_SPI_MCUX_LPSPI CONFIG_MCUX_COMPONENT_driver.lpflexcomm) - set_variable_ifdef(CONFIG_SPI_MCUX_LPSPI CONFIG_MCUX_COMPONENT_driver.lpflexcomm_lpspi) else() set_variable_ifdef(CONFIG_I2C_MCUX_LPI2C CONFIG_MCUX_COMPONENT_driver.lpi2c) set_variable_ifdef(CONFIG_UART_MCUX_LPUART CONFIG_MCUX_COMPONENT_driver.lpuart) - set_variable_ifdef(CONFIG_SPI_MCUX_LPSPI CONFIG_MCUX_COMPONENT_driver.lpspi) endif() set_variable_ifdef(CONFIG_DMA_MCUX_LPC CONFIG_MCUX_COMPONENT_driver.lpc_dma)