#define BTDM_LPCLK_SEL_RTC_SLOW (2)
#define BTDM_LPCLK_SEL_8M (3)
-/* Sleep duration */
-#define BTDM_MIN_SLEEP_DURATION (20)
+/* Sleep and wakeup interval control */
+#define BTDM_MIN_SLEEP_DURATION (12) // threshold of interval in slots to allow to fall into modem sleep
+#define BTDM_MODEM_WAKE_UP_DELAY (4) // delay in slots of modem wake up procedure, including re-enable PHY/RF
#define BT_DEBUG(...)
#define BT_API_CALL_CHECK(info, api_call, ret) \
uint32_t (* _btdm_lpcycles_2_us)(uint32_t cycles);
uint32_t (* _btdm_us_2_lpcycles)(uint32_t us);
bool (* _btdm_sleep_check_duration)(uint32_t *slot_cnt);
- void (* _btdm_sleep_enter)(void);
- void (* _btdm_sleep_exit)(void); /* called from ISR */
+ void (* _btdm_sleep_enter_phase1)(uint32_t lpcycles); /* called when interrupt is disabled */
+ void (* _btdm_sleep_enter_phase2)(void);
+ void (* _btdm_sleep_exit_phase1)(void); /* called from ISR */
+ void (* _btdm_sleep_exit_phase2)(void); /* called from ISR */
+ void (* _btdm_sleep_exit_phase3)(void); /* called from task */
uint32_t _magic;
};
extern void btdm_controller_disable(void);
extern uint8_t btdm_controller_get_mode(void);
extern const char *btdm_controller_get_compile_version(void);
-extern void btdm_rf_bb_init(void);
+extern void btdm_rf_bb_init_phase2(void); // shall be called after PHY/RF is enabled
/* Sleep */
extern void btdm_controller_enable_sleep(bool enable);
extern void btdm_controller_set_sleep_mode(uint8_t mode);
static uint32_t IRAM_ATTR btdm_lpcycles_2_us(uint32_t cycles);
static uint32_t IRAM_ATTR btdm_us_2_lpcycles(uint32_t us);
static bool IRAM_ATTR btdm_sleep_check_duration(uint32_t *slot_cnt);
-static void IRAM_ATTR btdm_sleep_enter_wrapper(void);
-static void IRAM_ATTR btdm_sleep_exit_wrapper(void);
+static void btdm_sleep_enter_phase1_wrapper(uint32_t lpcycles);
+static void btdm_sleep_enter_phase2_wrapper(void);
+static void IRAM_ATTR btdm_sleep_exit_phase1_wrapper(void);
+static void btdm_sleep_exit_phase3_wrapper(void);
/* Local variable definition
***************************************************************************
._btdm_lpcycles_2_us = btdm_lpcycles_2_us,
._btdm_us_2_lpcycles = btdm_us_2_lpcycles,
._btdm_sleep_check_duration = btdm_sleep_check_duration,
- ._btdm_sleep_enter = btdm_sleep_enter_wrapper,
- ._btdm_sleep_exit = btdm_sleep_exit_wrapper,
+ ._btdm_sleep_enter_phase1 = btdm_sleep_enter_phase1_wrapper,
+ ._btdm_sleep_enter_phase2 = btdm_sleep_enter_phase2_wrapper,
+ ._btdm_sleep_exit_phase1 = btdm_sleep_exit_phase1_wrapper,
+ ._btdm_sleep_exit_phase2 = NULL,
+ ._btdm_sleep_exit_phase3 = btdm_sleep_exit_phase3_wrapper,
._magic = OSI_MAGIC_VALUE,
};
#endif /* #if CONFIG_SPIRAM_USE_MALLOC */
/* Static variable declare */
-static bool btdm_bb_init_flag = false;
+// timestamp when PHY/RF was switched on
+static int64_t s_time_phy_rf_just_enabled = 0;
static esp_bt_controller_status_t btdm_controller_status = ESP_BT_CONTROLLER_STATUS_IDLE;
static portMUX_TYPE global_int_mux = portMUX_INITIALIZER_UNLOCKED;
#ifdef CONFIG_PM_ENABLE
static esp_pm_lock_handle_t s_pm_lock;
+static esp_timer_handle_t s_btdm_slp_tmr;
+static QueueHandle_t s_pm_lock_sem = NULL;
#endif
+static inline void btdm_check_and_init_bb(void)
+{
+ /* init BT-BB if PHY/RF has been switched off since last BT-BB init */
+ int64_t latest_ts = esp_phy_rf_get_on_ts();
+ if (latest_ts != s_time_phy_rf_just_enabled ||
+ s_time_phy_rf_just_enabled == 0) {
+ btdm_rf_bb_init_phase2();
+ s_time_phy_rf_just_enabled = latest_ts;
+ }
+}
+
#if CONFIG_SPIRAM_USE_MALLOC
static bool btdm_queue_generic_register(const btdm_queue_item_t *queue)
{
static uint32_t IRAM_ATTR btdm_lpcycles_2_us(uint32_t cycles)
{
- // The number of lp cycles should not lead to overflow. Thrs: 100s (for 32kHz freq)
+ // The number of lp cycles should not lead to overflow. Thrs: 100s
// clock measurement is conducted
uint64_t us = (uint64_t)btdm_lpcycle_us * cycles;
us = (us + (1 << (btdm_lpcycle_us_frac - 1))) >> btdm_lpcycle_us_frac;
if (*slot_cnt < BTDM_MIN_SLEEP_DURATION) {
return false;
}
- /* wake up 3 slots in advance */
- *slot_cnt = *slot_cnt -3;
+ /* wake up in advance considering the delay in enabling PHY/RF */
+ *slot_cnt -= BTDM_MODEM_WAKE_UP_DELAY;
return true;
}
-static void IRAM_ATTR btdm_sleep_enter_wrapper(void)
+static void btdm_sleep_enter_phase1_wrapper(uint32_t lpcycles)
+{
+#ifdef CONFIG_PM_ENABLE
+ // start a timer to wake up and acquire the pm_lock before modem_sleep awakes
+ uint32_t us_to_sleep = btdm_lpcycles_2_us(lpcycles);
+
+#define BTDM_MIN_TIMER_UNCERTAINTY_US (1800)
+ assert(us_to_sleep > BTDM_MIN_TIMER_UNCERTAINTY_US);
+ // allow a maximum time uncertainty to be about 488ppm(1/2048) at least as clock drift
+ // and set the timer in advance
+ uint32_t uncertainty = (us_to_sleep >> 11);
+ if (uncertainty < BTDM_MIN_TIMER_UNCERTAINTY_US) {
+ uncertainty = BTDM_MIN_TIMER_UNCERTAINTY_US;
+ }
+
+ if (esp_timer_start_once(s_btdm_slp_tmr, us_to_sleep - uncertainty) != ESP_OK) {
+ ESP_LOGW(BTDM_LOG_TAG, "timer start failed");
+ }
+#endif
+}
+
+static void btdm_sleep_enter_phase2_wrapper(void)
{
if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG) {
esp_modem_sleep_enter(MODEM_BLE_MODULE);
esp_modem_sleep_enter(MODEM_CLASSIC_BT_MODULE);
+#ifdef CONFIG_PM_ENABLE
+ esp_pm_lock_release(s_pm_lock);
+ semphr_give_wrapper(s_pm_lock_sem);
+#endif
} else if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_EVED) {
esp_modem_sleep_enter(MODEM_BLE_MODULE);
// pause bluetooth baseband
}
}
-static void IRAM_ATTR btdm_sleep_exit_wrapper(void)
+static void IRAM_ATTR btdm_sleep_exit_phase1_wrapper(void)
+{
+#ifdef CONFIG_PM_ENABLE
+ if (semphr_take_from_isr_wrapper(s_pm_lock_sem, NULL) == pdTRUE) {
+ esp_pm_lock_acquire(s_pm_lock);
+ }
+#endif
+}
+
+static void btdm_sleep_exit_phase3_wrapper(void)
{
if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG) {
esp_modem_sleep_exit(MODEM_BLE_MODULE);
esp_modem_sleep_exit(MODEM_CLASSIC_BT_MODULE);
+ btdm_check_and_init_bb();
+#ifdef CONFIG_PM_ENABLE
+ esp_timer_stop(s_btdm_slp_tmr);
+#endif
} else if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_EVED) {
// resume bluetooth baseband
periph_module_enable(PERIPH_BT_BASEBAND_MODULE);
}
}
+#ifdef CONFIG_PM_ENABLE
+static void IRAM_ATTR btdm_slp_tmr_callback(void *arg)
+{
+ if (semphr_take_wrapper(s_pm_lock_sem, 0) == pdTRUE) {
+ esp_pm_lock_acquire(s_pm_lock);
+ }
+}
+#endif
+
bool esp_vhci_host_check_send_available(void)
{
return API_vhci_host_check_send_available();
void esp_vhci_host_send_packet(uint8_t *data, uint16_t len)
{
if (!btdm_power_state_active()) {
+#if CONFIG_PM_ENABLE
+ if (semphr_take_wrapper(s_pm_lock_sem, 0)) {
+ esp_pm_lock_acquire(s_pm_lock);
+ }
+ esp_timer_stop(s_btdm_slp_tmr);
+#endif
btdm_wakeup_request();
}
API_vhci_host_send_packet(data, len);
esp_err_t esp_bt_controller_init(esp_bt_controller_config_t *cfg)
{
- BaseType_t ret;
+ esp_err_t err;
uint32_t btdm_cfg_mask = 0;
osi_funcs_p = (struct osi_funcs_t *)malloc_internal_wrapper(sizeof(struct osi_funcs_t));
return ESP_ERR_INVALID_ARG;
}
-#ifdef CONFIG_PM_ENABLE
- esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "bt", &s_pm_lock);
- if (err != ESP_OK) {
- return err;
- }
-#endif
-
ESP_LOGI(BTDM_LOG_TAG, "BT controller compile version [%s]", btdm_controller_get_compile_version());
#if CONFIG_SPIRAM_USE_MALLOC
btdm_queue_table_mux = xSemaphoreCreateMutex();
- if (btdm_queue_table == NULL) {
-#ifdef CONFIG_PM_ENABLE
- esp_pm_lock_delete(s_pm_lock);
- s_pm_lock = NULL;
-#endif
+ if (btdm_queue_table_mux == NULL) {
return ESP_ERR_NO_MEM;
}
memset(btdm_queue_table, 0, sizeof(btdm_queue_item_t) * BTDM_MAX_QUEUE_NUM);
#endif
+#ifdef CONFIG_PM_ENABLE
+ if ((err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "bt", &s_pm_lock)) != ESP_OK) {
+ goto error;
+ }
+ esp_timer_create_args_t create_args = {
+ .callback = btdm_slp_tmr_callback,
+ .arg = NULL,
+ .name = "btSlp"
+ };
+ if ((err = esp_timer_create(&create_args, &s_btdm_slp_tmr)) != ESP_OK) {
+ goto error;
+ }
+
+ s_pm_lock_sem = semphr_create_wrapper(1, 0);
+ if (s_pm_lock_sem == NULL) {
+ err = ESP_ERR_NO_MEM;
+ goto error;
+ }
+#endif
+
btdm_controller_mem_init();
periph_module_enable(PERIPH_BT_MODULE);
bool set_div_ret = false;
#if CONFIG_BTDM_LPCLK_SEL_MAIN_XTAL
select_src_ret = btdm_lpclk_select_src(BTDM_LPCLK_SEL_XTAL);
- set_div_ret = btdm_lpclk_set_div(rtc_clk_xtal_freq_get() * 32 - 1);
+ set_div_ret = btdm_lpclk_set_div(rtc_clk_xtal_freq_get() * 2 - 1);
assert(select_src_ret && set_div_ret);
btdm_lpcycle_us_frac = RTC_CLK_CAL_FRACT;
- btdm_lpcycle_us = 32 << btdm_lpcycle_us_frac;
+ btdm_lpcycle_us = 2 << (btdm_lpcycle_us_frac);
#elif CONFIG_BTDM_LPCLK_SEL_EXT_32K_XTAL
select_src_ret = btdm_lpclk_select_src(BTDM_LPCLK_SEL_XTAL32K);
set_div_ret = btdm_lpclk_set_div(0);
btdm_cfg_mask = btdm_config_mask_load();
- ret = btdm_controller_init(btdm_cfg_mask, cfg);
- if (ret) {
-#ifdef CONFIG_PM_ENABLE
- esp_pm_lock_delete(s_pm_lock);
- s_pm_lock = NULL;
-#endif
- return ESP_ERR_NO_MEM;
+ if (btdm_controller_init(btdm_cfg_mask, cfg) != 0) {
+ err = ESP_ERR_NO_MEM;
+ goto error;
}
btdm_controller_status = ESP_BT_CONTROLLER_STATUS_INITED;
+
return ESP_OK;
+
+error:
+#ifdef CONFIG_PM_ENABLE
+ if (s_pm_lock != NULL) {
+ esp_pm_lock_delete(s_pm_lock);
+ s_pm_lock = NULL;
+ }
+ if (s_btdm_slp_tmr != NULL) {
+ esp_timer_delete(s_btdm_slp_tmr);
+ s_btdm_slp_tmr = NULL;
+ }
+ if (s_pm_lock_sem) {
+ semphr_delete_wrapper(s_pm_lock_sem);
+ s_pm_lock_sem = NULL;
+ }
+#endif
+ return err;
}
esp_err_t esp_bt_controller_deinit(void)
periph_module_disable(PERIPH_BT_MODULE);
+#ifdef CONFIG_PM_ENABLE
+ esp_pm_lock_delete(s_pm_lock);
+ s_pm_lock = NULL;
+ esp_timer_stop(s_btdm_slp_tmr);
+ esp_timer_delete(s_btdm_slp_tmr);
+ s_btdm_slp_tmr = NULL;
+ semphr_delete_wrapper(s_pm_lock_sem);
+ s_pm_lock_sem = NULL;
+#endif
+
#if CONFIG_SPIRAM_USE_MALLOC
vSemaphoreDelete(btdm_queue_table_mux);
btdm_queue_table_mux = NULL;
btdm_lpcycle_us = 0;
btdm_controller_set_sleep_mode(BTDM_MODEM_SLEEP_MODE_NONE);
-#ifdef CONFIG_PM_ENABLE
- esp_pm_lock_delete(s_pm_lock);
- s_pm_lock = NULL;
-#endif
return ESP_OK;
}
btdm_controller_enable_sleep(true);
}
- if (btdm_bb_init_flag == false) {
- btdm_bb_init_flag = true;
- btdm_rf_bb_init(); /* only initialise once */
- }
+ // inititalize bluetooth baseband
+ btdm_check_and_init_bb();
ret = btdm_controller_enable(mode);
if (ret) {