From 38c69dc2f5ce345919e8a8a78b3ea2aa12d4876b Mon Sep 17 00:00:00 2001 From: Declan Snyder Date: Thu, 20 Mar 2025 11:04:39 -0500 Subject: [PATCH 1/2] include: spi.h: Add HW timing parameters Expand the struct spi_cs_control to be able to specify hardware CS timing parameters in addition to GPIO CS, without adding memory usage and without breaking existing API usage. Also add a property for delay between data frames to struct spi_config. The purpose of these changes is to be able to better support hardware which can configure specific fine tuned delays in the nanosecond range, which is the range that most spi device datasheets are specified in. Microseconds chip select control is not that useful except for the slow control caused by using a GPIO. The expectation is that this will be used by specifying peripheral timing parameters for the devices on the spi bus in DT, and then those drivers can call into the spi bus driver API by specifying these parameters. Signed-off-by: Declan Snyder --- include/zephyr/drivers/spi.h | 43 +++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/include/zephyr/drivers/spi.h b/include/zephyr/drivers/spi.h index ba11b5e73c612..b84dc6cc49903 100644 --- a/include/zephyr/drivers/spi.h +++ b/include/zephyr/drivers/spi.h @@ -152,24 +152,49 @@ extern "C" { /** * @brief SPI Chip Select control structure * - * This can be used to control a CS line via a GPIO line, instead of - * using the controller inner CS logic. + * This describes how the SPI CS line should be controlled, including whether + * to use GPIO or native hardware CS, and timing parameters. * */ struct spi_cs_control { /** * GPIO devicetree specification of CS GPIO. - * The device pointer can be set to NULL to fully inhibit CS control if - * necessary. The GPIO flags GPIO_ACTIVE_LOW/GPIO_ACTIVE_HIGH should be + * + * If set to NULL, hardware driver can attempt to natively control CS + * automatically if it has that capability and the correct pinmux has + * been applied. Otherwise, NULL will inhibit CS control. + * + * The GPIO flags GPIO_ACTIVE_LOW/GPIO_ACTIVE_HIGH should be * equivalent to SPI_CS_ACTIVE_HIGH/SPI_CS_ACTIVE_LOW options in struct * spi_config. */ struct gpio_dt_spec gpio; + /** - * Delay in microseconds to wait before starting the - * transmission and before releasing the CS line. + * Timing configuration. + * For GPIO CS, delay is microseconds to wait before starting + * transmission and releasing CS. + * For hardware CS, pcs_to_sck_delay_ns and sck_to_pcs_delay_ns + * control assertion/deassertion timing. */ - uint32_t delay; + union { + /** + * In the case of GPIO CS, this is delay in microseconds to wait + * before starting the transmission and before releasing the CS line. + */ + uint32_t delay; + + struct { + /** @brief Delay from PCS assertion to first SCK edge in nanoseconds. + * If set to 0, hardware default will be used. + */ + uint8_t pcs_to_sck_delay_ns; + /** @brief Delay from last SCK edge to PCS deassertion in nanoseconds. + * If set to 0, hardware default will be used. + */ + uint8_t sck_to_pcs_delay_ns; + }; + }; }; /** @@ -329,8 +354,10 @@ struct spi_config { * if not used). */ struct spi_cs_control cs; -}; + /** @brief Delay between data frames in nanoseconds (0 means 50% of frequency) */ + uint16_t dfs_delay_ns; +}; /** * @brief Structure initializer for spi_config from devicetree * From 8b0a93fd6a22d460cef8e1c36b12b4547b374a26 Mon Sep 17 00:00:00 2001 From: Declan Snyder Date: Thu, 20 Mar 2025 11:08:30 -0500 Subject: [PATCH 2/2] spi_nxp_lpspi: Support API timing parameters Support the new timing parameters in the API. We don't really need to check if the cs_control is GPIO because the pin function already determines that behavior. Also, if both the DT and the API is unset, introduce a new default behavior which is to make the transfer delay 50% of the SCK period. This is what is most commonly expected by users and often gets reported as a bug because they don't configure this. Signed-off-by: Declan Snyder --- .../spi/spi_nxp_lpspi/spi_nxp_lpspi_common.c | 17 ++++++++++++++--- drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_priv.h | 10 ++++------ 2 files changed, 18 insertions(+), 9 deletions(-) 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 97ebab609d5df..2566ee5cbc959 100644 --- a/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_common.c +++ b/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_common.c @@ -108,9 +108,20 @@ int spi_mcux_configure(const struct device *dev, const struct spi_config *spi_cf 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; + + /* TODO: deprecate the DT delay props */ + master_config.pcsToSckDelayInNanoSec = config->pcs_sck_delay ? + config->pcs_sck_delay : spi_cfg->cs.pcs_to_sck_delay_ns; + master_config.lastSckToPcsDelayInNanoSec = config->sck_pcs_delay ? + config->sck_pcs_delay : spi_cfg->cs.sck_to_pcs_delay_ns; + if (config->transfer_delay > 0) { + master_config.betweenTransferDelayInNanoSec = config->transfer_delay; + } else if (spi_cfg->dfs_delay_ns > 0) { + master_config.betweenTransferDelayInNanoSec = spi_cfg->dfs_delay_ns; + } else { + master_config.betweenTransferDelayInNanoSec = (1000000000 / spi_cfg->frequency) / 2; + } + master_config.whichPcs = spi_cfg->slave + kLPSPI_Pcs0; master_config.pcsActiveHighOrLow = (spi_cfg->operation & SPI_CS_ACTIVE_HIGH) ? kLPSPI_PcsActiveHigh : kLPSPI_PcsActiveLow; diff --git a/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_priv.h b/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_priv.h index e02db078028f8..8fc6afc066b5e 100644 --- a/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_priv.h +++ b/drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi_priv.h @@ -99,12 +99,10 @@ void lpspi_wait_tx_fifo_empty(const struct device *dev); .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \ .irq_config_func = spi_mcux_config_func_##n, \ - .pcs_sck_delay = UTIL_AND(DT_INST_NODE_HAS_PROP(n, pcs_sck_delay), \ - DT_INST_PROP(n, pcs_sck_delay)), \ - .sck_pcs_delay = UTIL_AND(DT_INST_NODE_HAS_PROP(n, sck_pcs_delay), \ - DT_INST_PROP(n, sck_pcs_delay)), \ - .transfer_delay = UTIL_AND(DT_INST_NODE_HAS_PROP(n, transfer_delay), \ - DT_INST_PROP(n, transfer_delay)), \ + /* TODO: deprecate the DT delay props */ \ + .pcs_sck_delay = DT_INST_PROP_OR(n, pcs_sck_delay, 0), \ + .sck_pcs_delay = DT_INST_PROP_OR(n, sck_pcs_delay, 0), \ + .transfer_delay = DT_INST_PROP_OR(n, transfer_delay, 0), \ .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ .data_pin_config = DT_INST_ENUM_IDX(n, data_pin_config), \ .output_config = DT_INST_PROP(n, tristate_output), \