Skip to content

Commit 3ccdd8b

Browse files
committed
fix(i2c): Fix possible error state in clear the bus,
Closes #13647
1 parent 9ec1042 commit 3ccdd8b

File tree

12 files changed

+190
-54
lines changed

12 files changed

+190
-54
lines changed

components/driver/i2c/i2c.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,10 @@ static esp_err_t i2c_master_clear_bus(i2c_port_t i2c_num)
684684
gpio_set_level(sda_io, 1); // STOP, SDA low -> high while SCL is HIGH
685685
i2c_set_pin(i2c_num, sda_io, scl_io, 1, 1, I2C_MODE_MASTER);
686686
#else
687-
i2c_ll_master_clr_bus(i2c_context[i2c_num].hal.dev, I2C_CLR_BUS_SCL_NUM);
687+
i2c_ll_master_clr_bus(i2c_context[i2c_num].hal.dev, I2C_CLR_BUS_SCL_NUM, true);
688+
while (i2c_ll_master_is_bus_clear_done(i2c_context[i2c_num].hal.dev)) {
689+
}
690+
i2c_ll_update(i2c_context[i2c_num].hal.dev);
688691
#endif
689692
return ESP_OK;
690693
}

components/esp_driver_i2c/i2c_master.c

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ static const char *TAG = "i2c.master";
4646
#define I2C_FIFO_LEN(port_num) (SOC_I2C_FIFO_LEN)
4747
#endif
4848

49+
#define I2C_CLR_BUS_TIMEOUT_MS (50) // 50ms is sufficient for clearing the bus
50+
4951
// Use the platform to same master bus handle
5052
typedef struct i2c_master_bus_platform_t i2c_master_bus_platform_t;
5153

@@ -57,6 +59,7 @@ static i2c_master_bus_platform_t s_platform;
5759

5860
static esp_err_t s_i2c_master_clear_bus(i2c_bus_handle_t handle)
5961
{
62+
esp_err_t ret = ESP_OK;
6063
#if !SOC_I2C_SUPPORT_HW_CLR_BUS
6164
const int scl_half_period = 5; // use standard 100kHz data rate
6265
int i = 0;
@@ -83,9 +86,23 @@ static esp_err_t s_i2c_master_clear_bus(i2c_bus_handle_t handle)
8386
i2c_common_set_pins(handle);
8487
#else
8588
i2c_hal_context_t *hal = &handle->hal;
86-
i2c_ll_master_clr_bus(hal->dev, I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT);
89+
i2c_ll_master_clr_bus(hal->dev, I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT, true);
90+
// If the i2c master clear bus state machine got disturbed when its work, it would go into error state.
91+
// The solution here is to use freertos tick counter to set time threshold. If its not return on time,
92+
// return invalid state and turn off the state machine for avoiding its always wrong.
93+
TickType_t start_tick = xTaskGetTickCount();
94+
const TickType_t timeout_ticks = pdMS_TO_TICKS(I2C_CLR_BUS_TIMEOUT_MS);
95+
while (i2c_ll_master_is_bus_clear_done(hal->dev)) {
96+
if ((xTaskGetTickCount() - start_tick) > timeout_ticks) {
97+
ESP_LOGE(TAG, "clear bus failed.");
98+
i2c_ll_master_clr_bus(hal->dev, 0, false);
99+
ret = ESP_ERR_INVALID_STATE;
100+
break;
101+
}
102+
}
103+
i2c_ll_update(hal->dev);
87104
#endif
88-
return ESP_OK;
105+
return ret;
89106
}
90107

91108
/**
@@ -97,6 +114,7 @@ static esp_err_t s_i2c_master_clear_bus(i2c_bus_handle_t handle)
97114
*/
98115
static esp_err_t s_i2c_hw_fsm_reset(i2c_master_bus_handle_t i2c_master)
99116
{
117+
esp_err_t ret = ESP_OK;
100118
i2c_hal_context_t *hal = &i2c_master->base->hal;
101119
#if !SOC_I2C_SUPPORT_HW_FSM_RST
102120
i2c_hal_timing_config_t timing_config;
@@ -106,7 +124,7 @@ static esp_err_t s_i2c_hw_fsm_reset(i2c_master_bus_handle_t i2c_master)
106124
i2c_ll_master_get_filter(hal->dev, &filter_cfg);
107125

108126
//to reset the I2C hw module, we need re-enable the hw
109-
s_i2c_master_clear_bus(i2c_master->base);
127+
ret = s_i2c_master_clear_bus(i2c_master->base);
110128
I2C_RCC_ATOMIC() {
111129
i2c_ll_reset_register(i2c_master->base->port_num);
112130
}
@@ -118,9 +136,9 @@ static esp_err_t s_i2c_hw_fsm_reset(i2c_master_bus_handle_t i2c_master)
118136
i2c_ll_master_set_filter(hal->dev, filter_cfg);
119137
#else
120138
i2c_ll_master_fsm_rst(hal->dev);
121-
s_i2c_master_clear_bus(i2c_master->base);
139+
ret = s_i2c_master_clear_bus(i2c_master->base);
122140
#endif
123-
return ESP_OK;
141+
return ret;
124142
}
125143

