| 
									
										
										
										
											2022-01-12 11:30:29 +08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: Apache-2.0 | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // The HAL layer for SPI (common part)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "hal/spi_hal.h"
 | 
					
						
							| 
									
										
										
										
											2021-05-19 10:53:21 +08:00
										 |  |  | #include "hal/log.h"
 | 
					
						
							| 
									
										
										
										
											2022-01-17 17:44:25 +08:00
										 |  |  | #include "hal/assert.h"
 | 
					
						
							| 
									
										
										
										
											2020-09-23 21:01:13 +08:00
										 |  |  | #include "soc/soc_caps.h"
 | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  | #include "soc/clk_tree_defs.h"
 | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-08 17:05:49 +08:00
										 |  |  | //This GDMA related part will be introduced by GDMA dedicated APIs in the future. Here we temporarily use macros.
 | 
					
						
							| 
									
										
										
										
											2023-06-21 19:00:59 +08:00
										 |  |  | #if SOC_AHB_GDMA_VERSION == 1
 | 
					
						
							| 
									
										
										
										
											2020-09-23 21:01:13 +08:00
										 |  |  | #include "soc/gdma_struct.h"
 | 
					
						
							|  |  |  | #include "hal/gdma_ll.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-27 21:56:16 +08:00
										 |  |  | #define spi_dma_ll_rx_enable_burst_data(dev, chan, enable)         gdma_ll_rx_enable_data_burst(&GDMA, chan, enable);
 | 
					
						
							|  |  |  | #define spi_dma_ll_tx_enable_burst_data(dev, chan, enable)         gdma_ll_tx_enable_data_burst(&GDMA, chan, enable);
 | 
					
						
							|  |  |  | #define spi_dma_ll_rx_enable_burst_desc(dev, chan, enable)         gdma_ll_rx_enable_descriptor_burst(&GDMA, chan, enable);
 | 
					
						
							|  |  |  | #define spi_dma_ll_tx_enable_burst_desc(dev, chan, enable)         gdma_ll_tx_enable_descriptor_burst(&GDMA, chan, enable);
 | 
					
						
							|  |  |  | #define spi_dma_ll_enable_out_auto_wrback(dev, chan, enable)          gdma_ll_tx_enable_auto_write_back(&GDMA, chan, enable);
 | 
					
						
							|  |  |  | #define spi_dma_ll_set_out_eof_generation(dev, chan, enable)          gdma_ll_tx_set_eof_mode(&GDMA, chan, enable);
 | 
					
						
							| 
									
										
										
										
											2020-09-23 21:01:13 +08:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-07 17:37:33 +08:00
										 |  |  | /* The tag may be unused if log level is set to NONE  */ | 
					
						
							|  |  |  | static const __attribute__((unused)) char SPI_HAL_TAG[] = "spi_hal"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | #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); \ | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 17:33:10 +08:00
										 |  |  | static void s_spi_hal_dma_init_config(const spi_hal_context_t *hal) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-01-27 21:56:16 +08:00
										 |  |  |     spi_dma_ll_rx_enable_burst_data(hal->dma_in, hal->rx_dma_chan, 1); | 
					
						
							|  |  |  |     spi_dma_ll_tx_enable_burst_data(hal->dma_out, hal->tx_dma_chan, 1); | 
					
						
							|  |  |  |     spi_dma_ll_rx_enable_burst_desc(hal->dma_in, hal->rx_dma_chan, 1); | 
					
						
							| 
									
										
										
										
											2023-01-19 11:43:05 +08:00
										 |  |  |     spi_dma_ll_tx_enable_burst_desc(hal->dma_out, hal->tx_dma_chan, 1); | 
					
						
							| 
									
										
										
										
											2020-09-14 17:33:10 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-27 21:56:16 +08:00
										 |  |  | void spi_hal_init(spi_hal_context_t *hal, uint32_t host_id, const spi_hal_config_t *config) | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | { | 
					
						
							|  |  |  |     memset(hal, 0, sizeof(spi_hal_context_t)); | 
					
						
							| 
									
										
										
										
											2020-09-09 10:21:49 +08:00
										 |  |  |     spi_dev_t *hw = SPI_LL_GET_HW(host_id); | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |     hal->hw = hw; | 
					
						
							| 
									
										
										
										
											2021-01-27 21:56:16 +08:00
										 |  |  |     hal->dma_in = config->dma_in; | 
					
						
							|  |  |  |     hal->dma_out = config->dma_out; | 
					
						
							|  |  |  |     hal->dma_enabled = config->dma_enabled; | 
					
						
							|  |  |  |     hal->dmadesc_tx = config->dmadesc_tx; | 
					
						
							|  |  |  |     hal->dmadesc_rx = config->dmadesc_rx; | 
					
						
							|  |  |  |     hal->tx_dma_chan = config->tx_dma_chan; | 
					
						
							|  |  |  |     hal->rx_dma_chan = config->rx_dma_chan; | 
					
						
							|  |  |  |     hal->dmadesc_n = config->dmadesc_n; | 
					
						
							| 
									
										
										
										
											2020-09-09 10:21:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 22:13:05 +08:00
										 |  |  |     spi_ll_master_init(hw); | 
					
						
							| 
									
										
										
										
											2020-09-14 17:33:10 +08:00
										 |  |  |     s_spi_hal_dma_init_config(hal); | 
					
						
							| 
									
										
										
										
											2019-04-18 22:13:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |     //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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-09 10:21:49 +08:00
										 |  |  | esp_err_t spi_hal_cal_clock_conf(const spi_hal_timing_param_t *timing_param, int *out_freq, spi_hal_timing_conf_t *timing_conf) | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-01-19 11:43:05 +08:00
										 |  |  |     spi_hal_timing_conf_t temp_conf = {}; | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  |     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); | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     //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.
 | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  |     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); | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-13 14:19:31 +08:00
										 |  |  | #ifdef CONFIG_IDF_TARGET_ESP32
 | 
					
						
							| 
									
										
										
										
											2020-09-09 10:21:49 +08:00
										 |  |  |     const int freq_limit = spi_hal_get_freq_limit(timing_param->use_gpio, timing_param->input_delay_ns); | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-09 10:21:49 +08:00
										 |  |  |     SPI_HAL_CHECK(timing_param->half_duplex || temp_conf.timing_dummy == 0 || timing_param->no_compensate, | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |                   "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 ); | 
					
						
							| 
									
										
										
										
											2019-06-13 14:19:31 +08:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (timing_conf) { | 
					
						
							|  |  |  |         *timing_conf = temp_conf; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (out_freq) { | 
					
						
							|  |  |  |         *out_freq = eff_clk_n; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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) | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  |     const int apbclk_kHz = source_freq_hz / 1000; | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |     //how many apb clocks a period has
 | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  |     const int spiclk_apb_n = source_freq_hz / eff_clk; | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |     const int gpio_delay_ns = gpio_is_used ? GPIO_MATRIX_DELAY_NS : 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |     //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; | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |     int dummy_required = delay_apb_n / spiclk_apb_n; | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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
 | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |         miso_delay = (dummy_required + 1) * spiclk_apb_n - delay_apb_n - 1; | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |     } else { | 
					
						
							|  |  |  |         //if the dummy is not required, maybe we should also delay half a SPI clock if the data comes too early
 | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |         if (delay_apb_n * 4 <= spiclk_apb_n) { | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |             miso_delay = -1; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     *dummy_n = dummy_required; | 
					
						
							|  |  |  |     *miso_delay_n = miso_delay; | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |     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); | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  | #ifdef CONFIG_IDF_TARGET_ESP32
 | 
					
						
							|  |  |  | //TODO: IDF-6578
 | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  | int spi_hal_get_freq_limit(bool gpio_is_used, int input_delay_ns) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     const int apbclk_kHz = APB_CLK_FREQ / 1000; | 
					
						
							|  |  |  |     const int gpio_delay_ns = gpio_is_used ? GPIO_MATRIX_DELAY_NS : 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |     //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; | 
					
						
							| 
									
										
										
										
											2019-01-23 17:07:03 +08:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-17 22:51:12 +08:00
										 |  |  |     return APB_CLK_FREQ / (delay_apb_n + 1); | 
					
						
							| 
									
										
										
										
											2020-09-08 17:05:49 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2023-01-18 10:56:25 +08:00
										 |  |  | #endif
 |