From cdff607d74ea2040117bf8d6ab731809b6f4c2e1 Mon Sep 17 00:00:00 2001 From: Ryan Erickson Date: Wed, 16 Feb 2022 11:16:09 -0600 Subject: [PATCH] modem: hl7800: Make sleep mode configurable Allow application to choose between 3 different low power levels of modem. Signed-off-by: Ryan Erickson --- drivers/modem/Kconfig.hl7800 | 37 ++++++++ drivers/modem/hl7800.c | 169 +++++++++++++++++++++++++++------ include/drivers/modem/hl7800.h | 18 +++- 3 files changed, 192 insertions(+), 32 deletions(-) diff --git a/drivers/modem/Kconfig.hl7800 b/drivers/modem/Kconfig.hl7800 index 2915df1a896..0f7488db45b 100644 --- a/drivers/modem/Kconfig.hl7800 +++ b/drivers/modem/Kconfig.hl7800 @@ -240,6 +240,39 @@ config MODEM_HL7800_PSM_ACTIVE_TIME endif # MODEM_HL7800_PSM +choice + prompt "Default Sleep Level" + default MODEM_HL7800_SLEEP_LEVEL_HIBERNATE + help + The application can override this setting + +config MODEM_HL7800_SLEEP_LEVEL_HIBERNATE + bool "Hibernate" + help + Lowest power consumption + IO state not retained + Application subsystem OFF + +config MODEM_HL7800_SLEEP_LEVEL_LITE_HIBERNATE + bool "Lite Hibernate" + help + IO state retained + Application subsystem OFF + +config MODEM_HL7800_SLEEP_LEVEL_SLEEP + bool "Sleep" + help + Highest power consumption of modem sleep states + IO state retained + Application subsystem ON + Allows sockets to remain open + +endchoice + +config MODEM_HL7800_SLEEP_DELAY_AFTER_REBOOT + int "Delay in seconds before sleep after reboot" + default 10 + endif # MODEM_HL7800_LOW_POWER_MODE config MODEM_HL7800_RX_STACK_SIZE @@ -320,4 +353,8 @@ config MODEM_HL7800_ADDRESS_FAMILY_IPV4V6 endchoice +config MODEM_HL7800_ALLOW_SLEEP_DELAY_MS + int "Milliseconds to delay before allowing modem to sleep" + default 5000 + endif # MODEM_HL7800 diff --git a/drivers/modem/hl7800.c b/drivers/modem/hl7800.c index 6dc613ffcb1..97e77144484 100644 --- a/drivers/modem/hl7800.c +++ b/drivers/modem/hl7800.c @@ -282,6 +282,7 @@ static const struct mdm_control_pinconfig pinconfig[] = { #define MDM_DEFAULT_AT_CMD_RETRIES 3 #define MDM_WAKEUP_TIME K_SECONDS(12) #define MDM_BOOT_TIME K_SECONDS(12) +#define MDM_WAKE_TO_CHECK_CTS_DELAY_MS K_MSEC(20) #define MDM_WAIT_FOR_DATA_TIME K_MSEC(50) #define MDM_RESET_LOW_TIME K_MSEC(50) @@ -294,7 +295,6 @@ static const struct mdm_control_pinconfig pinconfig[] = { #define DNS_WORK_DELAY_SECS 1 #define IFACE_WORK_DELAY K_MSEC(500) #define WAIT_FOR_KSUP_RETRIES 5 -#define ALLOW_SLEEP_DELAY_SECS K_SECONDS(5) #define CGCONTRDP_RESPONSE_NUM_DELIMS 7 #define COPS_RESPONSE_NUM_DELIMS 2 @@ -534,7 +534,8 @@ struct hl7800_iface_ctx { /* modem state */ bool allow_sleep; bool uart_on; - enum mdm_hl7800_sleep_state sleep_state; + enum mdm_hl7800_sleep desired_sleep_level; + enum mdm_hl7800_sleep sleep_state; enum hl7800_lpm low_power_mode; enum mdm_hl7800_network_state network_state; enum net_operator_status operator_status; @@ -561,10 +562,10 @@ static struct hl7800_iface_ctx ictx; static size_t hl7800_read_rx(struct net_buf **buf); static char *get_network_state_string(enum mdm_hl7800_network_state state); static char *get_startup_state_string(enum mdm_hl7800_startup_state state); -static char *get_sleep_state_string(enum mdm_hl7800_sleep_state state); +static char *get_sleep_state_string(enum mdm_hl7800_sleep state); static void set_network_state(enum mdm_hl7800_network_state state); static void set_startup_state(enum mdm_hl7800_startup_state state); -static void set_sleep_state(enum mdm_hl7800_sleep_state state); +static void set_sleep_state(enum mdm_hl7800_sleep state); static void generate_network_state_event(void); static void generate_startup_state_event(void); static void generate_sleep_state_event(void); @@ -577,6 +578,11 @@ static void mark_sockets_for_reconfig(void); #endif static void hl7800_build_mac(struct hl7800_iface_ctx *ictx); +#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE +static void initialize_sleep_level(void); +static int set_sleep_level(void); +#endif + #ifdef CONFIG_MODEM_HL7800_FW_UPDATE static char *get_fota_state_string(enum mdm_hl7800_fota_state state); static void set_fota_state(enum mdm_hl7800_fota_state state); @@ -612,24 +618,29 @@ static void check_hl7800_awake(void) #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE bool is_cmd_rdy = is_cmd_ready(); - if (is_cmd_rdy && (ictx.sleep_state != HL7800_SLEEP_STATE_AWAKE) && + if (is_cmd_rdy && (ictx.sleep_state != HL7800_SLEEP_AWAKE) && !ictx.allow_sleep && !ictx.wait_for_KSUP) { PRINT_AWAKE_MSG; - set_sleep_state(HL7800_SLEEP_STATE_AWAKE); + set_sleep_state(HL7800_SLEEP_AWAKE); k_sem_give(&ictx.mdm_awake); - } else if (!is_cmd_rdy && - ictx.sleep_state == HL7800_SLEEP_STATE_AWAKE && + } else if (!is_cmd_rdy && ictx.sleep_state == HL7800_SLEEP_AWAKE && ictx.allow_sleep) { PRINT_NOT_AWAKE_MSG; - /* If the device is sleeping (not ready to receive commands) - * then the device may send +KSUP when waking up. - * We should wait for it. - */ - ictx.wait_for_KSUP = true; - ictx.wait_for_KSUP_tries = 0; - set_sleep_state(HL7800_SLEEP_STATE_ASLEEP); - k_sem_reset(&ictx.mdm_awake); + if (ictx.desired_sleep_level == HL7800_SLEEP_HIBERNATE || + ictx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE) { + /* If the device is sleeping (not ready to receive commands) + * then the device may send +KSUP when waking up. + * We should wait for it. + */ + ictx.wait_for_KSUP = true; + ictx.wait_for_KSUP_tries = 0; + + set_sleep_state(ictx.desired_sleep_level); + + } else if (ictx.desired_sleep_level == HL7800_SLEEP_SLEEP) { + set_sleep_state(HL7800_SLEEP_SLEEP); + } } #endif } @@ -827,6 +838,7 @@ static void allow_sleep_work_callback(struct k_work *item) ARG_UNUSED(item); LOG_DBG("Allow sleep"); ictx.allow_sleep = true; + set_sleep_state(ictx.desired_sleep_level); modem_assert_wake(false); } @@ -836,7 +848,7 @@ static void allow_sleep(bool allow) if (allow) { k_work_reschedule_for_queue(&hl7800_workq, &ictx.allow_sleep_work, - ALLOW_SLEEP_DELAY_SECS); + K_MSEC(CONFIG_MODEM_HL7800_ALLOW_SLEEP_DELAY_MS)); } else { LOG_DBG("Keep awake"); k_work_cancel_delayable(&ictx.allow_sleep_work); @@ -922,6 +934,14 @@ static int wakeup_hl7800(void) int ret; allow_sleep(false); + + /* If modem is in sleep mode (not hibernate), + * then it can respond in ~10 ms. + */ + if (ictx.desired_sleep_level == HL7800_SLEEP_SLEEP) { + k_sleep(MDM_WAKE_TO_CHECK_CTS_DELAY_MS); + } + if (!is_cmd_ready()) { LOG_DBG("Waiting to wakeup"); ret = k_sem_take(&ictx.mdm_awake, MDM_WAKEUP_TIME); @@ -2190,22 +2210,107 @@ static void generate_startup_state_event(void) event_handler(HL7800_EVENT_STARTUP_STATE_CHANGE, &event); } -static char *get_sleep_state_string(enum mdm_hl7800_sleep_state state) +int mdm_hl7800_set_desired_sleep_level(enum mdm_hl7800_sleep level) +{ + int r = -EPERM; + +#if CONFIG_MODEM_HL7800_LOW_POWER_MODE + switch (level) { + case HL7800_SLEEP_AWAKE: + case HL7800_SLEEP_HIBERNATE: + case HL7800_SLEEP_LITE_HIBERNATE: + case HL7800_SLEEP_SLEEP: + ictx.desired_sleep_level = level; + r = 0; + break; + default: + r = -EINVAL; + } + + if (r == 0) { + hl7800_lock(); + wakeup_hl7800(); + r = set_sleep_level(); + allow_sleep(true); + hl7800_unlock(); + } +#endif + + return r; +} + +#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE + +static void initialize_sleep_level(void) +{ + if (ictx.desired_sleep_level == HL7800_SLEEP_UNINITIALIZED) { + if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_HIBERNATE)) { + ictx.desired_sleep_level = HL7800_SLEEP_HIBERNATE; + } else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_LITE_HIBERNATE)) { + ictx.desired_sleep_level = HL7800_SLEEP_LITE_HIBERNATE; + } else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_SLEEP)) { + ictx.desired_sleep_level = HL7800_SLEEP_SLEEP; + } else { + ictx.desired_sleep_level = HL7800_SLEEP_AWAKE; + } + } +} + +static int set_sleep_level(void) +{ + char cmd[sizeof("AT+KSLEEP=#,#,##")]; + static const char SLEEP_CMD_FMT[] = "AT+KSLEEP=%d,%d,%d"; + int delay = CONFIG_MODEM_HL7800_SLEEP_DELAY_AFTER_REBOOT; + int ret = 0; + + /* AT+KSLEEP= [,[,]] + * management 1 means the HL7800 decides when it enters sleep mode + */ + switch (ictx.desired_sleep_level) { + case HL7800_SLEEP_HIBERNATE: + snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 2, delay); + break; + case HL7800_SLEEP_LITE_HIBERNATE: + snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 1, delay); + break; + case HL7800_SLEEP_SLEEP: + snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 0, delay); + break; + default: + /* don't sleep */ + snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 2, 0, delay); + break; + } + + SEND_AT_CMD_EXPECT_OK(cmd); + +error: + return ret; +} + +#endif /* CONFIG_MODEM_HL7800_LOW_POWER_MODE */ + +static char *get_sleep_state_string(enum mdm_hl7800_sleep state) { /* clang-format off */ switch (state) { - PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP_STATE, UNINITIALIZED); - PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP_STATE, ASLEEP); - PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP_STATE, AWAKE); + PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, UNINITIALIZED); + PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, HIBERNATE); + PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, LITE_HIBERNATE); + PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, SLEEP); + PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, AWAKE); default: return "UNKNOWN"; } /* clang-format on */ } -static void set_sleep_state(enum mdm_hl7800_sleep_state state) +static void set_sleep_state(enum mdm_hl7800_sleep state) { ictx.sleep_state = state; + if (ictx.sleep_state != HL7800_SLEEP_AWAKE) { + k_sem_reset(&ictx.mdm_awake); + } generate_sleep_state_event(); } @@ -2296,7 +2401,7 @@ static bool on_cmd_startup_report(struct net_buf **buf, uint16_t len) #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE mark_sockets_for_reconfig(); #endif - set_sleep_state(HL7800_SLEEP_STATE_AWAKE); + set_sleep_state(HL7800_SLEEP_AWAKE); k_sem_give(&ictx.mdm_awake); } @@ -4412,8 +4517,13 @@ static void mdm_vgpio_work_cb(struct k_work *item) hl7800_lock(); if (!ictx.vgpio_state) { - if (ictx.sleep_state != HL7800_SLEEP_STATE_ASLEEP) { - set_sleep_state(HL7800_SLEEP_STATE_ASLEEP); + if (ictx.desired_sleep_level == HL7800_SLEEP_HIBERNATE || + ictx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE) { + if (ictx.sleep_state != ictx.desired_sleep_level) { + set_sleep_state(ictx.desired_sleep_level); + } else { + LOG_WRN("Unexpected sleep condition"); + } } if (ictx.iface && ictx.initialized && net_if_is_up(ictx.iface) && ictx.low_power_mode != HL7800_LPM_PSM) { @@ -4525,7 +4635,7 @@ static void modem_reset(void) k_sleep(MDM_RESET_LOW_TIME); ictx.mdm_startup_reporting_on = false; - set_sleep_state(HL7800_SLEEP_STATE_UNINITIALIZED); + set_sleep_state(HL7800_SLEEP_UNINITIALIZED); check_hl7800_awake(); set_network_state(HL7800_NOT_REGISTERED); set_startup_state(HL7800_STARTUP_STATE_UNKNOWN); @@ -4860,8 +4970,11 @@ reboot: /* enable GPIO6 low power monitoring */ SEND_AT_CMD_EXPECT_OK("AT+KHWIOCFG=3,1,6"); - /* Turn on sleep mode */ - SEND_AT_CMD_EXPECT_OK("AT+KSLEEP=1,2,10"); + initialize_sleep_level(); + ret = set_sleep_level(); + if (ret < 0) { + goto error; + } #if CONFIG_MODEM_HL7800_PSM ictx.low_power_mode = HL7800_LPM_PSM; diff --git a/include/drivers/modem/hl7800.h b/include/drivers/modem/hl7800.h index cc4f08fb728..e02f66865a0 100644 --- a/include/drivers/modem/hl7800.h +++ b/include/drivers/modem/hl7800.h @@ -135,10 +135,12 @@ enum mdm_hl7800_network_state { HL7800_UNABLE_TO_CONFIGURE = 0xf0 }; -enum mdm_hl7800_sleep_state { - HL7800_SLEEP_STATE_UNINITIALIZED = 0, - HL7800_SLEEP_STATE_ASLEEP, - HL7800_SLEEP_STATE_AWAKE +enum mdm_hl7800_sleep { + HL7800_SLEEP_UNINITIALIZED = 0, + HL7800_SLEEP_HIBERNATE, + HL7800_SLEEP_AWAKE, + HL7800_SLEEP_LITE_HIBERNATE, + HL7800_SLEEP_SLEEP, }; enum mdm_hl7800_fota_state { @@ -455,6 +457,14 @@ int32_t mdm_hl7800_polte_locate(void); */ int32_t mdm_hl7800_perform_site_survey(void); +/** + * @brief Set desired sleep level. Requires MODEM_HL7800_LOW_POWER_MODE + * + * @param level (sleep, lite hibernate, or hibernate) + * @return int negative errno, 0 on success + */ +int mdm_hl7800_set_desired_sleep_level(enum mdm_hl7800_sleep level); + #ifdef __cplusplus } #endif