126144
static void s_i2c_err_log_print(i2c_master_event_t event, bool bypass_nack_log)
@@ -572,7 +590,7 @@ static esp_err_t s_i2c_transaction_start(i2c_master_dev_handle_t i2c_dev, int xf
572590
// Sometimes when the FSM get stuck, the ACK_ERR interrupt will occur endlessly until we reset the FSM and clear bus.
573591
esp_err_t ret = ESP_OK;
574592
if (i2c_master->status == I2C_STATUS_TIMEOUT || i2c_ll_is_bus_busy(hal->dev)) {
575-
s_i2c_hw_fsm_reset(i2c_master);
593+
ESP_RETURN_ON_ERROR(s_i2c_hw_fsm_reset(i2c_master), TAG, "reset hardware failed");
576594
}
577595

578596
if (i2c_master->base->pm_lock) {

components/hal/esp32/include/hal/i2c_ll.h

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ static inline void i2c_ll_master_set_bus_timing(i2c_dev_t *hw, i2c_hal_clk_confi
106106
/* SCL period. According to the TRM, we should always subtract 1 to SCL low period */
107107
HAL_ASSERT(bus_cfg->scl_low > 0);
108108
hw->scl_low_period.period = bus_cfg->scl_low - 1;
109-
/* Still according to the TRM, if filter is not enbled, we have to subtract 7,
109+
/* Still according to the TRM, if filter is not enabled, we have to subtract 7,
110110
* if SCL filter is enabled, we have to subtract:
111111
* 8 if SCL filter is between 0 and 2 (included)
112112
* 6 + SCL threshold if SCL filter is between 3 and 7 (included)
@@ -547,7 +547,7 @@ static inline void i2c_ll_get_stop_timing(i2c_dev_t *hw, int *setup_time, int *h
547547
*
548548
* @param hw Beginning address of the peripheral registers
549549
* @param ptr Pointer to data buffer
550-
* @param len Amount of data needs to be writen
550+
* @param len Amount of data needs to be written
551551
*
552552
* @return None.
553553
*/
@@ -612,7 +612,7 @@ static inline void i2c_ll_master_get_filter(i2c_dev_t *hw, uint8_t *filter_conf)
612612
}
613613

614614
/**
615-
* @brief Reste I2C master FSM. When the master FSM is stuck, call this function to reset the FSM
615+
* @brief Reset I2C master FSM. When the master FSM is stuck, call this function to reset the FSM
616616
*
617617
* @param hw Beginning address of the peripheral registers
618618
*
@@ -633,11 +633,23 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw)
633633
*
634634
* @return None
635635
*/
636-
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses)
636+
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses, bool enable)
637637
{
638638
;//ESP32 do not support
639639
}
640640

