mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-10-31 04:59:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			167 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
 | |
|  *
 | |
|  * SPDX-License-Identifier: Apache-2.0
 | |
|  */
 | |
| 
 | |
| // The HAL layer for SPI (common part)
 | |
| 
 | |
| #include "hal/spi_hal.h"
 | |
| #include "hal/log.h"
 | |
| #include "hal/assert.h"
 | |
| #include "hal/gpio_ll.h"    //for GPIO_LL_MATRIX_DELAY_NS
 | |
| #include "soc/soc_caps.h"
 | |
| #include "soc/clk_tree_defs.h"
 | |
| 
 | |
| /* The tag may be unused if log level is set to NONE  */
 | |
| static const __attribute__((unused)) char SPI_HAL_TAG[] = "spi_hal";
 | |
| 
 | |
| #define SPI_HAL_CHECK(a, str, ret_val, ...) \
 | |
|     if (!(a)) { \
 | |
|         HAL_LOGE(SPI_HAL_TAG,"%s(%d): "str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
 | |
|         return (ret_val); \
 | |
|     }
 | |
| 
 | |
| void spi_hal_init(spi_hal_context_t *hal, uint32_t host_id)
 | |
| {
 | |
|     memset(hal, 0, sizeof(spi_hal_context_t));
 | |
|     spi_dev_t *hw = SPI_LL_GET_HW(host_id);
 | |
|     hal->hw = hw;
 | |
|     spi_ll_master_init(hw);
 | |
| 
 | |
|     //Force a transaction done interrupt. This interrupt won't fire yet because
 | |
|     //we initialized the SPI interrupt as disabled. This way, we can just
 | |
|     //enable the SPI interrupt and the interrupt handler will kick in, handling
 | |
|     //any transactions that are queued.
 | |
|     spi_ll_enable_int(hw);
 | |
|     spi_ll_set_int_stat(hw);
 | |
|     spi_ll_set_mosi_delay(hw, 0, 0);
 | |
|     spi_ll_apply_config(hw);
 | |
| }
 | |
| 
 | |
| void spi_hal_config_io_default_level(spi_hal_context_t *hal, bool level)
 | |
| {
 | |
| #if SPI_LL_MOSI_FREE_LEVEL
 | |
|     // Config default output data line level when don't have transaction
 | |
|     spi_ll_set_mosi_free_level(hal->hw, level);
 | |
|     spi_ll_apply_config(hal->hw);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| void spi_hal_deinit(spi_hal_context_t *hal)
 | |
| {
 | |
|     spi_dev_t *hw = hal->hw;
 | |
|     if (hw) {
 | |
|         spi_ll_disable_int(hw);
 | |
|         spi_ll_clear_int_stat(hw);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #if SOC_SPI_SCT_SUPPORTED
 | |
| void spi_hal_sct_init(spi_hal_context_t *hal)
 | |
| {
 | |
|     spi_ll_conf_state_enable(hal->hw, true);
 | |
|     spi_ll_set_magic_number(hal->hw, SPI_LL_SCT_MAGIC_NUMBER);
 | |
|     spi_ll_disable_int(hal->hw);    //trans_done intr enabled in `add device` phase, sct mode should use sct_trans_done only
 | |
|     spi_ll_enable_intr(hal->hw, SPI_LL_INTR_SEG_DONE);
 | |
|     spi_ll_set_intr(hal->hw, SPI_LL_INTR_SEG_DONE);
 | |
| }
 | |
| 
 | |
| void spi_hal_sct_deinit(spi_hal_context_t *hal)
 | |
| {
 | |
|     spi_ll_conf_state_enable(hal->hw, false);
 | |
|     spi_ll_disable_intr(hal->hw, SPI_LL_INTR_SEG_DONE);
 | |
|     spi_ll_clear_intr(hal->hw, SPI_LL_INTR_SEG_DONE);
 | |
|     spi_ll_clear_int_stat(hal->hw);
 | |
|     spi_ll_enable_int(hal->hw); //recover trans_done intr
 | |
| }
 | |
| #endif  //#if SOC_SPI_SCT_SUPPORTED
 | |
| 
 | |
| esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, spi_hal_timing_conf_t *timing_conf)
 | |
| {
 | |
|     spi_hal_timing_conf_t temp_conf = {};
 | |
| 
 | |
|     int eff_clk_n = spi_ll_master_cal_clock(timing_param->clk_src_hz, timing_param->expected_freq, timing_param->duty_cycle, &temp_conf.clock_reg);
 | |
| 
 | |
|     //When the speed is too fast, we may need to use dummy cycles to compensate the reading.
 | |
|     //But these don't work for full-duplex connections.
 | |
|     spi_hal_cal_timing(timing_param->clk_src_hz, eff_clk_n, timing_param->use_gpio, timing_param->input_delay_ns, &temp_conf.timing_dummy, &temp_conf.timing_miso_delay);
 | |
| 
 | |
| #ifdef CONFIG_IDF_TARGET_ESP32
 | |
|     const int freq_limit = spi_hal_get_freq_limit(timing_param->use_gpio, timing_param->input_delay_ns);
 | |
| 
 | |
|     SPI_HAL_CHECK(timing_param->half_duplex || temp_conf.timing_dummy == 0 || timing_param->no_compensate,
 | |
|                   "When work in full-duplex mode at frequency > %.1fMHz, device cannot read correct data.\n\
 | |
| Try to use IOMUX pins to increase the frequency limit, or use the half duplex mode.\n\
 | |
| Please note the SPI master can only work at divisors of 80MHz, and the driver always tries to find the closest frequency to your configuration.\n\
 | |
| Specify ``SPI_DEVICE_NO_DUMMY`` to ignore this checking. Then you can output data at higher speed, or read data at your own risk.",
 | |
|                   ESP_ERR_NOT_SUPPORTED, freq_limit / 1000. / 1000 );
 | |
| #endif
 | |
| 
 | |
|     temp_conf.real_freq = eff_clk_n;
 | |
|     if (timing_conf) {
 | |
|         *timing_conf = temp_conf;
 | |
|     }
 | |
|     return ESP_OK;
 | |
| }
 | |
| 
 | |
| int spi_hal_master_cal_clock(int fapb, int hz, int duty_cycle)
 | |
| {
 | |
|     return spi_ll_master_cal_clock(fapb, hz, duty_cycle, NULL);
 | |
| }
 | |
| 
 | |
| 
 | |
| void spi_hal_cal_timing(int source_freq_hz, int eff_clk, bool gpio_is_used, int input_delay_ns, int *dummy_n, int *miso_delay_n)
 | |
| {
 | |
|     const int apbclk_kHz = source_freq_hz / 1000;
 | |
|     //how many apb clocks a period has
 | |
|     const int spiclk_apb_n = source_freq_hz / eff_clk;
 | |
|     int gpio_delay_ns = 0;
 | |
| #if GPIO_LL_MATRIX_DELAY_NS
 | |
|     gpio_delay_ns = gpio_is_used ? GPIO_LL_MATRIX_DELAY_NS : 0;
 | |
| #endif
 | |
| 
 | |
|     //how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
 | |
|     int delay_apb_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000;
 | |
|     if (delay_apb_n < 0) {
 | |
|         delay_apb_n = 0;
 | |
|     }
 | |
| 
 | |
|     int dummy_required = delay_apb_n / spiclk_apb_n;
 | |
| 
 | |
|     int miso_delay = 0;
 | |
|     if (dummy_required > 0) {
 | |
|         //due to the clock delay between master and slave, there's a range in which data is random
 | |
|         //give MISO a delay if needed to make sure we sample at the time MISO is stable
 | |
|         miso_delay = (dummy_required + 1) * spiclk_apb_n - delay_apb_n - 1;
 | |
|     } else {
 | |
|         //if the dummy is not required, maybe we should also delay half a SPI clock if the data comes too early
 | |
|         if (delay_apb_n * 4 <= spiclk_apb_n) {
 | |
|             miso_delay = -1;
 | |
|         }
 | |
|     }
 | |
|     *dummy_n = dummy_required;
 | |
|     *miso_delay_n = miso_delay;
 | |
|     HAL_LOGD(SPI_HAL_TAG, "eff: %d, limit: %dk(/%d), %d dummy, %d delay", eff_clk / 1000, apbclk_kHz / (delay_apb_n + 1), delay_apb_n, dummy_required, miso_delay);
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_IDF_TARGET_ESP32
 | |
| //TODO: IDF-6578
 | |
| int spi_hal_get_freq_limit(bool gpio_is_used, int input_delay_ns)
 | |
| {
 | |
|     const int apbclk_kHz = APB_CLK_FREQ / 1000;
 | |
|     int gpio_delay_ns = 0;
 | |
| #if GPIO_LL_MATRIX_DELAY_NS
 | |
|     gpio_delay_ns = gpio_is_used ? GPIO_LL_MATRIX_DELAY_NS : 0;
 | |
| #endif
 | |
| 
 | |
|     //how many apb clocks the delay is, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
 | |
|     int delay_apb_n = (1 + input_delay_ns + gpio_delay_ns) * apbclk_kHz / 1000 / 1000;
 | |
|     if (delay_apb_n < 0) {
 | |
|         delay_apb_n = 0;
 | |
|     }
 | |
| 
 | |
|     return APB_CLK_FREQ / (delay_apb_n + 1);
 | |
| }
 | |
| #endif
 | 
