blob: 92542a447abf238371e7b5ca96e16aebb05e9933 [file] [log] [blame]
/*
* Copyright (c) 2025 Netfeasa Ltd.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/modem/chat.h>
#include <zephyr/modem/backend/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_offload.h>
#include <zephyr/net/offloaded_netdev.h>
#include <zephyr/net/socket_offload.h>
#include <zephyr/posix/fcntl.h>
#include <zephyr/logging/log.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "hl78xx.h"
#include "hl78xx_chat.h"
#include "hl78xx_cfg.h"
#define MAX_SCRIPT_AT_CMD_RETRY 3
#define MDM_NODE DT_ALIAS(modem)
/* Check phandle target status for a specific phandle index */
#define HAS_GPIO_IDX(node_id, prop, idx) DT_PROP_HAS_IDX(node_id, prop, idx)
/* GPIO availability macros */
#define HAS_RESET_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_reset_gpios, 0)
#define HAS_WAKE_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_wake_gpios, 0)
#define HAS_VGPIO_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_vgpio_gpios, 0)
#define HAS_UART_CTS_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_cts_gpios, 0)
#define HAS_GPIO6_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_gpio6_gpios, 0)
#define HAS_PWR_ON_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_pwr_on_gpios, 0)
#define HAS_FAST_SHUTD_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_fast_shutd_gpios, 0)
#define HAS_UART_DSR_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_dsr_gpios, 0)
#define HAS_UART_DTR_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_dtr_gpios, 0)
#define HAS_GPIO8_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_gpio8_gpios, 0)
#define HAS_SIM_SWITCH_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_sim_switch_gpios, 0)
/* GPIO count macro */
#define GPIO_CONFIG_LEN \
(HAS_RESET_GPIO + HAS_WAKE_GPIO + HAS_VGPIO_GPIO + HAS_UART_CTS_GPIO + HAS_GPIO6_GPIO + \
HAS_PWR_ON_GPIO + HAS_FAST_SHUTD_GPIO + HAS_UART_DSR_GPIO + HAS_UART_DTR_GPIO + \
HAS_GPIO8_GPIO + HAS_SIM_SWITCH_GPIO)
LOG_MODULE_REGISTER(hl78xx_dev, CONFIG_MODEM_LOG_LEVEL);
/* RX thread work queue */
K_KERNEL_STACK_DEFINE(modem_workq_stack, CONFIG_MODEM_HL78XX_RX_WORKQ_STACK_SIZE);
static struct k_work_q modem_workq;
hl78xx_evt_monitor_dispatcher_t event_dispatcher;
static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt);
static int hl78xx_on_idle_state_enter(struct hl78xx_data *data);
struct hl78xx_state_handlers {
int (*on_enter)(struct hl78xx_data *data);
int (*on_leave)(struct hl78xx_data *data);
void (*on_event)(struct hl78xx_data *data, enum hl78xx_event evt);
};
/* Forward declare the table so functions earlier in this file can reference
* it. The table itself is defined later in the file (without 'static').
*/
const static struct hl78xx_state_handlers hl78xx_state_table[];
/** Dispatch an event to the registered event dispatcher, if any.
*
*/
static void event_dispatcher_dispatch(struct hl78xx_evt *notif)
{
if (event_dispatcher != NULL) {
event_dispatcher(notif);
}
}
/* -------------------------------------------------------------------------
* Utilities
* - small helpers and local utility functions
* -------------------------------------------------------------------------
*/
static const char *hl78xx_state_str(enum hl78xx_state state)
{
switch (state) {
case MODEM_HL78XX_STATE_IDLE:
return "idle";
case MODEM_HL78XX_STATE_RESET_PULSE:
return "reset pulse";
case MODEM_HL78XX_STATE_POWER_ON_PULSE:
return "power pulse";
case MODEM_HL78XX_STATE_AWAIT_POWER_ON:
return "await power on";
case MODEM_HL78XX_STATE_SET_BAUDRATE:
return "set baudrate";
case MODEM_HL78XX_STATE_RUN_INIT_SCRIPT:
return "run init script";
case MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT:
return "init fail diagnostic script ";
case MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT:
return "run rat cfg script";
case MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT:
return "run enable gprs script";
case MODEM_HL78XX_STATE_AWAIT_REGISTERED:
return "await registered";
case MODEM_HL78XX_STATE_CARRIER_ON:
return "carrier on";
case MODEM_HL78XX_STATE_CARRIER_OFF:
return "carrier off";
case MODEM_HL78XX_STATE_SIM_POWER_OFF:
return "sim power off";
case MODEM_HL78XX_STATE_AIRPLANE:
return "airplane mode";
case MODEM_HL78XX_STATE_INIT_POWER_OFF:
return "init power off";
case MODEM_HL78XX_STATE_POWER_OFF_PULSE:
return "power off pulse";
case MODEM_HL78XX_STATE_AWAIT_POWER_OFF:
return "await power off";
default:
return "UNKNOWN state";
}
return "";
}
static const char *hl78xx_event_str(enum hl78xx_event event)
{
switch (event) {
case MODEM_HL78XX_EVENT_RESUME:
return "resume";
case MODEM_HL78XX_EVENT_SUSPEND:
return "suspend";
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
return "script success";
case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
return "script failed";
case MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART:
return "script require restart";
case MODEM_HL78XX_EVENT_TIMEOUT:
return "timeout";
case MODEM_HL78XX_EVENT_REGISTERED:
return "registered";
case MODEM_HL78XX_EVENT_DEREGISTERED:
return "deregistered";
case MODEM_HL78XX_EVENT_BUS_OPENED:
return "bus opened";
case MODEM_HL78XX_EVENT_BUS_CLOSED:
return "bus closed";
case MODEM_HL78XX_EVENT_SOCKET_READY:
return "socket ready";
default:
return "unknown event";
}
return "";
}
static bool hl78xx_gpio_is_enabled(const struct gpio_dt_spec *gpio)
{
return (gpio->port != NULL);
}
static void hl78xx_log_event(enum hl78xx_event evt)
{
LOG_DBG("event %s", hl78xx_event_str(evt));
}
static void hl78xx_start_timer(struct hl78xx_data *data, k_timeout_t timeout)
{
k_work_schedule(&data->timeout_work, timeout);
}
static void hl78xx_stop_timer(struct hl78xx_data *data)
{
k_work_cancel_delayable(&data->timeout_work);
}
static void hl78xx_timeout_handler(struct k_work *item)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(item);
struct hl78xx_data *data = CONTAINER_OF(dwork, struct hl78xx_data, timeout_work);
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_TIMEOUT);
}
static void hl78xx_bus_pipe_handler(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
switch (event) {
case MODEM_PIPE_EVENT_OPENED:
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_OPENED);
break;
case MODEM_PIPE_EVENT_CLOSED:
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_CLOSED);
break;
default:
break;
}
}
static void hl78xx_log_state_changed(enum hl78xx_state last_state, enum hl78xx_state new_state)
{
LOG_INF("switch from %s to %s", hl78xx_state_str(last_state), hl78xx_state_str(new_state));
}
static void hl78xx_event_dispatch_handler(struct k_work *item)
{
struct hl78xx_data *data =
CONTAINER_OF(item, struct hl78xx_data, events.event_dispatch_work);
uint8_t events[sizeof(data->events.event_buf)];
uint8_t events_cnt;
k_mutex_lock(&data->events.event_rb_lock, K_FOREVER);
events_cnt = (uint8_t)ring_buf_get(&data->events.event_rb, events,
sizeof(data->events.event_buf));
k_mutex_unlock(&data->events.event_rb_lock);
LOG_DBG("dequeued %d events", events_cnt);
for (uint8_t i = 0; i < events_cnt; i++) {
hl78xx_event_handler(data, (enum hl78xx_event)events[i]);
}
}
void hl78xx_delegate_event(struct hl78xx_data *data, enum hl78xx_event evt)
{
k_mutex_lock(&data->events.event_rb_lock, K_FOREVER);
ring_buf_put(&data->events.event_rb, (uint8_t *)&evt, 1);
k_mutex_unlock(&data->events.event_rb_lock);
k_work_submit_to_queue(&modem_workq, &data->events.event_dispatch_work);
}
/* -------------------------------------------------------------------------
* Chat callbacks / URC handlers
* - unsolicited response handlers and chat-related parsers
* -------------------------------------------------------------------------
*/
void hl78xx_on_cxreg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
enum cellular_registration_status registration_status = 0;
struct hl78xx_evt event = {.type = HL78XX_LTE_REGISTRATION_STAT_UPDATE};
#ifndef CONFIG_MODEM_HL78XX_12
enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE;
struct hl78xx_evt rat_event;
bool rat_mode_updated = false;
int act_value = -1;
#endif /* CONFIG_MODEM_HL78XX_12 */
if (argc < 2) {
return;
}
/* +CXREG: <stat>[,<tac>[...]] */
if (argc > 2 && strlen(argv[1]) == 1 && strlen(argv[2]) == 1) {
/* This is a condition to distinguish received message between URC message and User
* request network status request. If the message is User message, then argv[1] and
* argv[2] will be 1 character long. If the message is URC request network status
* request, then argv[1] will be 1 character long while argv[2] will be 2 characters
* long.
*/
registration_status = ATOI(argv[2], 0, "registration_status");
#ifndef CONFIG_MODEM_HL78XX_12
if (argc > 4 && strlen(argv[5]) == 1) {
act_value = ATOI(argv[5], -1, "act_value");
LOG_DBG("act_value: %d, argc: %d, argv[5]: %s", act_value, argc, argv[5]);
switch (act_value) {
case 7:
rat_mode = HL78XX_RAT_CAT_M1;
break;
case 9:
rat_mode = HL78XX_RAT_NB1;
break;
default:
rat_mode = HL78XX_RAT_MODE_NONE;
break;
}
rat_mode_updated = true;
LOG_DBG("RAT mode from response: %d", rat_mode);
}
#endif /* CONFIG_MODEM_HL78XX_12 */
} else {
registration_status = ATOI(argv[1], 0, "registration_status");
#ifndef CONFIG_MODEM_HL78XX_12
if (argc > 3 && strlen(argv[4]) == 1) {
act_value = ATOI(argv[4], -1, "act_value");
LOG_DBG("act_value: %d, argc: %d, argv[4]: %s", act_value, argc, argv[4]);
switch (act_value) {
case 7:
rat_mode = HL78XX_RAT_CAT_M1;
break;
case 9:
rat_mode = HL78XX_RAT_NB1;
break;
default:
rat_mode = HL78XX_RAT_MODE_NONE;
break;
}
rat_mode_updated = true;
LOG_DBG("RAT mode from URC: %d", rat_mode);
}
#endif /* CONFIG_MODEM_HL78XX_12 */
}
HL78XX_LOG_DBG("%s: %d", argv[0], registration_status);
if (registration_status == data->status.registration.network_state_current) {
#ifndef CONFIG_MODEM_HL78XX_12
/* Check if RAT mode changed even if registration status didn't */
if (rat_mode_updated && rat_mode != -1 &&
rat_mode != data->status.registration.rat_mode) {
data->status.registration.rat_mode = rat_mode;
rat_event.type = HL78XX_LTE_RAT_UPDATE;
rat_event.content.rat_mode = rat_mode;
event_dispatcher_dispatch(&rat_event);
}
#endif /* CONFIG_MODEM_HL78XX_12 */
return;
}
/* Update the previous registration state */
data->status.registration.network_state_previous =
data->status.registration.network_state_current;
/* Update the current registration state */
data->status.registration.network_state_current = registration_status;
event.content.reg_status = data->status.registration.network_state_current;
data->status.registration.is_registered_previously =
data->status.registration.is_registered_currently;
#ifndef CONFIG_MODEM_HL78XX_12
/* Update RAT mode if parsed */
if (rat_mode_updated && rat_mode != -1 && rat_mode != data->status.registration.rat_mode) {
data->status.registration.rat_mode = rat_mode;
rat_event.type = HL78XX_LTE_RAT_UPDATE;
rat_event.content.rat_mode = rat_mode;
event_dispatcher_dispatch(&rat_event);
}
#endif /* CONFIG_MODEM_HL78XX_12 */
/* Update current registration flag */
if (hl78xx_is_registered(data)) {
data->status.registration.is_registered_currently = true;
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_REGISTERED);
#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
k_sem_give(&data->stay_in_boot_mode_sem);
#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
} else {
data->status.registration.is_registered_currently = false;
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_DEREGISTERED);
}
event_dispatcher_dispatch(&event);
}
void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
int module_status;
struct hl78xx_evt event = {.type = HL78XX_LTE_MODEM_STARTUP};
if (argc != 2) {
return;
}
module_status = ATOI(argv[1], 0, "module_status");
event.content.value = module_status;
event_dispatcher_dispatch(&event);
HL78XX_LOG_DBG("Module status: %d", module_status);
}
void hl78xx_on_imei(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc != 2) {
return;
}
HL78XX_LOG_DBG("IMEI: %s %s", argv[0], argv[1]);
k_mutex_lock(&data->api_lock, K_FOREVER);
safe_strncpy((char *)data->identity.imei, argv[1], sizeof(data->identity.imei));
k_mutex_unlock(&data->api_lock);
}
void hl78xx_on_cgmm(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc != 2) {
return;
}
HL78XX_LOG_DBG("cgmm: %s %s", argv[0], argv[1]);
k_mutex_lock(&data->api_lock, K_FOREVER);
safe_strncpy((char *)data->identity.model_id, argv[1], sizeof(data->identity.model_id));
k_mutex_unlock(&data->api_lock);
}
void hl78xx_on_imsi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc != 2) {
return;
}
HL78XX_LOG_DBG("IMSI: %s %s", argv[0], argv[1]);
k_mutex_lock(&data->api_lock, K_FOREVER);
safe_strncpy((char *)data->identity.imsi, argv[1], sizeof(data->identity.imsi));
k_mutex_unlock(&data->api_lock);
#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
/* set the APN automatically */
modem_detect_apn(data, argv[1]);
#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI */
}
void hl78xx_on_cgmi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc != 2) {
return;
}
HL78XX_LOG_DBG("cgmi: %s %s", argv[0], argv[1]);
k_mutex_lock(&data->api_lock, K_FOREVER);
safe_strncpy((char *)data->identity.manufacturer, argv[1],
sizeof(data->identity.manufacturer));
k_mutex_unlock(&data->api_lock);
}
void hl78xx_on_cgmr(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc != 2) {
return;
}
HL78XX_LOG_DBG("cgmr: %s %s", argv[0], argv[1]);
k_mutex_lock(&data->api_lock, K_FOREVER);
safe_strncpy((char *)data->identity.fw_version, argv[1], sizeof(data->identity.fw_version));
k_mutex_unlock(&data->api_lock);
}
void hl78xx_on_iccid(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc != 2) {
return;
}
HL78XX_LOG_DBG("ICCID: %s %s", argv[0], argv[1]);
k_mutex_lock(&data->api_lock, K_FOREVER);
safe_strncpy((char *)data->identity.iccid, argv[1], sizeof(data->identity.iccid));
k_mutex_unlock(&data->api_lock);
#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID)
/* set the APN automatically */
modem_detect_apn(data, argv[1]);
#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID */
}
#if defined(CONFIG_MODEM_HL78XX_12)
void hl78xx_on_kstatev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE;
struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE};
if (argc != 3) {
return;
}
rat_mode = ATOI(argv[2], 0, "rat_mode");
#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG
hl78xx_on_kstatev_parser(data, (enum hl78xx_info_transfer_event)ATOI(argv[1], 0, "status"),
rat_mode);
#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */
if (rat_mode != data->status.registration.rat_mode) {
data->status.registration.rat_mode = rat_mode;
event.content.rat_mode = data->status.registration.rat_mode;
event_dispatcher_dispatch(&event);
}
}
#endif
void hl78xx_on_ksrep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc < 2) {
return;
}
data->status.ksrep = ATOI(argv[1], 0, "ksrep");
HL78XX_LOG_DBG("KSREP: %s %s", argv[0], argv[1]);
}
void hl78xx_on_ksrat(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE};
if (argc < 2) {
return;
}
data->status.registration.rat_mode = (uint8_t)ATOI(argv[1], 0, "rat_mode");
event.content.rat_mode = data->status.registration.rat_mode;
event_dispatcher_dispatch(&event);
HL78XX_LOG_DBG("KSRAT: %s %s", argv[0], argv[1]);
}
void hl78xx_on_kselacq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc < 2) {
return;
}
if (argc > 3) {
data->kselacq_data.mode = 0;
data->kselacq_data.rat1 = ATOI(argv[1], 0, "rat1");
data->kselacq_data.rat2 = ATOI(argv[2], 0, "rat2");
data->kselacq_data.rat3 = ATOI(argv[3], 0, "rat3");
} else {
data->kselacq_data.mode = 0;
data->kselacq_data.rat1 = 0;
data->kselacq_data.rat2 = 0;
data->kselacq_data.rat3 = 0;
}
}
void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
uint8_t rat_id;
uint8_t kbnd_bitmap_size;
if (argc < 3) {
return;
}
rat_id = ATOI(argv[1], 0, "rat");
kbnd_bitmap_size = strlen(argv[2]);
HL78XX_LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]);
if (kbnd_bitmap_size >= MDM_BAND_HEX_STR_LEN) {
LOG_ERR("%d %s Unexpected band bitmap length of %d", __LINE__, __func__,
kbnd_bitmap_size);
return;
}
if (rat_id >= HL78XX_RAT_COUNT) {
return;
}
data->status.kbndcfg[rat_id].rat = rat_id;
strncpy(data->status.kbndcfg[rat_id].bnd_bitmap, argv[2], kbnd_bitmap_size);
data->status.kbndcfg[rat_id].bnd_bitmap[kbnd_bitmap_size] = '\0';
}
void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc < 3) {
return;
}
data->status.rssi = ATOI(argv[1], 0, "rssi");
}
void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc < 7) {
return;
}
data->status.rsrq = ATOI(argv[5], 0, "rsrq");
data->status.rsrp = ATOI(argv[6], 0, "rsrp");
}
void hl78xx_on_cfun(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc < 2) {
return;
}
data->status.phone_functionality.functionality = ATOI(argv[1], 0, "phone_func");
data->status.phone_functionality.in_progress = false;
}
void hl78xx_on_cops(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data)
{
struct hl78xx_data *data = (struct hl78xx_data *)user_data;
if (argc < 3) {
return;
}
safe_strncpy((char *)data->status.network_operator.operator, argv[3],
sizeof(data->status.network_operator.operator));
data->status.network_operator.format = ATOI(argv[2], 0, "network_operator_format");
}
/* -------------------------------------------------------------------------
* Pipe & chat initialization
* - modem backend pipe setup and chat initialisation helpers
* -------------------------------------------------------------------------
*/
static void hl78xx_init_pipe(const struct device *dev)
{
const struct hl78xx_config *cfg = dev->config;
struct hl78xx_data *data = dev->data;
const struct modem_backend_uart_config uart_backend_config = {
.uart = cfg->uart,
.receive_buf = data->buffers.uart_rx,
.receive_buf_size = sizeof(data->buffers.uart_rx),
.transmit_buf = data->buffers.uart_tx,
.transmit_buf_size = ARRAY_SIZE(data->buffers.uart_tx),
};
data->uart_pipe = modem_backend_uart_init(&data->uart_backend, &uart_backend_config);
}
/* Initialize the modem chat subsystem using wrappers from hl78xx_chat.c */
static int modem_init_chat(const struct device *dev)
{
struct hl78xx_data *data = dev->data;
const struct modem_chat_config chat_config = {
.user_data = data,
.receive_buf = data->buffers.chat_rx,
.receive_buf_size = sizeof(data->buffers.chat_rx),
.delimiter = data->buffers.delimiter,
.delimiter_size = strlen(data->buffers.delimiter),
.filter = data->buffers.filter,
.filter_size = data->buffers.filter ? strlen(data->buffers.filter) : 0,
.argv = data->buffers.argv,
.argv_size = (uint16_t)ARRAY_SIZE(data->buffers.argv),
.unsol_matches = hl78xx_get_unsol_matches(),
.unsol_matches_size = (uint16_t)hl78xx_get_unsol_matches_size(),
};
return modem_chat_init(&data->chat, &chat_config);
}
/* clang-format off */
int modem_dynamic_cmd_send(
struct hl78xx_data *data,
modem_chat_script_callback script_user_callback,
const uint8_t *cmd, uint16_t cmd_size,
const struct modem_chat_match *response_matches, uint16_t matches_size,
bool user_cmd
)
{
int ret = 0;
int script_ret = 0;
/* validate input parameters */
if (data == NULL) {
LOG_ERR("%d %s Invalid parameter", __LINE__, __func__);
errno = EINVAL;
return -1;
}
struct modem_chat_script_chat dynamic_script = {
.request = cmd,
.request_size = cmd_size,
.response_matches = response_matches,
.response_matches_size = matches_size,
.timeout = 1000,
};
struct modem_chat_script chat_script = {
.name = "dynamic_script",
.script_chats = &dynamic_script,
.script_chats_size = 1,
.abort_matches = hl78xx_get_abort_matches(),
.abort_matches_size = hl78xx_get_abort_matches_size(),
.callback = script_user_callback,
.timeout = 1000
};
ret = k_mutex_lock(&data->tx_lock, K_NO_WAIT);
if (ret < 0) {
if (user_cmd == false) {
errno = -ret;
}
return -1;
}
/* run the chat script */
script_ret = modem_chat_run_script(&data->chat, &chat_script);
if (script_ret < 0) {
LOG_ERR("%d %s Failed to run at command: %d", __LINE__, __func__, script_ret);
} else {
LOG_DBG("Chat script executed successfully.");
}
ret = k_mutex_unlock(&data->tx_lock);
if (ret < 0) {
if (user_cmd == false) {
errno = -ret;
}
/* we still return the script result if available, prioritize script_ret */
return script_ret < 0 ? -1 : script_ret;
}
return script_ret;
}
/* clang-format on */
/* -------------------------------------------------------------------------
* GPIO ISR callbacks
* - lightweight wrappers for GPIO interrupts (logging & event dispatch)
* -------------------------------------------------------------------------
*/
void mdm_vgpio_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb);
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
const struct gpio_dt_spec *spec = &config->mdm_gpio_vgpio;
if (spec == NULL || spec->port == NULL) {
LOG_ERR("VGPIO GPIO spec is not configured properly");
return;
}
if (!(pins & BIT(spec->pin))) {
return; /* not our pin */
}
LOG_DBG("VGPIO ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec));
}
#if HAS_UART_DSR_GPIO
void mdm_uart_dsr_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb);
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_dsr;
if (spec == NULL || spec->port == NULL) {
LOG_ERR("DSR GPIO spec is not configured properly");
return;
}
if (!(pins & BIT(spec->pin))) {
return; /* not our pin */
}
LOG_DBG("DSR ISR callback %d", gpio_pin_get_dt(spec));
}
#endif
void mdm_gpio6_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb);
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
const struct gpio_dt_spec *spec = &config->mdm_gpio_gpio6;
if (spec == NULL || spec->port == NULL) {
LOG_ERR("GPIO6 GPIO spec is not configured properly");
return;
}
if (!(pins & BIT(spec->pin))) {
return; /* not our pin */
}
LOG_DBG("GPIO6 ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec));
}
void mdm_uart_cts_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb);
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_cts;
if (spec == NULL || spec->port == NULL) {
LOG_ERR("CTS GPIO spec is not configured properly");
return;
}
if (!(pins & BIT(spec->pin))) {
return; /* not our pin */
}
LOG_DBG("CTS ISR callback %d", gpio_pin_get_dt(spec));
}
bool hl78xx_is_registered(struct hl78xx_data *data)
{
return (data->status.registration.network_state_current ==
CELLULAR_REGISTRATION_REGISTERED_HOME) ||
(data->status.registration.network_state_current ==
CELLULAR_REGISTRATION_REGISTERED_ROAMING);
}
/*
* hl78xx_is_registered - convenience helper
*
* Simple predicate to test if the modem reports a registered state.
*/
static int hl78xx_on_reset_pulse_state_enter(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
gpio_pin_set_dt(&config->mdm_gpio_wake, 0);
}
gpio_pin_set_dt(&config->mdm_gpio_reset, 1);
hl78xx_start_timer(data, K_MSEC(config->reset_pulse_duration_ms));
return 0;
}
/* -------------------------------------------------------------------------
* State machine handlers
* - state enter/leave and per-state event handlers
* -------------------------------------------------------------------------
*/
static void hl78xx_reset_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_TIMEOUT:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
default:
break;
}
}
static int hl78xx_on_reset_pulse_state_leave(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
gpio_pin_set_dt(&config->mdm_gpio_reset, 0);
}
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
gpio_pin_set_dt(&config->mdm_gpio_wake, 1);
}
hl78xx_stop_timer(data);
return 0;
}
static int hl78xx_on_power_on_pulse_state_enter(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1);
}
hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms));
return 0;
}
static void hl78xx_power_on_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_TIMEOUT:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
default:
break;
}
}
static int hl78xx_on_power_on_pulse_state_leave(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0);
}
hl78xx_stop_timer(data);
return 0;
}
static int hl78xx_on_await_power_on_state_enter(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
hl78xx_start_timer(data, K_MSEC(config->startup_time_ms));
return 0;
}
static void hl78xx_await_power_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_TIMEOUT:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_SCRIPT);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
default:
break;
}
}
static int hl78xx_on_run_init_script_state_enter(struct hl78xx_data *data)
{
modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data);
return modem_pipe_open_async(data->uart_pipe);
}
static void hl78xx_run_init_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_BUS_OPENED:
modem_chat_attach(&data->chat, data->uart_pipe);
/* Run init script via chat TU wrapper (script symbols live in hl78xx_chat.c) */
hl78xx_run_init_script_async(data);
break;
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT);
break;
case MODEM_HL78XX_EVENT_BUS_CLOSED:
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT);
break;
default:
break;
}
}
static int hl78xx_on_run_init_diagnose_script_state_enter(struct hl78xx_data *data)
{
hl78xx_run_init_fail_script_async(data);
return 0;
}
static void hl78xx_run_init_fail_script_event_handler(struct hl78xx_data *data,
enum hl78xx_event evt)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
switch (evt) {
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
if (data->status.ksrep == 0) {
hl78xx_run_enable_ksup_urc_script_async(data);
hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms));
} else {
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE);
}
}
break;
case MODEM_HL78XX_EVENT_TIMEOUT:
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE);
break;
}
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE);
break;
}
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
case MODEM_HL78XX_EVENT_BUS_CLOSED:
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
if (!hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
LOG_ERR("modem wake pin is not enabled, make sure modem low power is "
"disabled, if you are not sure enable wake up pin by adding it "
"dts!!");
}
if (data->status.script_fail_counter++ < MAX_SCRIPT_AT_CMD_RETRY) {
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE);
break;
}
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE);
break;
}
}
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
default:
break;
}
}
static int hl78xx_on_rat_cfg_script_state_enter(struct hl78xx_data *data)
{
int ret = 0;
bool modem_require_restart = false;
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
enum hl78xx_cell_rat_mode rat_config_request = HL78XX_RAT_MODE_NONE;
const char *cmd_restart = (const char *)SET_AIRPLANE_MODE_CMD;
ret = hl78xx_rat_cfg(data, &modem_require_restart, &rat_config_request);
if (ret < 0) {
goto error;
}
ret = hl78xx_band_cfg(data, &modem_require_restart, rat_config_request);
if (ret < 0) {
goto error;
}
if (modem_require_restart) {
ret = modem_dynamic_cmd_send(data, NULL, cmd_restart, strlen(cmd_restart),
hl78xx_get_ok_match(), 1, false);
if (ret < 0) {
goto error;
}
hl78xx_start_timer(data,
K_MSEC(config->shutdown_time_ms + config->startup_time_ms));
return 0;
}
hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data);
return 0;
error:
hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data);
LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret);
return ret;
}
static void hl78xx_run_rat_cfg_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
int ret = 0;
switch (evt) {
case MODEM_HL78XX_EVENT_TIMEOUT:
LOG_DBG("Rebooting modem to apply new RAT settings");
ret = hl78xx_run_post_restart_script_async(data);
if (ret < 0) {
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND);
}
break;
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
break;
default:
break;
}
}
static int hl78xx_on_await_power_off_state_enter(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms));
return 0;
}
static void hl78xx_await_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
if (evt == MODEM_HL78XX_EVENT_TIMEOUT) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
}
}
static int hl78xx_on_enable_gprs_state_enter(struct hl78xx_data *data)
{
int ret = 0;
/* Apply the APN if not configured yet */
if (data->status.apn.state == APN_STATE_REFRESH_REQUESTED) {
/* APN has been updated by the user, apply the new APN */
HL78XX_LOG_DBG("APN refresh requested, applying new APN: \"%s\"",
data->identity.apn);
data->status.apn.state = APN_STATE_NOT_CONFIGURED;
} else {
#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG)
snprintf(data->identity.apn, sizeof(data->identity.apn), "%s",
CONFIG_MODEM_HL78XX_APN);
#elif defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI)
/* autodetect APN from IMSI */
/* the list of SIM profiles. Global scope, so the app can change it */
/* AT+CCID or AT+CIMI needs to be run here if it is not ran in the init script */
if (strlen(data->identity.apn) < 1) {
LOG_WRN("%d %s APN is left blank", __LINE__, __func__);
}
#else /* defined(CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK) */
/* set blank string to get apn from network */
#endif
}
ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_AIRPLANE, false);
if (ret) {
goto error;
}
ret = hl78xx_set_apn_internal(data, data->identity.apn, strlen(data->identity.apn));
if (ret) {
goto error;
}
#if defined(CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE)
ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_FULLY_FUNCTIONAL, false);
if (ret) {
goto error;
}
#endif /* CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE */
hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data);
return 0;
error:
hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data);
LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret);
return ret;
}
static void hl78xx_enable_gprs_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
hl78xx_start_timer(data, MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT);
break;
case MODEM_HL78XX_EVENT_TIMEOUT:
break;
case MODEM_HL78XX_EVENT_REGISTERED:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
break;
default:
break;
}
}
static int hl78xx_on_await_registered_state_enter(struct hl78xx_data *data)
{
return 0;
}
static void hl78xx_await_registered_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
hl78xx_start_timer(data, K_SECONDS(MDM_REGISTRATION_TIMEOUT));
break;
case MODEM_HL78XX_EVENT_TIMEOUT:
/**
* No need to run periodic script to check registration status because URC is used
* to notify the status change.
*
* If the modem is not registered within the timeout period, it will stay in this
* state forever.
*
* @attention MDM_REGISTRATION_TIMEOUT should be long enough to allow the modem to
* register to the network, especially for the first time registration. And also
* consider the network conditions / number of bands etc.. that may affect
* the registration process.
*
* TODO: add a mechanism to exit this state and retry the registration process
*
*/
LOG_WRN("Modem failed to register to the network within %d seconds",
MDM_REGISTRATION_TIMEOUT);
break;
case MODEM_HL78XX_EVENT_REGISTERED:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
break;
default:
break;
}
}
static int hl78xx_on_await_registered_state_leave(struct hl78xx_data *data)
{
hl78xx_stop_timer(data);
return 0;
}
static int hl78xx_on_carrier_on_state_enter(struct hl78xx_data *data)
{
notif_carrier_on(data->dev);
iface_status_work_cb(data, hl78xx_chat_callback_handler);
return 0;
}
static void hl78xx_carrier_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
hl78xx_start_timer(data, K_SECONDS(2));
break;
case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
break;
case MODEM_HL78XX_EVENT_TIMEOUT:
dns_work_cb(data->dev, true);
break;
case MODEM_HL78XX_EVENT_DEREGISTERED:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
break;
default:
break;
}
}
static int hl78xx_on_carrier_on_state_leave(struct hl78xx_data *data)
{
hl78xx_stop_timer(data);
return 0;
}
static int hl78xx_on_carrier_off_state_enter(struct hl78xx_data *data)
{
notif_carrier_off(data->dev);
/* Check whether or not there is any sockets are connected,
* if true, wait until sockets are closed properly
*/
if (check_if_any_socket_connected(data->dev) == false) {
hl78xx_start_timer(data, K_MSEC(100));
} else {
/* There are still active sockets, wait until they are closed */
hl78xx_start_timer(data, K_MSEC(5000));
}
return 0;
}
static void hl78xx_carrier_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
case MODEM_HL78XX_EVENT_SCRIPT_FAILED:
case MODEM_HL78XX_EVENT_TIMEOUT:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT);
break;
case MODEM_HL78XX_EVENT_DEREGISTERED:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF);
break;
default:
break;
}
}
static int hl78xx_on_carrier_off_state_leave(struct hl78xx_data *data)
{
hl78xx_stop_timer(data);
return 0;
}
/* pwroff script moved to hl78xx_chat.c */
static int hl78xx_on_init_power_off_state_enter(struct hl78xx_data *data)
{
/**
* Eventhough you have power switch or etc.., start the power off script first
* to gracefully disconnect from the network
*
* IMSI detach before powering down IS recommended by the AT command manual
*
*/
return hl78xx_run_pwroff_script_async(data);
}
static void hl78xx_init_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
switch (evt) {
case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS:
hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE);
break;
case MODEM_HL78XX_EVENT_TIMEOUT:
break;
case MODEM_HL78XX_EVENT_DEREGISTERED:
hl78xx_stop_timer(data);
break;
default:
break;
}
}
static int hl78xx_on_init_power_off_state_leave(struct hl78xx_data *data)
{
return 0;
}
static int hl78xx_on_power_off_pulse_state_enter(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1);
}
hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms));
return 0;
}
static void hl78xx_power_off_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
if (evt == MODEM_HL78XX_EVENT_TIMEOUT) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_OFF);
}
}
static int hl78xx_on_power_off_pulse_state_leave(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0);
}
hl78xx_stop_timer(data);
return 0;
}
static int hl78xx_on_idle_state_enter(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
gpio_pin_set_dt(&config->mdm_gpio_wake, 0);
}
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
gpio_pin_set_dt(&config->mdm_gpio_reset, 1);
}
modem_chat_release(&data->chat);
modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data);
modem_pipe_close_async(data->uart_pipe);
k_sem_give(&data->suspended_sem);
return 0;
}
static void hl78xx_idle_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
switch (evt) {
case MODEM_HL78XX_EVENT_BUS_CLOSED:
break;
case MODEM_HL78XX_EVENT_RESUME:
if (config->autostarts) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
break;
}
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE);
break;
}
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON);
break;
}
hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT);
break;
case MODEM_HL78XX_EVENT_SUSPEND:
k_sem_give(&data->suspended_sem);
break;
default:
break;
}
}
static int hl78xx_on_idle_state_leave(struct hl78xx_data *data)
{
const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config;
k_sem_take(&data->suspended_sem, K_NO_WAIT);
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) {
gpio_pin_set_dt(&config->mdm_gpio_reset, 0);
}
if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) {
gpio_pin_set_dt(&config->mdm_gpio_wake, 1);
}
return 0;
}
static int hl78xx_on_state_enter(struct hl78xx_data *data)
{
int ret = 0;
enum hl78xx_state s = data->status.state;
/* Use an explicit bounds check against the last enum value so this
* code can reference the table even though the table is defined later
* in the file. MODEM_HL78XX_STATE_AWAIT_POWER_OFF is the last value in
* the `enum hl78xx_state`.
*/
if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_enter) {
ret = hl78xx_state_table[s].on_enter(data);
}
return ret;
}
static int hl78xx_on_state_leave(struct hl78xx_data *data)
{
int ret = 0;
enum hl78xx_state s = data->status.state;
if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_leave) {
ret = hl78xx_state_table[s].on_leave(data);
}
return ret;
}
void hl78xx_enter_state(struct hl78xx_data *data, enum hl78xx_state state)
{
int ret;
ret = hl78xx_on_state_leave(data);
if (ret < 0) {
LOG_WRN("failed to leave state, error: %i", ret);
return;
}
data->status.state = state;
ret = hl78xx_on_state_enter(data);
if (ret < 0) {
LOG_WRN("failed to enter state error: %i", ret);
}
}
static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt)
{
enum hl78xx_state state;
enum hl78xx_state s;
hl78xx_log_event(evt);
s = data->status.state;
state = data->status.state;
if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_event) {
hl78xx_state_table[s].on_event(data, evt);
} else {
LOG_ERR("%d %s unknown event", __LINE__, __func__);
}
if (state != s) {
hl78xx_log_state_changed(state, s);
}
}
#ifdef CONFIG_PM_DEVICE
/* -------------------------------------------------------------------------
* Power management
* -------------------------------------------------------------------------
*/
static int hl78xx_driver_pm_action(const struct device *dev, enum pm_device_action action)
{
struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
int ret = 0;
LOG_WRN("%d %s PM_DEVICE_ACTION: %d", __LINE__, __func__, action);
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
/* suspend the device */
LOG_DBG("%d PM_DEVICE_ACTION_SUSPEND", __LINE__);
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND);
ret = k_sem_take(&data->suspended_sem, K_SECONDS(30));
break;
case PM_DEVICE_ACTION_RESUME:
LOG_DBG("%d PM_DEVICE_ACTION_RESUME", __LINE__);
/* resume the device */
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME);
break;
case PM_DEVICE_ACTION_TURN_ON:
/*
* powered on the device, used when the power
* domain this device belongs is resumed.
*/
LOG_DBG("%d PM_DEVICE_ACTION_TURN_ON", __LINE__);
break;
case PM_DEVICE_ACTION_TURN_OFF:
/*
* power off the device, used when the power
* domain this device belongs is suspended.
*/
LOG_DBG("%d PM_DEVICE_ACTION_TURN_OFF", __LINE__);
break;
default:
return -ENOTSUP;
}
return ret;
}
#endif /* CONFIG_PM_DEVICE */
/* -------------------------------------------------------------------------
* Initialization
* -------------------------------------------------------------------------
*/
static int hl78xx_init(const struct device *dev)
{
int ret;
const struct hl78xx_config *config = (const struct hl78xx_config *)dev->config;
struct hl78xx_data *data = (struct hl78xx_data *)dev->data;
k_mutex_init(&data->api_lock);
k_mutex_init(&data->tx_lock);
/* Initialize work queue and event handling */
k_work_queue_start(&modem_workq, modem_workq_stack,
K_KERNEL_STACK_SIZEOF(modem_workq_stack), K_PRIO_COOP(7), NULL);
k_work_init_delayable(&data->timeout_work, hl78xx_timeout_handler);
k_work_init(&data->events.event_dispatch_work, hl78xx_event_dispatch_handler);
ring_buf_init(&data->events.event_rb, sizeof(data->events.event_buf),
data->events.event_buf);
k_sem_init(&data->suspended_sem, 0, 1);
#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
k_sem_init(&data->stay_in_boot_mode_sem, 0, 1);
#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */
k_sem_init(&data->script_stopped_sem_tx_int, 0, 1);
k_sem_init(&data->script_stopped_sem_rx_int, 0, 1);
data->dev = dev;
/* reset to default */
data->buffers.eof_pattern_size = strlen(data->buffers.eof_pattern);
data->buffers.termination_pattern_size = strlen(data->buffers.termination_pattern);
memset(data->identity.apn, 0, MDM_APN_MAX_LENGTH);
/* GPIO validation */
const struct gpio_dt_spec *gpio_pins[GPIO_CONFIG_LEN] = {
#if HAS_RESET_GPIO
&config->mdm_gpio_reset,
#endif
#if HAS_WAKE_GPIO
&config->mdm_gpio_wake,
#endif
#if HAS_VGPIO_GPIO
&config->mdm_gpio_vgpio,
#endif
#if HAS_UART_CTS_GPIO
&config->mdm_gpio_uart_cts,
#endif
#if HAS_GPIO6_GPIO
&config->mdm_gpio_gpio6,
#endif
#if HAS_PWR_ON_GPIO
&config->mdm_gpio_pwr_on,
#endif
#if HAS_FAST_SHUTD_GPIO
&config->mdm_gpio_fast_shutdown,
#endif
#if HAS_UART_DSR_GPIO
&config->mdm_gpio_uart_dsr,
#endif
#if HAS_UART_DTR_GPIO
&config->mdm_gpio_uart_dtr,
#endif
#if HAS_GPIO8_GPIO
&config->mdm_gpio_gpio8,
#endif
#if HAS_SIM_SWITCH_GPIO
&config->mdm_gpio_sim_switch,
#endif
};
for (int i = 0; i < ARRAY_SIZE(gpio_pins); i++) {
if (gpio_pins[i] == NULL || !gpio_is_ready_dt(gpio_pins[i])) {
const char *port_name = "unknown";
if (gpio_pins[i] != NULL && gpio_pins[i]->port != NULL) {
port_name = gpio_pins[i]->port->name;
}
LOG_ERR("GPIO port (%s) not ready!", port_name);
return -ENODEV;
}
}
/* GPIO configuration */
struct {
const struct gpio_dt_spec *spec;
gpio_flags_t flags;
const char *name;
} gpio_config[GPIO_CONFIG_LEN] = {
#if HAS_RESET_GPIO
{&config->mdm_gpio_reset, GPIO_OUTPUT, "reset"},
#endif
#if HAS_WAKE_GPIO
{&config->mdm_gpio_wake, GPIO_OUTPUT, "wake"},
#endif
#if HAS_VGPIO_GPIO
{&config->mdm_gpio_vgpio, GPIO_INPUT, "VGPIO"},
#endif
#if HAS_UART_CTS_GPIO
{&config->mdm_gpio_uart_cts, GPIO_INPUT, "CTS"},
#endif
#if HAS_GPIO6_GPIO
{&config->mdm_gpio_gpio6, GPIO_INPUT, "GPIO6"},
#endif
#if HAS_PWR_ON_GPIO
{&config->mdm_gpio_pwr_on, GPIO_OUTPUT, "pwr_on"},
#endif
#if HAS_FAST_SHUTD_GPIO
{&config->mdm_gpio_fast_shutdown, GPIO_OUTPUT, "fast_shutdown"},
#endif
#if HAS_UART_DSR_GPIO
{&config->mdm_gpio_uart_dsr, GPIO_INPUT, "DSR"},
#endif
#if HAS_UART_DTR_GPIO
{&config->mdm_gpio_uart_dtr, GPIO_OUTPUT, "DTR"},
#endif
#if HAS_GPIO8_GPIO
{&config->mdm_gpio_gpio8, GPIO_INPUT, "GPIO8"},
#endif
#if HAS_SIM_SWITCH_GPIO
{&config->mdm_gpio_sim_switch, GPIO_INPUT, "SIM_SWITCH"},
#endif
};
for (int i = 0; i < ARRAY_SIZE(gpio_config); i++) {
ret = gpio_pin_configure_dt(gpio_config[i].spec, gpio_config[i].flags);
if (ret < 0) {
LOG_ERR("Failed to configure %s pin", gpio_config[i].name);
goto error;
}
}
#if HAS_VGPIO_GPIO
/* VGPIO interrupt setup */
gpio_init_callback(&data->gpio_cbs.vgpio_cb, mdm_vgpio_callback_isr,
BIT(config->mdm_gpio_vgpio.pin));
ret = gpio_add_callback(config->mdm_gpio_vgpio.port, &data->gpio_cbs.vgpio_cb);
if (ret) {
LOG_ERR("Cannot setup VGPIO callback! (%d)", ret);
goto error;
}
ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_vgpio, GPIO_INT_EDGE_BOTH);
if (ret) {
LOG_ERR("Error configuring VGPIO interrupt! (%d)", ret);
goto error;
}
#endif /* HAS_VGPIO_GPIO */
#if HAS_GPIO6_GPIO
/* GPIO6 interrupt setup */
gpio_init_callback(&data->gpio_cbs.gpio6_cb, mdm_gpio6_callback_isr,
BIT(config->mdm_gpio_gpio6.pin));
ret = gpio_add_callback(config->mdm_gpio_gpio6.port, &data->gpio_cbs.gpio6_cb);
if (ret) {
LOG_ERR("Cannot setup GPIO6 callback! (%d)", ret);
goto error;
}
ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_gpio6, GPIO_INT_EDGE_BOTH);
if (ret) {
LOG_ERR("Error configuring GPIO6 interrupt! (%d)", ret);
goto error;
}
#endif /* HAS_GPIO6_GPIO */
/* UART pipe initialization */
(void)hl78xx_init_pipe(dev);
ret = modem_init_chat(dev);
if (ret < 0) {
goto error;
}
#ifndef CONFIG_PM_DEVICE
hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME);
#else
pm_device_init_suspended(dev);
#endif /* CONFIG_PM_DEVICE */
#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING
k_sem_take(&data->stay_in_boot_mode_sem, K_FOREVER);
#endif
return 0;
error:
return ret;
}
int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatcher_t handler)
{
event_dispatcher = handler;
return 0;
}
/*
* State handler table
* Maps each hl78xx_state to optional enter/leave/event handlers. NULL
* entries mean the state has no action for that phase.
*/
/* clang-format off */
const static struct hl78xx_state_handlers hl78xx_state_table[] = {
[MODEM_HL78XX_STATE_IDLE] = {
hl78xx_on_idle_state_enter,
hl78xx_on_idle_state_leave,
hl78xx_idle_event_handler
},
[MODEM_HL78XX_STATE_RESET_PULSE] = {
hl78xx_on_reset_pulse_state_enter,
hl78xx_on_reset_pulse_state_leave,
hl78xx_reset_pulse_event_handler
},
[MODEM_HL78XX_STATE_POWER_ON_PULSE] = {
hl78xx_on_power_on_pulse_state_enter,
hl78xx_on_power_on_pulse_state_leave,
hl78xx_power_on_pulse_event_handler
},
[MODEM_HL78XX_STATE_AWAIT_POWER_ON] = {
hl78xx_on_await_power_on_state_enter,
NULL,
hl78xx_await_power_on_event_handler
},
[MODEM_HL78XX_STATE_SET_BAUDRATE] = {
NULL,
NULL,
NULL
},
[MODEM_HL78XX_STATE_RUN_INIT_SCRIPT] = {
hl78xx_on_run_init_script_state_enter,
NULL,
hl78xx_run_init_script_event_handler
},
[MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT] = {
hl78xx_on_run_init_diagnose_script_state_enter,
NULL,
hl78xx_run_init_fail_script_event_handler
},
[MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT] = {
hl78xx_on_rat_cfg_script_state_enter,
NULL,
hl78xx_run_rat_cfg_script_event_handler
},
[MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT] = {
hl78xx_on_enable_gprs_state_enter,
NULL,
hl78xx_enable_gprs_event_handler
},
[MODEM_HL78XX_STATE_AWAIT_REGISTERED] = {
hl78xx_on_await_registered_state_enter,
hl78xx_on_await_registered_state_leave,
hl78xx_await_registered_event_handler
},
[MODEM_HL78XX_STATE_CARRIER_ON] = {
hl78xx_on_carrier_on_state_enter,
hl78xx_on_carrier_on_state_leave,
hl78xx_carrier_on_event_handler
},
[MODEM_HL78XX_STATE_CARRIER_OFF] = {
hl78xx_on_carrier_off_state_enter,
hl78xx_on_carrier_off_state_leave,
hl78xx_carrier_off_event_handler
},
[MODEM_HL78XX_STATE_SIM_POWER_OFF] = {
NULL,
NULL,
NULL
},
[MODEM_HL78XX_STATE_AIRPLANE] = {
NULL,
NULL,
NULL
},
[MODEM_HL78XX_STATE_INIT_POWER_OFF] = {
hl78xx_on_init_power_off_state_enter,
hl78xx_on_init_power_off_state_leave,
hl78xx_init_power_off_event_handler
},
[MODEM_HL78XX_STATE_POWER_OFF_PULSE] = {
hl78xx_on_power_off_pulse_state_enter,
hl78xx_on_power_off_pulse_state_leave,
hl78xx_power_off_pulse_event_handler
},
[MODEM_HL78XX_STATE_AWAIT_POWER_OFF] = {
hl78xx_on_await_power_off_state_enter,
NULL,
hl78xx_await_power_off_event_handler
},
};
/* clang-format on */
static DEVICE_API(cellular, hl78xx_api) = {
.get_signal = hl78xx_api_func_get_signal,
.get_modem_info = hl78xx_api_func_get_modem_info_standard,
.get_registration_status = hl78xx_api_func_get_registration_status,
.set_apn = hl78xx_api_func_set_apn,
.set_callback = NULL,
};
/* -------------------------------------------------------------------------
* Device API and DT registration
* -------------------------------------------------------------------------
*/
#define MODEM_HL78XX_DEFINE_INSTANCE(inst, power_ms, reset_ms, startup_ms, shutdown_ms, start, \
init_script, periodic_script) \
static const struct hl78xx_config hl78xx_cfg_##inst = { \
.uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \
.mdm_gpio_reset = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_reset_gpios, {}), \
.mdm_gpio_wake = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_wake_gpios, {}), \
.mdm_gpio_pwr_on = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_pwr_on_gpios, {}), \
.mdm_gpio_fast_shutdown = \
GPIO_DT_SPEC_INST_GET_OR(inst, mdm_fast_shutd_gpios, {}), \
.mdm_gpio_uart_dtr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dtr_gpios, {}), \
.mdm_gpio_uart_dsr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dsr_gpios, {}), \
.mdm_gpio_uart_cts = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_cts_gpios, {}), \
.mdm_gpio_vgpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_vgpio_gpios, {}), \
.mdm_gpio_gpio6 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio6_gpios, {}), \
.mdm_gpio_gpio8 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio8_gpios, {}), \
.mdm_gpio_sim_switch = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_sim_select_gpios, {}), \
.power_pulse_duration_ms = (power_ms), \
.reset_pulse_duration_ms = (reset_ms), \
.startup_time_ms = (startup_ms), \
.shutdown_time_ms = (shutdown_ms), \
.autostarts = (start), \
.init_chat_script = (init_script), \
.periodic_chat_script = (periodic_script), \
}; \
static struct hl78xx_data hl78xx_data_##inst = { \
.buffers.delimiter = "\r\n", \
.buffers.eof_pattern = EOF_PATTERN, \
.buffers.termination_pattern = TERMINATION_PATTERN, \
}; \
\
PM_DEVICE_DT_INST_DEFINE(inst, hl78xx_driver_pm_action); \
\
DEVICE_DT_INST_DEFINE(inst, hl78xx_init, PM_DEVICE_DT_INST_GET(inst), &hl78xx_data_##inst, \
&hl78xx_cfg_##inst, POST_KERNEL, \
CONFIG_MODEM_HL78XX_DEV_INIT_PRIORITY, &hl78xx_api);
#define MODEM_DEVICE_SWIR_HL78XX(inst) \
MODEM_HL78XX_DEFINE_INSTANCE(inst, CONFIG_MODEM_HL78XX_DEV_POWER_PULSE_DURATION, \
CONFIG_MODEM_HL78XX_DEV_RESET_PULSE_DURATION, \
CONFIG_MODEM_HL78XX_DEV_STARTUP_TIME, \
CONFIG_MODEM_HL78XX_DEV_SHUTDOWN_TIME, false, NULL, NULL)
#define DT_DRV_COMPAT swir_hl7812
DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT swir_hl7800
DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX)
#undef DT_DRV_COMPAT