641+
/**
642+
* @brief Get the clear bus state
643+
*
644+
* @param hw Beginning address of the peripheral registers
645+
*
646+
* @return true: the clear bus not finish, otherwise, false.
647+
*/
648+
static inline bool i2c_ll_master_is_bus_clear_done(i2c_dev_t *hw)
649+
{
650+
return true;
651+
}
652+
641653
/**
642654
* @brief Set I2C source clock
643655
*
@@ -875,7 +887,7 @@ static inline void i2c_ll_get_scl_clk_timing(i2c_dev_t *hw, int *high_period, in
875887
* @brief Configure I2C SCL timing
876888
*
877889
* @param hw Beginning address of the peripheral registers
878-
* @param high_period The I2C SCL hight period (in core clock cycle, hight_period > 2)
890+
* @param high_period The I2C SCL high period (in core clock cycle, hight_period > 2)
879891
* @param low_period The I2C SCL low period (in core clock cycle, low_period > 1)
880892
* @param wait_high_period The I2C SCL wait rising edge period.
881893
*
@@ -1058,7 +1070,7 @@ static inline uint32_t i2c_ll_get_hw_version(i2c_dev_t *hw)
10581070
* @brief Configure I2C SCL timing
10591071
*
10601072
* @param hw Beginning address of the peripheral registers
1061-
* @param hight_period The I2C SCL hight period (in APB cycle)
1073+
* @param hight_period The I2C SCL high period (in APB cycle)
10621074
* @param low_period The I2C SCL low period (in APB cycle)
10631075
*
10641076
* @return None.

components/hal/esp32c2/include/hal/i2c_ll.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -667,18 +667,29 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw)
667667
*
668668
* @param hw Beginning address of the peripheral registers
669669
* @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out.
670+
* @param enable True to start the state machine, otherwise, false
670671
*
671672
* @return None
672673
*/
673-
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses)
674+
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses, bool enable)
674675
{
675676
hw->scl_sp_conf.scl_rst_slv_num = slave_pulses;
676-
hw->scl_sp_conf.scl_rst_slv_en = 1;
677+
hw->scl_sp_conf.scl_rst_slv_en = enable;
677678
hw->ctr.conf_upgate = 1;
678679
// hardware will clear scl_rst_slv_en after sending SCL pulses,
679-
// and we should set conf_upgate bit to synchronize register value.
680-
while (hw->scl_sp_conf.scl_rst_slv_en);
681-
hw->ctr.conf_upgate = 1;
680+
// and we should set conf_upgate bit to synchronize register value after this function.
681+
}
682+
683+
/**
684+
* @brief Get the clear bus state
685+
*
686+
* @param hw Beginning address of the peripheral registers
687+
*
688+
* @return true: the clear bus not finish, otherwise, false.
689+
*/
690+
static inline bool i2c_ll_master_is_bus_clear_done(i2c_dev_t *hw)
691+
{
692+
return hw->scl_sp_conf.scl_rst_slv_en;
682693
}
683694

684695
/**

components/hal/esp32c3/include/hal/i2c_ll.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -790,18 +790,29 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw)
790790
*
791791
* @param hw Beginning address of the peripheral registers
792792
* @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out.
793+
* @param enable True to start the state machine, otherwise, false
793794
*
794795
* @return None
795796
*/
796-
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses)
797+
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses, bool enable)
797798
{
798799
hw->scl_sp_conf.scl_rst_slv_num = slave_pulses;
799-
hw->scl_sp_conf.scl_rst_slv_en = 1;
800+
hw->scl_sp_conf.scl_rst_slv_en = enable;
800801
hw->ctr.conf_upgate = 1;
801802
// hardware will clear scl_rst_slv_en after sending SCL pulses,
802-
// and we should set conf_upgate bit to synchronize register value.
803-
while (hw->scl_sp_conf.scl_rst_slv_en);
804-
hw->ctr.conf_upgate = 1;
803+
// and we should set conf_upgate bit to synchronize register value after this function.
804+
}
805+
806+
/**
807+
* @brief Get the clear bus state
808+
*
809+
* @param hw Beginning address of the peripheral registers
810+
*
811+
* @return true: the clear bus not finish, otherwise, false.
812+
*/
813+
static inline bool i2c_ll_master_is_bus_clear_done(i2c_dev_t *hw)
814+
{
815+
return hw->scl_sp_conf.scl_rst_slv_en;
805816
}
806817

807818
/**

components/hal/esp32c5/include/hal/i2c_ll.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -747,18 +747,29 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw)
747747
*
748748
* @param hw Beginning address of the peripheral registers
749749
* @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out.
750+
* @param enable True to start the state machine, otherwise, false
750751
*
751752
* @return None
752753
*/
753-
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses)
754+
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses, bool enable)
754755
{
755756
hw->scl_sp_conf.scl_rst_slv_num = slave_pulses;
756-
hw->scl_sp_conf.scl_rst_slv_en = 1;
757+
hw->scl_sp_conf.scl_rst_slv_en = enable;
757758
hw->ctr.conf_upgate = 1;
758759
// hardware will clear scl_rst_slv_en after sending SCL pulses,
759-
// and we should set conf_upgate bit to synchronize register value.
760-
while (hw->scl_sp_conf.scl_rst_slv_en);
761-
hw->ctr.conf_upgate = 1;
760+
// and we should set conf_upgate bit to synchronize register value after this function.
761+
}
762+
763+
/**
764+
* @brief Get the clear bus state
765+
*
766+
* @param hw Beginning address of the peripheral registers
767+
*
768+
* @return true: the clear bus not finish, otherwise, false.
769+
*/
770+
static inline bool i2c_ll_master_is_bus_clear_done(i2c_dev_t *hw)
771+
{
772+
return hw->scl_sp_conf.scl_rst_slv_en;
762773
}
763774

764775
/**

components/hal/esp32c6/include/hal/i2c_ll.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -754,18 +754,29 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw)
754754
*
755755
* @param hw Beginning address of the peripheral registers
756756
* @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out.
757+
* @param enable True to start the state machine, otherwise, false
757758
*
758759
* @return None
759760
*/
760-
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses)
761+
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses, bool enable)
761762
{
762763
hw->scl_sp_conf.scl_rst_slv_num = slave_pulses;
763-
hw->scl_sp_conf.scl_rst_slv_en = 1;
764+
hw->scl_sp_conf.scl_rst_slv_en = enable;
764765
hw->ctr.conf_upgate = 1;
765766
// hardware will clear scl_rst_slv_en after sending SCL pulses,
766-
// and we should set conf_upgate bit to synchronize register value.
767-
while (hw->scl_sp_conf.scl_rst_slv_en);
768-
hw->ctr.conf_upgate = 1;
767+
// and we should set conf_upgate bit to synchronize register value after this function.
768+
}
769+
770+
/**
771+
* @brief Get the clear bus state
772+
*
773+
* @param hw Beginning address of the peripheral registers
774+
*
775+
* @return true: the clear bus not finish, otherwise, false.
776+
*/
777+
static inline bool i2c_ll_master_is_bus_clear_done(i2c_dev_t *hw)
778+
{
779+
return hw->scl_sp_conf.scl_rst_slv_en;
769780
}
770781

771782
/**

components/hal/esp32c61/include/hal/i2c_ll.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -741,18 +741,29 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw)
741741
*
742742
* @param hw Beginning address of the peripheral registers
743743
* @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out.
744+
* @param enable True to start the state machine, otherwise, false
744745
*
745746
* @return None
746747
*/
747-
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses)
748+
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses, bool enable)
748749
{
749750
hw->scl_sp_conf.scl_rst_slv_num = slave_pulses;
750-
hw->scl_sp_conf.scl_rst_slv_en = 1;
751+
hw->scl_sp_conf.scl_rst_slv_en = enable;
751752
hw->ctr.conf_upgate = 1;
752753
// hardware will clear scl_rst_slv_en after sending SCL pulses,
753-
// and we should set conf_upgate bit to synchronize register value.
754-
while (hw->scl_sp_conf.scl_rst_slv_en);
755-
hw->ctr.conf_upgate = 1;
754+
// and we should set conf_upgate bit to synchronize register value after this function.
755+
}
756+
757+
/**
758+
* @brief Get the clear bus state
759+
*
760+
* @param hw Beginning address of the peripheral registers
761+
*
762+
* @return true: the clear bus not finish, otherwise, false.
763+
*/
764+
static inline bool i2c_ll_master_is_bus_clear_done(i2c_dev_t *hw)
765+
{
766+
return hw->scl_sp_conf.scl_rst_slv_en;
756767
}
757768

758769
/**

components/hal/esp32h2/include/hal/i2c_ll.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -740,18 +740,29 @@ static inline void i2c_ll_master_fsm_rst(i2c_dev_t *hw)
740740
*
741741
* @param hw Beginning address of the peripheral registers
742742
* @param slave_pulses When I2C master is IDLE, the number of pulses will be sent out.
743+
* @param enable True to start the state machine, otherwise, false
743744
*
744745
* @return None
745746
*/
746-
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses)
747+
static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw, uint32_t slave_pulses, bool enable)
747748
{
748749
hw->scl_sp_conf.scl_rst_slv_num = slave_pulses;
749-
hw->scl_sp_conf.scl_rst_slv_en = 1;
750+
hw->scl_sp_conf.scl_rst_slv_en = enable;
750751
hw->ctr.conf_upgate = 1;
751752
// hardware will clear scl_rst_slv_en after sending SCL pulses,
752-
// and we should set conf_upgate bit to synchronize register value.
753-
while (hw->scl_sp_conf.scl_rst_slv_en);
754-
hw->ctr.conf_upgate = 1;
753+
// and we should set conf_upgate bit to synchronize register value after this function.
754+
}
755+
756+
/**
757+
* @brief Get the clear bus state
758+
*
759+
* @param hw Beginning address of the peripheral registers
760+
*
761+
* @return true: the clear bus not finish, otherwise, false.
762+
*/
763+
static inline bool i2c_ll_master_is_bus_clear_done(i2c_dev_t *hw)
764+
{
765+
return hw->scl_sp_conf.scl_rst_slv_en;
755766
}
756767

757768
/**

0 commit comments

Comments
 (0)