diff --git a/CMakeLists.txt b/CMakeLists.txt index 23bb7e96..acf54d27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,4 +6,5 @@ zephyr_include_directories(include) +add_subdirectory(drivers) add_subdirectory(lib) diff --git a/Kconfig b/Kconfig index 26968cbb..d5344497 100644 --- a/Kconfig +++ b/Kconfig @@ -5,4 +5,5 @@ # as the module Kconfig entry point (see zephyr/module.yml). You can browse # module options by going to Zephyr -> Modules in Kconfig. +rsource "drivers/Kconfig" rsource "lib/Kconfig" diff --git a/app/boards/nrf9151dk_nrf9151_ns.conf b/app/boards/nrf9151dk_nrf9151_ns.conf index dc6225ac..408ca61f 100644 --- a/app/boards/nrf9151dk_nrf9151_ns.conf +++ b/app/boards/nrf9151dk_nrf9151_ns.conf @@ -9,7 +9,7 @@ # set here will take precedence if they are present in both files. # When working with PC terminal, unmask the following config. -CONFIG_SM_POWER_PIN=8 +CONFIG_SM_POWER_PIN=-1 CONFIG_SM_INDICATE_PIN=0 # When working with external MCU, unmask the following config. diff --git a/app/boards/nrf9151dk_nrf9151_ns.overlay b/app/boards/nrf9151dk_nrf9151_ns.overlay index 2465139c..e7a6528f 100644 --- a/app/boards/nrf9151dk_nrf9151_ns.overlay +++ b/app/boards/nrf9151dk_nrf9151_ns.overlay @@ -6,54 +6,33 @@ / { chosen { - ncs,sm-uart = &uart0; + ncs,sm-uart = &dtr_uart0; }; }; +/* DTR with DK */ &uart0 { status = "okay"; hw-flow-control; -}; -&uart2 { - compatible = "nordic,nrf-uarte"; - current-speed = <115200>; - status = "disabled"; - hw-flow-control; + dtr_uart0: dtr-uart { + compatible = "nordic,dtr-uart"; + dtr-gpios = <&gpio0 8 (GPIO_PULL_UP | GPIO_ACTIVE_HIGH)>; + ri-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>; + status = "okay"; + }; +}; - pinctrl-0 = <&uart2_default_alt>; - pinctrl-1 = <&uart2_sleep_alt>; - pinctrl-names = "default", "sleep"; +&gpio0 { + status = "okay"; + /* Use PORT event for DTR (8) and RI (0) pins to save power */ + sense-edge-mask = <0x00000101>; }; &i2c2 { status = "disabled"; }; -&pinctrl { - uart2_default_alt: uart2_default_alt { - group1 { - psels = ; - bias-pull-up; - }; - group2 { - psels = , - , - ; - }; - }; - - uart2_sleep_alt: uart2_sleep_alt { - group1 { - psels = , - , - , - ; - low-power-enable; - }; - }; -}; - /* Enable external flash */ &gd25wb256 { status = "okay"; diff --git a/app/overlay-cmux.conf b/app/overlay-cmux.conf index fe227930..9389efb9 100644 --- a/app/overlay-cmux.conf +++ b/app/overlay-cmux.conf @@ -11,18 +11,6 @@ CONFIG_SM_SKIP_READY_MSG=y CONFIG_MODEM_MODULES=y CONFIG_MODEM_CMUX=y -# Enable Serial Modem UART backend -CONFIG_MODEM_BACKEND_UART=n -CONFIG_MODEM_BACKEND_UART_ASYNC=n -CONFIG_MODEM_BACKEND_UART_SLM=y -CONFIG_MODEM_BACKEND_UART_SLM_TRANSMIT_TIMEOUT_MS=1000 - -# These buffers are unused after AT#CMUX is enabled -# so use minimal buffer size -CONFIG_SM_UART_RX_BUF_COUNT=2 -CONFIG_SM_UART_RX_BUF_SIZE=128 -CONFIG_SM_UART_TX_BUF_SIZE=128 - # debug options #CONFIG_MODEM_CMUX_LOG_LEVEL_DBG=y #CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y diff --git a/app/overlay-external-mcu-nrf91x1.conf b/app/overlay-external-mcu-nrf91x1.conf new file mode 100644 index 00000000..08995de4 --- /dev/null +++ b/app/overlay-external-mcu-nrf91x1.conf @@ -0,0 +1,9 @@ +# Segger RTT +CONFIG_USE_SEGGER_RTT=n +# Where console messages (printk) are output. +# By itself, SM does not output any. +CONFIG_RTT_CONSOLE=n +CONFIG_UART_CONSOLE=y +# Where logs are output. +CONFIG_LOG_BACKEND_RTT=n +CONFIG_LOG_BACKEND_UART=y diff --git a/app/overlay-external-mcu-nrf91x1.overlay b/app/overlay-external-mcu-nrf91x1.overlay new file mode 100644 index 00000000..9222e508 --- /dev/null +++ b/app/overlay-external-mcu-nrf91x1.overlay @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + ncs,sm-uart = &dtr_uart2; + }; +}; + +/* For logging. TODO: Disable. */ +// &uart0 { +// status = "okay"; +// hw-flow-control; +// }; + +&uart0 { + status = "disabled"; +}; + +&dtr_uart0 { + status = "disabled"; +}; + + +&gpio0 { + status = "okay"; + /* Use PORT event for DTR (31) and RI (30) pins to save power */ + sense-edge-mask = <0xC0000000>; +}; + +/* DTR with external MCU */ +&uart2 { + compatible = "nordic,nrf-uarte"; + current-speed = <115200>; + hw-flow-control; + status = "okay"; + + dtr_uart2: dtr-uart { + compatible = "nordic,dtr-uart"; + dtr-gpios = <&gpio0 31 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + ri-gpios = <&gpio0 30 GPIO_ACTIVE_LOW>; + status = "okay"; + }; + pinctrl-0 = <&uart2_default_alt>; + pinctrl-1 = <&uart2_sleep_alt>; + pinctrl-names = "default", "sleep"; +}; + + +&pinctrl { + uart2_default_alt: uart2_default_alt { + group1 { + psels = ; + bias-pull-up; + }; + group2 { + psels = , + , + ; + }; + }; + + uart2_sleep_alt: uart2_sleep_alt { + group1 { + psels = , + , + , + ; + low-power-enable; + }; + }; +}; diff --git a/app/overlay-ppp-cmux-linux.conf b/app/overlay-ppp-cmux-linux.conf index dda3afed..1809fd09 100644 --- a/app/overlay-ppp-cmux-linux.conf +++ b/app/overlay-ppp-cmux-linux.conf @@ -11,25 +11,12 @@ CONFIG_SM_CR_TERMINATION=y CONFIG_MODEM_CMUX_MTU=127 CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE=536 -CONFIG_SM_CMUX_UART_BUFFER_SIZE=600 -# Enable Serial Modem UART backend -CONFIG_MODEM_BACKEND_UART=n -CONFIG_MODEM_BACKEND_UART_ASYNC=n -CONFIG_MODEM_BACKEND_UART_SLM=y - -# For sending full 600 bytes at 115200 baudrate -# 600 * 10 / 115200 = 52.1 ms -CONFIG_MODEM_BACKEND_UART_SLM_TRANSMIT_TIMEOUT_MS=53 -# Assume at least baudrate 115200 for UART -# so CMUX frame can be received in 12 ms (134*10/115200) -CONFIG_MODEM_BACKEND_UART_SLM_RECEIVE_IDLE_TIMEOUT_MS=12 - -# These buffers are unused after AT#CMUX is enabled -# so use minimal buffer size -CONFIG_SM_UART_RX_BUF_COUNT=2 -CONFIG_SM_UART_RX_BUF_SIZE=128 -CONFIG_SM_UART_TX_BUF_SIZE=128 +# With CMUX, the UART buffers should be at least the size of the +# CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE +CONFIG_SM_UART_RX_BUF_COUNT=3 +CONFIG_SM_UART_RX_BUF_SIZE=256 +CONFIG_SM_UART_TX_BUF_SIZE=768 # When using PPP, disable commands of IP-based protocols to save flash space. CONFIG_SM_FTPC=n diff --git a/app/overlay-ppp-without-cmux.conf b/app/overlay-ppp-without-cmux.conf new file mode 100644 index 00000000..b5bcec14 --- /dev/null +++ b/app/overlay-ppp-without-cmux.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Enable modem UART backend +CONFIG_MODEM_BACKEND_UART=y +CONFIG_MODEM_BACKEND_UART_ASYNC=y diff --git a/app/overlay-ppp.conf b/app/overlay-ppp.conf index 34ef2661..a0a86e5b 100644 --- a/app/overlay-ppp.conf +++ b/app/overlay-ppp.conf @@ -24,12 +24,6 @@ CONFIG_NET_L2_PPP=y CONFIG_MODEM_MODULES=y CONFIG_MODEM_PPP=y -# Enable Serial Modem UART backend -CONFIG_MODEM_BACKEND_UART=n -CONFIG_MODEM_BACKEND_UART_ASYNC=n -CONFIG_MODEM_BACKEND_UART_SLM=y -CONFIG_MODEM_BACKEND_UART_SLM_TRANSMIT_TIMEOUT_MS=1000 - # L2 protocol CONFIG_NET_L2_PPP_MGMT=y CONFIG_NET_L2_PPP_OPTION_MRU=y diff --git a/app/overlay-zephyr-modem-nrf9160dk-nrf52840.conf b/app/overlay-zephyr-modem-nrf9160dk-nrf52840.conf index a7571c85..0732f504 100644 --- a/app/overlay-zephyr-modem-nrf9160dk-nrf52840.conf +++ b/app/overlay-zephyr-modem-nrf9160dk-nrf52840.conf @@ -5,4 +5,30 @@ # # nRF52 <=> nRF91 interface pin 4 (see https://docs.nordicsemi.com/bundle/ug_nrf91_dk/page/UG/nrf91_DK/board_controller.html) -CONFIG_SM_POWER_PIN=22 +CONFIG_SLM_POWER_PIN=-1 + +CONFIG_USE_SEGGER_RTT=n +# Where console messages (printk) are output. +# By itself, SLM does not output any. +CONFIG_RTT_CONSOLE=n +CONFIG_UART_CONSOLE=y +# Where SLM logs are output. +CONFIG_LOG_BACKEND_RTT=n +CONFIG_LOG_BACKEND_UART=y + +CONFIG_SHELL=y +CONFIG_GPIO=y +CONFIG_GPIO_SHELL=y +CONFIG_PM_DEVICE_SHELL=y + +CONFIG_UART_1_INTERRUPT_DRIVEN=n +CONFIG_UART_1_ASYNC=y +CONFIG_UART_USE_RUNTIME_CONFIGURE=y +CONFIG_UART_ASYNC_API=y + +CONFIG_SLM_SMS=n +CONFIG_SLM_GNSS=n +CONFIG_SLM_NRF_CLOUD=n +CONFIG_SLM_GPIO=n + +CONFIG_SIZE_OPTIMIZATIONS_AGGRESSIVE=y diff --git a/app/overlay-zephyr-modem-nrf9160dk-nrf52840.overlay b/app/overlay-zephyr-modem-nrf9160dk-nrf52840.overlay index e9c1b3f0..454fa062 100644 --- a/app/overlay-zephyr-modem-nrf9160dk-nrf52840.overlay +++ b/app/overlay-zephyr-modem-nrf9160dk-nrf52840.overlay @@ -8,11 +8,18 @@ / { chosen { - ncs,sm-uart = &uart1; + ncs,sm-uart = &dtr_uart; }; }; &uart1 { current-speed = <115200>; hw-flow-control; + status = "okay"; + dtr_uart: dtr-uart { + compatible = "nordic,dtr-uart"; + dtr-gpios = <&interface_to_nrf52840 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + ri-gpios = <&interface_to_nrf52840 5 (GPIO_ACTIVE_LOW)>; + status = "okay"; + }; }; diff --git a/app/prj.conf b/app/prj.conf index acb08057..e471f48d 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -29,6 +29,10 @@ CONFIG_LOG_BACKEND_UART=n # errors if Serial Modem does not start properly. CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=2048 +# DTR UART +CONFIG_DTR_UART=y +CONFIG_DTR_UART_LOG_LEVEL_DBG=y + # Network CONFIG_NETWORKING=y CONFIG_NET_SOCKETS=y @@ -133,6 +137,7 @@ CONFIG_SM_EXTERNAL_XTAL=n #CONFIG_SM_LOG_LEVEL_DBG=y #CONFIG_LOG_PRINTK=n #CONFIG_LOG_MODE_IMMEDIATE=y +CONFIG_DEBUG_OPTIMIZATIONS=y # For using external GNSS antenna #CONFIG_MODEM_ANTENNA=y diff --git a/app/sample.yaml b/app/sample.yaml index 7ae3564c..92b99ad7 100644 --- a/app/sample.yaml +++ b/app/sample.yaml @@ -85,6 +85,7 @@ tests: build_only: true extra_args: - EXTRA_CONF_FILE="overlay-ppp.conf" + - EXTRA_CONF_FILE="overlay-ppp-without-cmux.conf" - EXTRA_DTC_OVERLAY_FILE="overlay-ppp-without-cmux.overlay" platform_allow: - nrf9160dk/nrf9160/ns @@ -104,6 +105,7 @@ tests: build_only: true extra_args: - EXTRA_CONF_FILE="overlay-ppp.conf" + - EXTRA_CONF_FILE="overlay-ppp-without-cmux.conf" - EXTRA_DTC_OVERLAY_FILE="overlay-ppp-without-cmux.overlay" extra_configs: - CONFIG_SM_POWER_PIN=31 diff --git a/app/src/sm_at_commands.c b/app/src/sm_at_commands.c index 53c93fe3..6c328da9 100644 --- a/app/src/sm_at_commands.c +++ b/app/src/sm_at_commands.c @@ -78,12 +78,10 @@ enum sleep_modes { SLEEP_MODE_IDLE }; -#if POWER_PIN_IS_ENABLED static struct { struct k_work_delayable work; uint32_t mode; } sleep_control; -#endif bool verify_datamode_control(uint16_t time_limit, uint16_t *time_limit_min); @@ -127,8 +125,6 @@ static int handle_at_slmver(enum at_parser_cmd_type cmd_type, struct at_parser * return ret; } -#if POWER_PIN_IS_ENABLED - static void go_sleep_wk(struct k_work *) { if (sleep_control.mode == SLEEP_MODE_IDLE) { @@ -167,8 +163,6 @@ static int handle_at_sleep(enum at_parser_cmd_type cmd_type, struct at_parser *p return ret; } -#endif /* POWER_PIN_IS_ENABLED */ - static void final_call(void (*func)(void)) { /* Delegate the final call to a worker so that the "OK" response is properly sent. */ @@ -193,13 +187,13 @@ static int handle_at_shutdown(enum at_parser_cmd_type cmd_type, struct at_parser return -EINVAL; } + sm_at_host_uninit(); final_call(sm_shutdown); return 0; } FUNC_NORETURN void sm_reset(void) { - sm_at_host_uninit(); sm_power_off_modem(); LOG_PANIC(); sys_reboot(SYS_REBOOT_COLD); @@ -212,6 +206,7 @@ static int handle_at_reset(enum at_parser_cmd_type cmd_type, struct at_parser *, return -EINVAL; } + sm_at_host_uninit(); final_call(sm_reset); return 0; } @@ -377,40 +372,38 @@ int sm_at_init(void) { int err; -#if POWER_PIN_IS_ENABLED k_work_init_delayable(&sleep_control.work, go_sleep_wk); -#endif err = sm_at_tcp_proxy_init(); if (err) { - LOG_ERR("TCP Server could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "TCP Server", err); return -EFAULT; } err = sm_at_udp_proxy_init(); if (err) { - LOG_ERR("UDP Server could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "UDP Server", err); return -EFAULT; } err = sm_at_socket_init(); if (err) { - LOG_ERR("TCPIP could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "Socket", err); return -EFAULT; } err = sm_at_icmp_init(); if (err) { - LOG_ERR("ICMP could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "ICMP", err); return -EFAULT; } #if defined(CONFIG_SM_SMS) err = sm_at_sms_init(); if (err) { - LOG_ERR("SMS could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "SMS", err); return -EFAULT; } #endif err = sm_at_fota_init(); if (err) { - LOG_ERR("FOTA could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "FOTA", err); return -EFAULT; } #if defined(CONFIG_SM_NRF_CLOUD) @@ -419,63 +412,63 @@ int sm_at_init(void) /* Allow nRF Cloud initialization to fail as sometimes JWT is missing * especially during development. */ - LOG_ERR("nRF Cloud could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "nRF Cloud", err); err = 0; } #endif #if defined(CONFIG_SM_GNSS) err = sm_at_gnss_init(); if (err) { - LOG_ERR("GNSS could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "GNSS", err); return -EFAULT; } #endif #if defined(CONFIG_SM_FTPC) err = sm_at_ftp_init(); if (err) { - LOG_ERR("FTP could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "FTP", err); return -EFAULT; } #endif #if defined(CONFIG_SM_MQTTC) err = sm_at_mqtt_init(); if (err) { - LOG_ERR("MQTT could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "MQTT", err); return -EFAULT; } #endif #if defined(CONFIG_SM_HTTPC) err = sm_at_httpc_init(); if (err) { - LOG_ERR("HTTP could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "HTTP", err); return -EFAULT; } #endif #if defined(CONFIG_SM_GPIO) err = sm_at_gpio_init(); if (err) { - LOG_ERR("GPIO could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "GPIO", err); return -EFAULT; } #endif #if defined(CONFIG_SM_TWI) err = sm_at_twi_init(); if (err) { - LOG_ERR("TWI could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "TWI", err); return -EFAULT; } #endif #if defined(CONFIG_SM_CARRIER) err = sm_at_carrier_init(); if (err) { - LOG_ERR("LwM2M carrier could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "LwM2M carrier", err); return -EFAULT; } #endif #if defined(CONFIG_LWM2M_CARRIER_SETTINGS) err = sm_at_carrier_cfg_init(); if (err) { - LOG_ERR("LwM2M carrier could not be initialized: %d", err); + LOG_ERR("%s initialization failed (%d).", "LwM2M carrier", err); return -EFAULT; } #endif @@ -485,7 +478,7 @@ int sm_at_init(void) #if defined(CONFIG_SM_PPP) err = sm_ppp_init(); if (err) { - LOG_ERR("PPP initialization failed. (%d)", err); + LOG_ERR("%s initialization failed (%d).", "PPP", err); return err; } #endif @@ -498,76 +491,79 @@ void sm_at_uninit(void) err = sm_at_tcp_proxy_uninit(); if (err) { - LOG_WRN("TCP Server could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "TCP Server", err); } err = sm_at_udp_proxy_uninit(); if (err) { - LOG_WRN("UDP Server could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "UDP Server", err); } err = sm_at_socket_uninit(); if (err) { - LOG_WRN("TCPIP could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "Socket", err); } err = sm_at_icmp_uninit(); if (err) { - LOG_WRN("ICMP could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "ICMP", err); } #if defined(CONFIG_SM_SMS) err = sm_at_sms_uninit(); if (err) { - LOG_WRN("SMS could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "SMS", err); } #endif err = sm_at_fota_uninit(); if (err) { - LOG_WRN("FOTA could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "FOTA", err); } #if defined(CONFIG_SM_NRF_CLOUD) err = sm_at_nrfcloud_uninit(); if (err) { - LOG_WRN("nRF Cloud could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "nRF Cloud", err); } #endif #if defined(CONFIG_SM_GNSS) err = sm_at_gnss_uninit(); if (err) { - LOG_WRN("GNSS could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "GNSS", err); } #endif #if defined(CONFIG_SM_FTPC) err = sm_at_ftp_uninit(); if (err) { - LOG_WRN("FTP could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "FTP", err); } #endif #if defined(CONFIG_SM_MQTTC) err = sm_at_mqtt_uninit(); if (err) { - LOG_WRN("MQTT could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "MQTT", err); } #endif #if defined(CONFIG_SM_HTTPC) err = sm_at_httpc_uninit(); if (err) { - LOG_WRN("HTTP could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "HTTP", err); } #endif #if defined(CONFIG_SM_TWI) err = sm_at_twi_uninit(); if (err) { - LOG_ERR("TWI could not be uninit: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "TWI", err); } #endif #if defined(CONFIG_SM_GPIO) err = sm_at_gpio_uninit(); if (err) { - LOG_ERR("GPIO could not be uninit: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "GPIO", err); } #endif #if defined(CONFIG_SM_CARRIER) err = sm_at_carrier_uninit(); if (err) { - LOG_ERR("LwM2M carrier could not be uninitialized: %d", err); + LOG_WRN("%s uninitialization failed (%d).", "LwM2M carrier", err); } #endif +#if defined(CONFIG_SM_CMUX) + sm_cmux_uninit(); +#endif } diff --git a/app/src/sm_at_host.c b/app/src/sm_at_host.c index 2afcce48..1abf1147 100644 --- a/app/src/sm_at_host.c +++ b/app/src/sm_at_host.c @@ -38,7 +38,6 @@ enum sm_operation_mode { SM_DATA_MODE, /* Raw data sending */ SM_NULL_MODE /* Discard incoming until next command */ }; -static struct sm_at_backend at_backend; static enum sm_operation_mode at_mode; static sm_datamode_handler_t datamode_handler; static int datamode_handler_result; @@ -445,76 +444,20 @@ static void format_final_result(char *buf, size_t buf_len, size_t buf_max_len) } } } -static void restore_at_backend(void) -{ - const int err = at_backend.start(); - - if (err) { - LOG_ERR("Failed to restore AT backend. (%d) Resetting.", err); - sm_reset(); - } -} - -static int stop_at_backend(void) -{ - const int err = at_backend.stop(); - - if (!err) { - /* Wait for UART disabling to complete. */ - k_sleep(K_MSEC(100)); - } - return err; -} - -int sm_at_set_backend(const struct sm_at_backend new_backend) -{ - const struct sm_at_backend old_backend = at_backend; - int ret; - - if (old_backend.start) { - ret = stop_at_backend(); - if (ret) { - LOG_ERR("Failed to stop previous AT backend. (%d)", ret); - return ret; - } - } - - at_backend = new_backend; - ret = new_backend.start(); - if (ret) { - LOG_ERR("Failed to start AT backend. (%d)", ret); - stop_at_backend(); - - at_backend = old_backend; - restore_at_backend(); - } - - return ret; -} static int sm_at_send_indicate(const uint8_t *data, size_t len, bool print_full_debug, bool indicate) { int ret; + ARG_UNUSED(indicate); + if (k_is_in_isr()) { LOG_ERR("FIXME: Attempt to send AT response (of size %u) in ISR.", len); return -EINTR; - } else if (at_backend.send == NULL) { - LOG_ERR("Attempt to send via an uninitialized AT backend"); - return -EFAULT; - } - - if (indicate) { - enum pm_device_state state = PM_DEVICE_STATE_OFF; - - pm_device_state_get(sm_uart_dev, &state); - if (state != PM_DEVICE_STATE_ACTIVE) { - sm_ctrl_pin_indicate(); - } } - ret = at_backend.send(data, len); + ret = sm_tx_write(data, len); if (!ret) { LOG_HEXDUMP_DBG(data, print_full_debug ? len : MIN(HEXDUMP_LIMIT, len), "TX"); } @@ -531,13 +474,14 @@ int sm_at_send_str(const char *str) return sm_at_send(str, strlen(str)); } -static void cmd_send(uint8_t *buf, size_t cmd_length, size_t buf_size) +static void cmd_send(uint8_t *buf, size_t cmd_length, size_t buf_size, bool *stop_at_receive) { int err; size_t offset = 0; char *at_cmd = buf; LOG_HEXDUMP_DBG(buf, cmd_length, "RX"); + *stop_at_receive = false; /* UART can send additional characters when the device is powered on. * We ignore everything before the start of the AT-command. @@ -563,6 +507,10 @@ static void cmd_send(uint8_t *buf, size_t cmd_length, size_t buf_size) err = nrf_modem_at_cmd(buf + strlen(CRLF_STR), buf_size - strlen(CRLF_STR), "%s", at_cmd); if (err == -SILENT_AT_COMMAND_RET) { return; + } else if (err == -SILENT_AT_CMUX_COMMAND_RET) { + /* Stop processing AT commands until CMUX pipe is established. */ + *stop_at_receive = true; + return; } else if (err < 0) { LOG_ERR("AT command failed: %d", err); rsp_send_error(); @@ -586,7 +534,7 @@ static void cmd_send(uint8_t *buf, size_t cmd_length, size_t buf_size) } } -static size_t cmd_rx_handler(const uint8_t *buf, const size_t len) +static size_t cmd_rx_handler(const uint8_t *buf, const size_t len, bool *stop_at_receive) { size_t processed; static bool inside_quotes; @@ -594,6 +542,7 @@ static size_t cmd_rx_handler(const uint8_t *buf, const size_t len) static uint8_t prev_character; bool send = false; + *stop_at_receive = false; for (processed = 0; processed < len && send == false; processed++) { /* Handle control characters */ @@ -661,7 +610,7 @@ static size_t cmd_rx_handler(const uint8_t *buf, const size_t len) rsp_send_error(); } else if (at_cmd_len > 0) { sm_at_buf[at_cmd_len] = '\0'; - cmd_send(sm_at_buf, at_cmd_len, sizeof(sm_at_buf)); + cmd_send(sm_at_buf, at_cmd_len, sizeof(sm_at_buf), stop_at_receive); } else { /* Ignore 0 size command. */ } @@ -712,42 +661,47 @@ static size_t null_handler(const uint8_t *buf, const size_t len) return processed; } -void sm_at_receive(const uint8_t *buf, size_t len) +size_t sm_at_receive(const uint8_t *buf, size_t len, bool *stop_at_receive) { size_t ret = 0; + *stop_at_receive = false; + k_timer_stop(&inactivity_timer); - while (len > 0) { + while (ret < len) { switch (get_sm_mode()) { case SM_AT_COMMAND_MODE: - ret = cmd_rx_handler(buf, len); + ret += cmd_rx_handler(buf + ret, len - ret, stop_at_receive); + if (*stop_at_receive) { + return ret; + } break; case SM_DATA_MODE: - ret = raw_rx_handler(buf, len); + ret += raw_rx_handler(buf + ret, len - ret); break; case SM_NULL_MODE: - ret = null_handler(buf, len); + ret += null_handler(buf + ret, len - ret); break; } assert(ret <= len); - buf += ret; - len -= ret; } /* start inactivity timer in datamode */ if (get_sm_mode() == SM_DATA_MODE) { k_timer_start(&inactivity_timer, K_MSEC(sm_datamode_time_limit), K_NO_WAIT); } + + return ret; } AT_MONITOR(at_notify, ANY, notification_handler); static void notification_handler(const char *notification) { - if (get_sm_mode() == SM_AT_COMMAND_MODE && at_backend.send != NULL) { + if (get_sm_mode() == SM_AT_COMMAND_MODE) { #if defined(CONFIG_SM_PPP) if (!sm_fwd_cgev_notifs @@ -1001,22 +955,29 @@ int sm_at_host_init(void) static int at_host_power_off(bool shutting_down) { - int err = stop_at_backend(); + // MARKUS TODO: When we do AT#XSLEEP we only want to power down the DTR uart. + int err = pm_device_action_run(sm_uart_dev, PM_DEVICE_ACTION_SUSPEND); + if (err) { + LOG_WRN("Failed to suspend UART. (%d)", err); + return err; + } - if (!err || shutting_down) { + // // MARKUS TODO: When we do shutdown, we want to do the disable the UART at application level as well. + // int err = sm_uart_handler_disable(); - /* Power off UART module */ - err = pm_device_action_run(sm_uart_dev, PM_DEVICE_ACTION_SUSPEND); - if (err == -EALREADY) { - err = 0; - } - if (err) { - LOG_WRN("Failed to suspend UART. (%d)", err); - if (!shutting_down) { - restore_at_backend(); - } - } - } + // if (!err || shutting_down) { + // /* Wait for UART disabling to complete. */ + // k_sleep(K_MSEC(100)); + + // /* Power off UART module */ + // err = pm_device_action_run(sm_uart_dev, PM_DEVICE_ACTION_SUSPEND); + // if (err == -EALREADY) { + // err = 0; + // } + // if (err) { + // LOG_WRN("Failed to suspend UART. (%d)", err); + // } + // } return err; } @@ -1025,8 +986,12 @@ int sm_at_host_power_off(void) { const int err = at_host_power_off(false); - /* Write sync str to buffer so it is sent first when resuming. */ - sm_at_send_str(SM_SYNC_STR); + /* Write sync str to buffer so it is sent first when resuming. + * Note: This does trigger RI signal. + */ + if (!IS_ENABLED(CONFIG_SM_SKIP_READY_MSG)) { + sm_at_send_str(SM_SYNC_STR); + } return err; } @@ -1040,10 +1005,10 @@ int sm_at_host_power_on(void) return err; } - /* Wait for UART enabling to complete. */ - k_sleep(K_MSEC(100)); + // /* Wait for UART enabling to complete. */ + // k_sleep(K_MSEC(100)); + // sm_uart_handler_enable(); - restore_at_backend(); return 0; } diff --git a/app/src/sm_at_host.h b/app/src/sm_at_host.h index 688d732d..c072df9f 100644 --- a/app/src/sm_at_host.h +++ b/app/src/sm_at_host.h @@ -47,15 +47,6 @@ enum sm_datamode_operation { */ typedef int (*sm_datamode_handler_t)(uint8_t op, const uint8_t *data, int len, uint8_t flags); -/* All the AT backend API functions return 0 on success. */ -struct sm_at_backend { - int (*start)(void); - int (*send)(const uint8_t *data, size_t len); - int (*stop)(void); -}; -/** @retval 0 on success (the new backend is successfully started). */ -int sm_at_set_backend(struct sm_at_backend backend); - /** * @brief Sends the given data via the current AT backend. * @@ -66,8 +57,17 @@ int sm_at_send(const uint8_t *data, size_t len); /** @brief Identical to sm_at_send(str, strlen(str)). */ int sm_at_send_str(const char *str); -/** @brief Processes received AT bytes. */ -void sm_at_receive(const uint8_t *data, size_t len); +/** + * @brief Processes received AT bytes. + * + * @param data AT command bytes received. + * @param len Length of AT command bytes received. + * @param stop_at_receive Pointer to a boolean variable that will be set to true + * if the reception should be stopped. + * + * @retval Number of bytes processed. + */ +size_t sm_at_receive(const uint8_t *data, size_t len, bool *stop_at_receive); /** * @brief Initialize AT host for Serial Modem diff --git a/app/src/sm_cmux.c b/app/src/sm_cmux.c index bfdc8422..eb64e5da 100644 --- a/app/src/sm_cmux.c +++ b/app/src/sm_cmux.c @@ -9,8 +9,8 @@ #include "sm_ppp.h" #endif #include "sm_util.h" +#include "sm_uart_handler.h" #include -#include #include #include #include @@ -35,10 +35,6 @@ static struct { /* UART backend */ struct modem_pipe *uart_pipe; bool uart_pipe_open; - struct modem_backend_uart_slm uart_backend; - uint8_t uart_backend_receive_buf[CONFIG_SM_CMUX_UART_BUFFER_SIZE] - __aligned(sizeof(void *)); - uint8_t uart_backend_transmit_buf[CONFIG_SM_CMUX_UART_BUFFER_SIZE]; /* CMUX */ struct modem_cmux instance; @@ -73,6 +69,7 @@ static void rx_work_fn(struct k_work *work) static uint8_t recv_buf[RECV_BUF_LEN]; int ret; bool is_at; + bool ignored; for (int i = 0; i < ARRAY_SIZE(cmux.dlcis); ++i) { if (atomic_test_and_clear_bit(&cmux.dlci_channel_rx, i)) { @@ -93,7 +90,7 @@ static void rx_work_fn(struct k_work *work) } LOG_DBG("DLCI %u (AT) received %u bytes of data.", INDEX_TO_DLCI(i), ret); - sm_at_receive(recv_buf, ret); + sm_at_receive(recv_buf, ret, &ignored); } } } @@ -290,29 +287,6 @@ static void close_pipe(struct modem_pipe **pipe) } } -static int cmux_stop(void) -{ - if (cmux.uart_pipe && cmux.uart_pipe_open) { - /* CMUX is running. Just close the UART pipe to pause and be able to resume. - * This allows AT data to be cached by the CMUX module while the UART is off. - */ - modem_pipe_close_async(cmux.uart_pipe); - cmux.uart_pipe_open = false; - return 0; - } - - modem_cmux_release(&cmux.instance); - - close_pipe(&cmux.uart_pipe); - cmux.uart_pipe_open = false; - - for (size_t i = 0; i != ARRAY_SIZE(cmux.dlcis); ++i) { - close_pipe(&cmux.dlcis[i].pipe); - } - - return 0; -} - static bool cmux_is_started(void) { return (cmux.uart_pipe != NULL); @@ -344,6 +318,20 @@ void sm_cmux_init(void) cmux.requested_at_channel = UINT_MAX; } +void sm_cmux_uninit(void) +{ + if (cmux_is_started()) { + modem_cmux_release(&cmux.instance); + + close_pipe(&cmux.uart_pipe); + cmux.uart_pipe_open = false; + + for (size_t i = 0; i != ARRAY_SIZE(cmux.dlcis); ++i) { + close_pipe(&cmux.dlcis[i].pipe); + } + } +} + static struct cmux_dlci *cmux_get_dlci(enum cmux_channel channel) { #if defined(CONFIG_SM_PPP) @@ -396,16 +384,7 @@ static int cmux_start(void) } { - const struct modem_backend_uart_slm_config uart_backend_config = { - .uart = DEVICE_DT_GET(DT_CHOSEN(ncs_sm_uart)), - .receive_buf = cmux.uart_backend_receive_buf, - .receive_buf_size = sizeof(cmux.uart_backend_receive_buf), - .transmit_buf = cmux.uart_backend_transmit_buf, - .transmit_buf_size = sizeof(cmux.uart_backend_transmit_buf), - }; - - cmux.uart_pipe = - modem_backend_uart_slm_init(&cmux.uart_backend, &uart_backend_config); + cmux.uart_pipe = sm_uart_pipe_init(cmux_write_at_channel); if (!cmux.uart_pipe) { return -ENODEV; } @@ -423,26 +402,10 @@ static int cmux_start(void) return ret; } -static void cmux_starter(struct k_work *) -{ - const int ret = sm_at_set_backend((struct sm_at_backend) { - .start = cmux_start, - .send = cmux_write_at_channel, - .stop = cmux_stop - }); - - if (ret) { - LOG_ERR("Failed to start CMUX. (%d)", ret); - } else { - LOG_INF("CMUX started."); - } -} - SM_AT_CMD_CUSTOM(xcmux, "AT#XCMUX", handle_at_cmux); static int handle_at_cmux(enum at_parser_cmd_type cmd_type, struct at_parser *parser, uint32_t param_count) { - static struct k_work_delayable cmux_start_work; unsigned int at_dlci; int ret; @@ -480,12 +443,13 @@ static int handle_at_cmux(enum at_parser_cmd_type cmd_type, struct at_parser *pa cmux.at_channel = at_channel; } - k_work_init_delayable(&cmux_start_work, cmux_starter); - ret = k_work_schedule(&cmux_start_work, SM_UART_RESPONSE_DELAY); - if (ret == 1) { - ret = 0; - } else if (ret == 0) { - ret = -EAGAIN; + /* Respond before starting CMUX. */ + rsp_send_ok(); + ret = cmux_start(); + if (ret) { + LOG_ERR("Failed to start CMUX. (%d)", ret); + } else { + ret = -SILENT_AT_CMUX_COMMAND_RET; } return ret; } diff --git a/app/src/sm_cmux.h b/app/src/sm_cmux.h index 8e334fd3..4a6e02d9 100644 --- a/app/src/sm_cmux.h +++ b/app/src/sm_cmux.h @@ -10,8 +10,12 @@ struct modem_pipe; +/** @brief Initialize the CMUX subsystem. */ void sm_cmux_init(void); +/** @brief Uninitialize the CMUX subsystem. */ +void sm_cmux_uninit(void); + /* CMUX channels that are used by other modules. */ enum cmux_channel { #if defined(CONFIG_SM_PPP) diff --git a/app/src/sm_ctrl_pin.c b/app/src/sm_ctrl_pin.c index e5285be5..a561b0a9 100644 --- a/app/src/sm_ctrl_pin.c +++ b/app/src/sm_ctrl_pin.c @@ -22,8 +22,12 @@ LOG_MODULE_REGISTER(sm_ctrl_pin, CONFIG_SM_LOG_LEVEL); static const struct device *gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpio0)); -#if POWER_PIN_IS_ENABLED +static const struct gpio_dt_spec dtr_gpio = + GPIO_DT_SPEC_GET_OR(DT_CHOSEN(ncs_sm_uart), dtr_gpios, {0}); + static struct gpio_callback gpio_cb; + +#if POWER_PIN_IS_ENABLED #else BUILD_ASSERT(!IS_ENABLED(CONFIG_SM_START_SLEEP), "CONFIG_SM_START_SLEEP requires CONFIG_SM_POWER_PIN to be defined."); @@ -225,26 +229,33 @@ static void power_pin_callback_wakeup(const struct device *dev, k_work_submit(&work); } -void sm_ctrl_pin_enter_idle(void) +#endif /* POWER_PIN_IS_ENABLED */ + +static void dtr_enable_fn(struct k_work *) { - LOG_INF("Entering idle."); - int err; + LOG_INF("DTR pin callback work function."); + sm_at_host_power_on(); +} - gpio_remove_callback(gpio_dev, &gpio_cb); +static void dtr_pin_callback(const struct device *dev, struct gpio_callback *gpio_callback, + uint32_t) +{ + static K_WORK_DEFINE(work, dtr_enable_fn); + bool asserted = gpio_pin_get_dt(&dtr_gpio); - err = configure_power_pin_interrupt(power_pin_callback_wakeup, GPIO_INT_LEVEL_LOW); - if (err) { - return; - } + LOG_DBG("DTR pin %s.", asserted ? "asserted" : "de-asserted"); - err = ext_xtal_control(false); - if (err < 0) { - LOG_WRN("Failed to disable ext XTAL: %d", err); + if (asserted) { + gpio_remove_callback(dev, gpio_callback); + k_work_submit(&work); } } void sm_ctrl_pin_enter_sleep(void) { + int err; + + /* Stop threads, uninitialize host and disable DTR UART. */ sm_at_host_uninit(); /* Only power off the modem if it has not been put @@ -253,14 +264,24 @@ void sm_ctrl_pin_enter_sleep(void) if (!sm_is_modem_functional_mode(LTE_LC_FUNC_MODE_OFFLINE)) { sm_power_off_modem(); } - sm_ctrl_pin_enter_sleep_no_uninit(); + + gpio_pin_interrupt_configure_dt(&dtr_gpio, GPIO_INT_DISABLE); + + LOG_INF("Entering sleep."); + LOG_PANIC(); + nrf_gpio_cfg_sense_set(dtr_gpio.pin, NRF_GPIO_PIN_SENSE_LOW); // MARKUS TODO: Correct sense. And do not do this here. + + k_sleep(K_MSEC(100)); + + nrf_regulators_system_off(NRF_REGULATORS_NS); + assert(false); } void sm_ctrl_pin_enter_sleep_no_uninit(void) { LOG_INF("Entering sleep."); LOG_PANIC(); - nrf_gpio_cfg_sense_set(CONFIG_SM_POWER_PIN, NRF_GPIO_PIN_SENSE_LOW); + nrf_gpio_cfg_sense_set(dtr_gpio.pin, NRF_GPIO_PIN_SENSE_LOW); k_sleep(K_MSEC(100)); @@ -268,25 +289,23 @@ void sm_ctrl_pin_enter_sleep_no_uninit(void) assert(false); } -#endif /* POWER_PIN_IS_ENABLED */ - -int sm_ctrl_pin_indicate(void) +void sm_ctrl_pin_enter_idle(void) { - int err = 0; + LOG_INF("Entering idle."); + int err; -#if INDICATE_PIN_IS_ENABLED - if (k_work_delayable_is_pending(&indicate_work)) { - return 0; - } - LOG_DBG("Start indicating"); - err = gpio_pin_set(gpio_dev, CONFIG_SM_INDICATE_PIN, 1); + gpio_init_callback(&gpio_cb, dtr_pin_callback, BIT(dtr_gpio.pin)); + + err = gpio_add_callback_dt(&dtr_gpio, &gpio_cb); if (err) { - LOG_ERR("GPIO_0 set error: %d", err); - } else { - k_work_reschedule(&indicate_work, K_MSEC(CONFIG_SM_INDICATE_TIME)); + LOG_ERR("gpio_add_callback failed: %d", err); + return; + } + + err = ext_xtal_control(false); + if (err < 0) { + LOG_WRN("Failed to disable ext XTAL: %d", err); } -#endif - return err; } void sm_ctrl_pin_enter_shutdown(void) diff --git a/app/src/sm_defines.h b/app/src/sm_defines.h index 709d3e20..6731784f 100644 --- a/app/src/sm_defines.h +++ b/app/src/sm_defines.h @@ -17,6 +17,8 @@ enum { /* The command ran successfully and doesn't want the automatic response to be sent. */ SILENT_AT_COMMAND_RET = __ELASTERROR, + /* The AT to CMUX change command ran successfully. */ + SILENT_AT_CMUX_COMMAND_RET, }; /** The maximum allowed length of an AT command/response passed through the Serial Modem */ diff --git a/app/src/sm_ppp.c b/app/src/sm_ppp.c index 5d919e2a..c894dabe 100644 --- a/app/src/sm_ppp.c +++ b/app/src/sm_ppp.c @@ -14,7 +14,9 @@ #include #include #include -#include +#if !defined(CONFIG_SM_CMUX) +#include +#endif #include #include #include @@ -616,12 +618,12 @@ int sm_ppp_init(void) } { - static struct modem_backend_uart_slm ppp_uart_backend; + static struct modem_backend_uart ppp_uart_backend; static uint8_t ppp_uart_backend_receive_buf[sizeof(ppp_data_buf)] __aligned(sizeof(void *)); static uint8_t ppp_uart_backend_transmit_buf[sizeof(ppp_data_buf)]; - const struct modem_backend_uart_slm_config uart_backend_config = { + const struct modem_backend_uart_config uart_backend_config = { .uart = ppp_uart_dev, .receive_buf = ppp_uart_backend_receive_buf, .receive_buf_size = sizeof(ppp_uart_backend_receive_buf), @@ -629,7 +631,7 @@ int sm_ppp_init(void) .transmit_buf_size = sizeof(ppp_uart_backend_transmit_buf), }; - ppp_pipe = modem_backend_uart_slm_init(&ppp_uart_backend, &uart_backend_config); + ppp_pipe = modem_backend_uart_init(&ppp_uart_backend, &uart_backend_config); if (!ppp_pipe) { return -ENOSYS; } @@ -703,7 +705,6 @@ static void ppp_data_passing_thread(void*, void*, void*) { const size_t mtu = net_if_get_mtu(ppp_iface); struct zsock_pollfd fds[PPP_FDS_COUNT]; - enum pm_device_state state = PM_DEVICE_STATE_OFF; for (size_t i = 0; i != ARRAY_SIZE(fds); ++i) { fds[i].fd = ppp_fds[i]; @@ -743,14 +744,6 @@ static void ppp_data_passing_thread(void*, void*, void*) return; } - /* When DL data is received from the network, check if UART is suspended */ - if (src == MODEM_FD_IDX) { - pm_device_state_get(ppp_uart_dev, &state); - if (state != PM_DEVICE_STATE_ACTIVE) { - LOG_DBG("PPP data received but UART not active"); - sm_ctrl_pin_indicate(); - } - } const ssize_t len = zsock_recv(fds[src].fd, ppp_data_buf, mtu, ZSOCK_MSG_DONTWAIT); diff --git a/app/src/sm_uart_handler.c b/app/src/sm_uart_handler.c index 0b19f8e0..49e3b756 100644 --- a/app/src/sm_uart_handler.c +++ b/app/src/sm_uart_handler.c @@ -6,17 +6,23 @@ #include #include +#include #include #include #include +#include #include #include +#include +#include #include "sm_uart_handler.h" #include "sm_at_host.h" #include LOG_MODULE_REGISTER(sm_uart_handler, CONFIG_SM_LOG_LEVEL); +#define SM_PIPE CONFIG_SM_CMUX + #define UART_RX_TIMEOUT_US 2000 #define UART_ERROR_DELAY_MS 500 @@ -49,12 +55,30 @@ K_MSGQ_DEFINE(rx_event_queue, sizeof(struct rx_event_t), UART_RX_EVENT_COUNT, 4) RING_BUF_DECLARE(tx_buf, CONFIG_SM_UART_TX_BUF_SIZE); K_MUTEX_DEFINE(mutex_tx_put); /* Protects the tx_buf from multiple writes. */ -enum uart_recovery_state { - RECOVERY_IDLE, - RECOVERY_ONGOING, - RECOVERY_DISABLED +enum sm_uart_state { + SM_TX_ENABLED_BIT, + SM_RX_ENABLED_BIT, + SM_RX_RECOVERY_BIT, + SM_RX_RECOVERY_DISABLED_BIT +}; +static atomic_t uart_state; + +#if SM_PIPE + +enum sm_pipe_state { + SM_PIPE_INIT_BIT, + SM_PIPE_OPEN_BIT, }; -static atomic_t recovery_state; +static struct { + struct modem_pipe pipe; + sm_pipe_tx_t tx_cb; + atomic_t state; + + struct k_work notify_transmit_idle; + struct k_work notify_closed; +} sm_pipe; + +#endif K_SEM_DEFINE(tx_done_sem, 0, 1); @@ -112,6 +136,10 @@ static int rx_enable(void) struct rx_buf_t *buf; int ret; + if (atomic_test_bit(&uart_state, SM_RX_ENABLED_BIT)) { + return 0; + } + buf = rx_buf_alloc(); if (!buf) { LOG_ERR("UART RX failed to allocate buffer"); @@ -121,25 +149,28 @@ static int rx_enable(void) ret = uart_rx_enable(sm_uart_dev, buf->buf, sizeof(buf->buf), UART_RX_TIMEOUT_US); if (ret) { LOG_ERR("UART RX enable failed: %d", ret); + rx_buf_unref(buf); return ret; } + atomic_set_bit(&uart_state, SM_RX_ENABLED_BIT); return 0; } -static int sm_uart_rx_disable(void) +static int rx_disable(void) { int err; - /* Wait for possible rx_enable to complete. */ - if (atomic_set(&recovery_state, RECOVERY_DISABLED) == RECOVERY_ONGOING) { + atomic_set_bit(&uart_state, SM_RX_RECOVERY_DISABLED_BIT); + + while (atomic_test_bit(&uart_state, SM_RX_RECOVERY_BIT)) { + /* Wait until possible recovery is complete. */ k_sleep(K_MSEC(10)); } err = uart_rx_disable(sm_uart_dev); if (err) { LOG_ERR("UART RX disable failed: %d", err); - atomic_set(&recovery_state, RECOVERY_IDLE); return err; } @@ -150,41 +181,99 @@ static void rx_recovery(void) { int err; - if (atomic_get(&recovery_state) != RECOVERY_ONGOING) { + if (atomic_test_bit(&uart_state, SM_RX_RECOVERY_DISABLED_BIT)) { return; } + atomic_set_bit(&uart_state, SM_RX_RECOVERY_BIT); + err = rx_enable(); if (err) { k_work_schedule(&rx_process_work, K_MSEC(UART_RX_MARGIN_MS)); - return; } - atomic_cas(&recovery_state, RECOVERY_ONGOING, RECOVERY_IDLE); + atomic_clear_bit(&uart_state, SM_RX_RECOVERY_BIT); } static void rx_process(struct k_work *work) { +#if SM_PIPE + /* With pipe, CMUX layer is notified and it requests the data. */ + if (atomic_test_bit(&sm_pipe.state, SM_PIPE_INIT_BIT)) { + if (atomic_test_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT)) { + modem_pipe_notify_receive_ready(&sm_pipe.pipe); + } + return; + } +#endif + /* Without pipe, we push the data immediately. */ struct rx_event_t rx_event; + size_t processed; + bool stop_at_receive = false; + int err; while (k_msgq_get(&rx_event_queue, &rx_event, K_NO_WAIT) == 0) { - sm_at_receive(rx_event.buf, rx_event.len); - rx_buf_unref(rx_event.buf); + processed = sm_at_receive(rx_event.buf, rx_event.len, &stop_at_receive); + + if (processed == rx_event.len) { + /* All data processed, release the buffer. */ + rx_buf_unref(rx_event.buf); + } else { + rx_event.len -= processed; + rx_event.buf += processed; + err = k_msgq_put_front(&rx_event_queue, &rx_event, K_NO_WAIT); + if (err) { + LOG_ERR("RX event queue full, dropped %zu bytes", rx_event.len); + rx_buf_unref(rx_event.buf); + } + } + + if (stop_at_receive) { + break; + } } rx_recovery(); } +static void tx_enable(void) +{ + if (!atomic_test_and_set_bit(&uart_state, SM_TX_ENABLED_BIT)) { + k_sem_give(&tx_done_sem); + } +} + +static int tx_disable(k_timeout_t timeout) +{ + int err; + + if (!atomic_test_and_clear_bit(&uart_state, SM_TX_ENABLED_BIT)) { + return 0; + } + + if (k_sem_take(&tx_done_sem, timeout) == 0) { + return 0; + } + + err = uart_tx_abort(sm_uart_dev); + if (!err) { + LOG_INF("TX aborted"); + } else if (err != -EFAULT) { + LOG_ERR("uart_tx_abort failed (%d).", err); + return err; + } + + return 0; +} + static int tx_start(void) { uint8_t *buf; size_t len; int err; - enum pm_device_state state = PM_DEVICE_STATE_OFF; - pm_device_state_get(sm_uart_dev, &state); - if (state != PM_DEVICE_STATE_ACTIVE) { - return 1; + if (!atomic_test_bit(&uart_state, SM_TX_ENABLED_BIT)) { + return -EAGAIN; } len = ring_buf_get_claim(&tx_buf, &buf, ring_buf_capacity_get(&tx_buf)); @@ -198,6 +287,28 @@ static int tx_start(void) return 0; } +static inline void uart_callback_notify_pipe_transmit_idle(void) +{ +#if SM_PIPE + if (atomic_test_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT)) { + k_work_submit(&sm_pipe.notify_transmit_idle); + } +#endif +} + +static inline void uart_callback_notify_pipe_closure(void) +{ +#if SM_PIPE + if (atomic_test_bit(&sm_pipe.state, SM_PIPE_INIT_BIT) && + !atomic_test_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT) && + !atomic_test_bit(&uart_state, SM_RX_ENABLED_BIT) && + !atomic_test_bit(&uart_state, SM_TX_ENABLED_BIT)) { + /* Pipe is closed, RX and TX are idle, notify the closure */ + k_work_submit(&sm_pipe.notify_closed); + } +#endif +} + static void uart_callback(const struct device *dev, struct uart_event *evt, void *user_data) { struct rx_buf_t *buf; @@ -215,10 +326,11 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void LOG_ERR("UART_TX_%s failure: %d", (evt->type == UART_TX_DONE) ? "DONE" : "ABORTED", err); } - if (ring_buf_is_empty(&tx_buf) == false) { + if (ring_buf_is_empty(&tx_buf) == false && evt->type == UART_TX_DONE) { tx_start(); } else { k_sem_give(&tx_done_sem); + uart_callback_notify_pipe_transmit_idle(); } break; case UART_RX_RDY: @@ -227,7 +339,7 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void rx_event.len = evt->data.rx.len; err = k_msgq_put(&rx_event_queue, &rx_event, K_NO_WAIT); if (err) { - LOG_ERR("UART_RX_RDY failure: %d, dropped: %d", err, evt->data.rx.len); + LOG_ERR("RX event queue full, dropped %zu bytes", evt->data.rx.len); rx_buf_unref(evt->data.rx.buf); break; } @@ -255,16 +367,20 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void } break; case UART_RX_DISABLED: - if (atomic_cas(&recovery_state, RECOVERY_IDLE, RECOVERY_ONGOING)) { - k_work_submit((struct k_work *)&rx_process_work); - } + LOG_INF("UART_RX_DISABLED"); + atomic_clear_bit(&uart_state, SM_RX_ENABLED_BIT); + k_work_submit((struct k_work *)&rx_process_work); break; default: break; } + + uart_callback_notify_pipe_closure(); } -/* Write the data to tx_buffer and trigger sending. */ +/* Write the data to tx_buffer and trigger sending. Repeat until everything is sent. + * Returns 0 on success or a negative error code. + */ static int sm_uart_tx_write(const uint8_t *data, size_t len) { size_t ret; @@ -272,22 +388,29 @@ static int sm_uart_tx_write(const uint8_t *data, size_t len) int err; k_mutex_lock(&mutex_tx_put, K_FOREVER); + while (sent < len) { ret = ring_buf_put(&tx_buf, data + sent, len - sent); if (ret) { sent += ret; - } else { - /* Buffer full, block and start TX. */ - k_sem_take(&tx_done_sem, K_FOREVER); - err = tx_start(); - if (err) { - LOG_ERR("TX buf overflow, %d dropped. Unable to send: %d", - len - sent, - err); - k_sem_give(&tx_done_sem); - k_mutex_unlock(&mutex_tx_put); - return err; - } + continue; + } + + /* Buffer full, block and start TX. */ + err = k_sem_take(&tx_done_sem, K_FOREVER); + if (err) { + LOG_ERR("TX %s failed (%d). TX buf overflow, %zu dropped.", + "semaphore take", err, len - sent); + k_mutex_unlock(&mutex_tx_put); + return err; + } + err = tx_start(); + if (err && err != -EAGAIN) { + LOG_ERR("TX %s failed (%d). TX buf overflow, %zu dropped.", "start", err, + len - sent); + k_sem_give(&tx_done_sem); + k_mutex_unlock(&mutex_tx_put); + return err; } } k_mutex_unlock(&mutex_tx_put); @@ -297,8 +420,8 @@ static int sm_uart_tx_write(const uint8_t *data, size_t len) if (err == 1) { k_sem_give(&tx_done_sem); return 0; - } else if (err) { - LOG_ERR("TX start failed: %d", err); + } else if (err && err != -EAGAIN) { + LOG_ERR("TX %s failed (%d).", "start", err); k_sem_give(&tx_done_sem); return err; } @@ -309,7 +432,17 @@ static int sm_uart_tx_write(const uint8_t *data, size_t len) return 0; } -static int sm_uart_handler_init(void) +int sm_tx_write(const uint8_t *data, size_t len) +{ +#if SM_PIPE + if (atomic_test_bit(&sm_pipe.state, SM_PIPE_INIT_BIT) && sm_pipe.tx_cb != NULL) { + return sm_pipe.tx_cb(data, len); + } +#endif + return sm_uart_tx_write(data, len); +} + +int sm_uart_handler_enable(void) { int err; uint32_t start_time; @@ -350,7 +483,9 @@ static int sm_uart_handler_init(void) LOG_ERR("Cannot set callback: %d", err); return -EFAULT; } - (void)atomic_set(&recovery_state, RECOVERY_IDLE); + + atomic_clear(&uart_state); + tx_enable(); err = rx_enable(); if (err) { return -EFAULT; @@ -358,19 +493,220 @@ static int sm_uart_handler_init(void) k_work_init_delayable(&rx_process_work, rx_process); - k_sem_give(&tx_done_sem); - /* Flush possibly pending data in case Serial Modem was idle. */ tx_start(); return 0; } -int sm_uart_handler_enable(void) +int sm_uart_handler_disable(void) { - return sm_at_set_backend((struct sm_at_backend) { - .start = sm_uart_handler_init, - .send = sm_uart_tx_write, - .stop = sm_uart_rx_disable - }); + int err; + + err = tx_disable(K_MSEC(50)); + if (err) { + LOG_ERR("TX disable failed (%d).", err); + return err; + } + + err = rx_disable(); + if (err) { + LOG_ERR("RX disable failed (%d).", err); + return err; + } + + return 0; } + +#if SM_PIPE + +static int pipe_open(void *data) +{ + int ret; + + ARG_UNUSED(data); + + if (!atomic_test_bit(&sm_pipe.state, SM_PIPE_INIT_BIT)) { + return -EINVAL; + } + + if (atomic_test_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT)) { + return -EALREADY; + } + + atomic_clear_bit(&uart_state, SM_RX_RECOVERY_DISABLED_BIT); + ret = rx_enable(); + if (ret) { + return ret; + } + + tx_enable(); + + atomic_set_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT); + modem_pipe_notify_opened(&sm_pipe.pipe); + + return 0; +} + +/* Returns the number of bytes written or a negative error code. */ +static int pipe_transmit(void *data, const uint8_t *buf, size_t size) +{ + size_t ret; + size_t sent = 0; + + ARG_UNUSED(data); + + if (!atomic_test_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT)) { + return -EPERM; + } + + if (!buf || size == 0) { + return -EINVAL; + } + + while (sent < size) { + ret = ring_buf_put(&tx_buf, buf + sent, size - sent); + if (ret) { + sent += ret; + } else { + /* Buffer full. */ + break; + } + } + + if (k_sem_take(&tx_done_sem, K_NO_WAIT) == 0) { + int err = tx_start(); + + if (err == 1 || err == -EAGAIN) { + k_sem_give(&tx_done_sem); + return (int)sent; + } else if (err) { + LOG_ERR("TX %s failed (%d).", "start", err); + k_sem_give(&tx_done_sem); + return err; + } + } + + return (int)sent; +} + +static int pipe_receive(void *data, uint8_t *buf, size_t size) +{ + struct rx_event_t rx_event; + size_t received = 0; + size_t copy_size; + int err; + + ARG_UNUSED(data); + + if (!buf || size == 0) { + return 0; + } + + while (size > received) { + if (k_msgq_get(&rx_event_queue, &rx_event, K_NO_WAIT)) { + break; + } + copy_size = MIN(size - received, rx_event.len); + memcpy(buf, rx_event.buf, copy_size); + received += copy_size; + buf += copy_size; + + if (rx_event.len == copy_size) { + rx_buf_unref(rx_event.buf); + } else { + rx_event.len -= copy_size; + rx_event.buf += copy_size; + err = k_msgq_put_front(&rx_event_queue, &rx_event, K_NO_WAIT); + if (err) { + LOG_ERR("RX event queue full, dropped %zu bytes", rx_event.len); + rx_buf_unref(rx_event.buf); + } + } + } + if (k_msgq_num_used_get(&rx_event_queue) == 0) { + /* Try to recover RX, in case it was disabled. */ + rx_recovery(); + } + + return (int)received; +} + +static int pipe_close(void *data) +{ + int err; + + ARG_UNUSED(data); + + if (!atomic_test_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT)) { + return -EALREADY; + } + + atomic_clear_bit(&sm_pipe.state, SM_PIPE_OPEN_BIT); + + err = tx_disable(K_MSEC(50)); + if (err) { + LOG_WRN("%s disable failed (%d).", "TX", err); + } + + err = rx_disable(); + if (err) { + LOG_ERR("%s disable failed (%d).", "RX", err); + } + + return 0; +} + +static const struct modem_pipe_api modem_pipe_api = { + .open = pipe_open, + .transmit = pipe_transmit, + .receive = pipe_receive, + .close = pipe_close, +}; + +static void notify_transmit_idle_fn(struct k_work *work) +{ + ARG_UNUSED(work); + modem_pipe_notify_transmit_idle(&sm_pipe.pipe); +} +static void notify_closed_fn(struct k_work *work) +{ + ARG_UNUSED(work); + modem_pipe_notify_closed(&sm_pipe.pipe); +} + +static void at_to_cmux_switch(void) +{ + /* TX handling when moving from AT to CMUX: + * - Complete (OK message) TX transmission through regular UART. + */ + tx_disable(K_MSEC(10)); + + /* Possible TX handling improvements: + * - Callers in sm_uart_tx_write need to abort when CMUX change is done. There may be + * multiple callers and they may be blocked. + * - TX buffer must be flushed or the data must be routed to CMUX. + */ + + /* RX handling when moving from AT to CMUX: + * - RX and RX buffers are retained. + * - Data in RX buffers is routed to CMUX AT channel. + */ +} + +struct modem_pipe *sm_uart_pipe_init(sm_pipe_tx_t pipe_tx_cb) +{ + k_work_init(&sm_pipe.notify_transmit_idle, notify_transmit_idle_fn); + k_work_init(&sm_pipe.notify_closed, notify_closed_fn); + + sm_pipe.tx_cb = pipe_tx_cb; + atomic_set_bit(&sm_pipe.state, SM_PIPE_INIT_BIT); + + modem_pipe_init(&sm_pipe.pipe, &sm_pipe, &modem_pipe_api); + + at_to_cmux_switch(); + + return &sm_pipe.pipe; +} + +#endif /* SM_PIPE */ diff --git a/app/src/sm_uart_handler.h b/app/src/sm_uart_handler.h index 54baa04e..9046030d 100644 --- a/app/src/sm_uart_handler.h +++ b/app/src/sm_uart_handler.h @@ -9,20 +9,56 @@ /** @file sm_uart_handler.h * - * @brief pure UART handler for Serial Modem + * @brief pure UART handler for serial LTE modem * @{ */ #include "sm_trap_macros.h" #include +#include #define UART_RX_MARGIN_MS 10 extern const struct device *const sm_uart_dev; extern uint32_t sm_uart_baudrate; -/** @retval 0 on success. Otherwise, the error code is returned. */ +/** + * @brief UART pipe transmit callback type. + * + * @retval Amount of bytes written on success, otherwise a negative error code. + */ +typedef int (*sm_pipe_tx_t)(const uint8_t *data, size_t len); + +/** @brief Enable the UART handler. + * + * @retval 0 on success. Otherwise, a negative error code. + */ int sm_uart_handler_enable(void); +/** @brief Disable the UART handler. + * + * @retval 0 on success. Otherwise, a negative error code. + */ +int sm_uart_handler_disable(void); + +/** + * @brief Write data to UART or to a modem pipe. + * + * @param data Data to write. + * @param len Length of data to write. + * + * @retval 0 on success. Otherwise, a negative error code. + */ +int sm_tx_write(const uint8_t *data, size_t len); + +/** + * @brief Initialize UART pipe for Serial Modem. + * + * @param pipe_tx Transmit callback for the pipe. + * + * @retval Pointer to the initialized pipe on success, NULL otherwise. + */ +struct modem_pipe *sm_uart_pipe_init(sm_pipe_tx_t pipe_tx); + /** @} */ #endif /* SM_UART_HANDLER_ */ diff --git a/doc/app/sm_description.rst b/doc/app/sm_description.rst index 880e75fe..1f89618f 100644 --- a/doc/app/sm_description.rst +++ b/doc/app/sm_description.rst @@ -340,8 +340,11 @@ The following configuration files are provided: This disables most of the IP-based protocols available through AT commands (such as FTP and MQTT) as it is expected that the controlling chip's own IP stack is used instead. See :ref:`CONFIG_SM_PPP ` and :ref:`SM_AT_PPP` for more information. -* :file:`overlay-ppp-without-cmux.overlay` - Devicetree overlay that configures the UART to be used by PPP. - This configuration file should be included when building Serial Modem with PPP and without CMUX, in addition to :file:`overlay-ppp.conf`. +* :file:`overlay-ppp-without-cmux.conf` - Configuration file that enables support for the second UART to be used by PPP. + This configuration file should be included when building Serial Modem with PPP and without CMUX, in addition to :file:`overlay-ppp.conf` and :file:`overlay-ppp-without-cmux.overlay`. + +* :file:`overlay-ppp-without-cmux.overlay` - Devicetree overlay that configures the second UART to be used by PPP. + This configuration file should be included when building Serial Modem with PPP and without CMUX, in addition to :file:`overlay-ppp.conf` and :file:`overlay-ppp-without-cmux.conf`. It can be customized to fit your configuration, such as UART settings, baud rate, and flow control. By default, it sets the baud rate of the PPP UART to 1 000 000. diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt new file mode 100644 index 00000000..53759c47 --- /dev/null +++ b/drivers/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +add_subdirectory_ifdef(CONFIG_DTR_UART dtr_uart) diff --git a/drivers/Kconfig b/drivers/Kconfig new file mode 100644 index 00000000..6c84de8a --- /dev/null +++ b/drivers/Kconfig @@ -0,0 +1,8 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +menu "Drivers" +rsource "dtr_uart/Kconfig" +endmenu diff --git a/drivers/dtr_uart/CMakeLists.txt b/drivers/dtr_uart/CMakeLists.txt new file mode 100644 index 00000000..80f8539f --- /dev/null +++ b/drivers/dtr_uart/CMakeLists.txt @@ -0,0 +1,8 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_DTR_UART dtr_uart.c) diff --git a/drivers/dtr_uart/Kconfig b/drivers/dtr_uart/Kconfig new file mode 100644 index 00000000..29fe73ed --- /dev/null +++ b/drivers/dtr_uart/Kconfig @@ -0,0 +1,29 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +config DTR_UART + bool "DTR UART implementation - DCE side" + select GPIO + select UART_ASYNC_API + select RING_BUFFER + select PM_DEVICE + help + Data Terminal Ready (DTR) UART implements UART API and extends UART communication + with DTR and Ring Indicator (RI) signals. + + Data Communication Equipment (DCE) is controlled by DTE (Data Terminal Equipment) + using DTR signal. When DTR is deasserted, DCE will stop the UART communication and + power down the UART peripheral. When DTR is asserted, DCE will power up the UART + peripheral and start the UART communication. + + RI signal is used by DCE to notify DTE that data is available to read. + +if DTR_UART + +module = DTR_UART +module-str = dtr uart +source "subsys/logging/Kconfig.template.log_config" + +endif # DTR_UART diff --git a/drivers/dtr_uart/dtr_uart.c b/drivers/dtr_uart/dtr_uart.c new file mode 100644 index 00000000..43e70038 --- /dev/null +++ b/drivers/dtr_uart/dtr_uart.c @@ -0,0 +1,674 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * DTR (Data Terminal Ready) Logic: + * + * This driver implements DTR flow control where DTR input levels directly + * correspond to DTR assertion/deassertion events: + * + * DTR Input Level 0 → DTR_DEASSERTED → UART inactive (powered down) + * DTR Input Level 1 → DTR_ASSERTED → UART active (powered up and ready) + * + * Internal state representation (matches input level): + * - dtr_state = 0: DTR deasserted, UART inactive + * - dtr_state = 1: DTR asserted, UART active + */ + +LOG_MODULE_REGISTER(dtr_uart, CONFIG_DTR_UART_LOG_LEVEL); + +#define DT_DRV_COMPAT nordic_dtr_uart + +/* Low power uart structure. */ +struct dtr_uart_data { + + /* Device structure */ // MARKUS TODO: This is us, should be a nicer way to get. + const struct device *dev; + + /* Timer used for TX timeouting. */ + struct k_timer tx_timer; + + /* Current TX buffer. */ + const uint8_t *tx_buf; + size_t tx_len; + bool tx_started; + + /* RX state */ + bool app_rx_enable; + bool rx_active; + int32_t rx_timeout; + + /* Semaphore used to wait for RX to be disabled */ + struct k_sem rx_disable_sem; + + uart_callback_t user_callback; + void *user_data; + + /* PM state */ + bool pm_suspended; + + /* DTR state: 0 = deasserted (UART inactive), 1 = asserted (UART active) */ + bool dtr_state; + struct gpio_callback dtr_cb; + struct k_mutex dtr_mutex; + + /* Worker for processing DTR signal changes */ + struct k_work_delayable dtr_work; + + /* Worker for deactivating RI signal. */ + struct k_work_delayable ri_work; +}; + +/* Forward declarations. */ +static void user_callback(const struct device *dev, struct uart_event *evt); + +/* Configuration structured. */ +struct dtr_uart_config { + /* Physical UART device */ + const struct device *uart; + struct gpio_dt_spec dtr_gpio; + struct gpio_dt_spec ri_gpio; +}; + +static inline struct dtr_uart_data *get_dev_data(const struct device *dev) +{ + return dev->data; +} + +static inline const struct dtr_uart_config *get_dev_config(const struct device *dev) +{ + return dev->config; +} + +static inline const struct device *get_dev(struct dtr_uart_data *data) +{ + return data->dev; +} + +static void tx_complete(struct dtr_uart_data *data) +{ + data->tx_started = false; + data->tx_buf = NULL; + data->tx_len = 0; +} + +static void activate_tx(struct dtr_uart_data *data) +{ + const struct dtr_uart_config *config = get_dev_config(get_dev(data)); + + if (data->tx_buf && data->tx_len > 0) { + int err; + + data->tx_started = true; + err = uart_tx(config->uart, data->tx_buf, data->tx_len, SYS_FOREVER_US); + if (err) { + LOG_ERR("TX: Not started (%d).", err); + + struct uart_event evt = { + .type = UART_TX_ABORTED, + .data.tx.buf = data->tx_buf, + .data.tx.len = 0, + }; + + tx_complete(data); + user_callback(get_dev(data), &evt); + } + } +} + +static int deactivate_tx(struct dtr_uart_data *data) +{ + const struct dtr_uart_config *config = get_dev_config(get_dev(data)); + int err; + + if (data->tx_buf && !data->tx_started) { + LOG_DBG("TX: Abort - Before started."); + + struct uart_event evt = { + .type = UART_TX_ABORTED, + .data.tx.buf = data->tx_buf, + .data.tx.len = 0, + }; + tx_complete(data); + user_callback(get_dev(data), &evt); + return 0; + } + + err = uart_tx_abort(config->uart); + if (err == 0) { + LOG_DBG("TX: Abort."); + } else if (err == -EFAULT) { + LOG_DBG("TX: Abort - No active transfer."); + } else { + /* We assume that UART_TX_ABORTED is sent. */ + LOG_ERR("TX: Abort (%d).", err); + } + + return err; +} + +static int deactivate_rx(struct dtr_uart_data *data) +{ + const struct dtr_uart_config *config = get_dev_config(get_dev(data)); + int err; + + if (!data->rx_active) { + LOG_WRN("RX: Not active"); + return -EALREADY; + } + data->rx_active = false; + err = uart_rx_disable(config->uart); + if (err) { + LOG_ERR("RX: Failed to disable (err: %d)", err); + } + + return err; +} + +static void activate_rx(struct dtr_uart_data *data) +{ + if (data->rx_active) { + LOG_WRN("RX: Already active"); + return; + } + + if (!data->app_rx_enable) { + LOG_WRN("RX: Not enabled by app"); + return; + } + + struct uart_event evt = { + .type = UART_RX_BUF_REQUEST, + }; + user_callback(get_dev(data), &evt); +} + +static int power_on_uart(struct dtr_uart_data *data) +{ + const struct dtr_uart_config *config = get_dev_config(get_dev(data)); + enum pm_device_state state = PM_DEVICE_STATE_OFF; + + int err = pm_device_state_get(config->uart, &state); + if (err) { + LOG_ERR("Failed to get PM device state: %d", err); + return err; + } + if (state != PM_DEVICE_STATE_ACTIVE) { + /* Power on UART module */ + err = pm_device_action_run(config->uart, PM_DEVICE_ACTION_RESUME); + if (err) { + LOG_ERR("Failed to resume UART device: %d", err); + } + LOG_DBG("UART powered on"); + } + + return err; +} + +static int power_off_uart(struct dtr_uart_data *data) +{ + const struct dtr_uart_config *config = get_dev_config(get_dev(data)); + enum pm_device_state state = PM_DEVICE_STATE_ACTIVE; + + int err = pm_device_state_get(config->uart, &state); + if (err) { + LOG_ERR("Failed to get PM device state: %d", err); + return err; + } + if (state != PM_DEVICE_STATE_SUSPENDED) { + /* Power off UART module */ + err = pm_device_action_run(config->uart, PM_DEVICE_ACTION_SUSPEND); + if (err) { + LOG_ERR("Failed to suspend UART device: %d", err); + } + LOG_DBG("UART powered off"); + } + return err; +} + +/**************************** WORKERS **********************************/ +static void ri_work_fn(struct k_work *work) +{ + struct k_work_delayable *delayed_work = CONTAINER_OF(work, struct k_work_delayable, work); + struct dtr_uart_data *data = + CONTAINER_OF(delayed_work, struct dtr_uart_data, ri_work); + + gpio_pin_set_dt(&get_dev_config(get_dev(data))->ri_gpio, 0); +} + +static void ri_start(struct dtr_uart_data *data) +{ + gpio_pin_set_dt(&get_dev_config(get_dev(data))->ri_gpio, 1); + k_work_schedule(&data->ri_work, K_MSEC(1000)); +} + +static void uart_dtr_input_gpio_callback(const struct device *port, struct gpio_callback *cb, + uint32_t pins) +{ + struct dtr_uart_data *data = CONTAINER_OF(cb, struct dtr_uart_data, dtr_cb); + + k_work_reschedule(&data->dtr_work, K_MSEC(10)); +} + +static void dtr_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct dtr_uart_data *data = CONTAINER_OF(dwork, struct dtr_uart_data, dtr_work); + const struct dtr_uart_config *cfg = get_dev_config(data->dev); + + if (k_is_in_isr()) + { + LOG_ERR("DTR work handler called from ISR, this is not supported."); + return; + } + k_mutex_lock(&data->dtr_mutex, K_FOREVER); + + bool asserted = gpio_pin_get_dt(&cfg->dtr_gpio) && !data->pm_suspended; + int err; + + if (data->dtr_state == asserted) { + LOG_WRN("DTR is already %s, ignoring event", asserted ? "asserted" : "deasserted"); + goto exit; + } + + data->dtr_state = asserted; + + if (asserted) { + /* Stop RI signal. */ + k_work_cancel_delayable(&data->ri_work); + gpio_pin_set_dt(&get_dev_config(get_dev(data))->ri_gpio, 0); + + /* Power on UART module */ + err = power_on_uart(data); + if (err) { + LOG_ERR("Failed to resume UART device: %d", err); + } + + activate_rx(data); + activate_tx(data); + } else { + deactivate_tx(data); + + k_sem_reset(&data->rx_disable_sem); + err = deactivate_rx(data); + if (err == 0) { + /* Wait for RX to be fully disabled, before powering UART down. */ + k_sem_take(&data->rx_disable_sem, K_MSEC(100)); + } + power_off_uart(data); + } +exit: + k_mutex_unlock(&data->dtr_mutex); +} + +/****************************** UART ********************************/ + +static void user_callback(const struct device *dev, struct uart_event *evt) +{ + const struct dtr_uart_data *data = get_dev_data(dev); + + if (data->user_callback) { + data->user_callback(dev, evt, data->user_data); + } +} + +static void uart_callback(const struct device *uart, struct uart_event *evt, void *user_data) +{ + struct device *dev = user_data; + struct dtr_uart_data *data = get_dev_data(dev); + + switch (evt->type) { + case UART_TX_DONE: + LOG_DBG("TX: done"); + tx_complete(data); + user_callback(dev, evt); + break; + case UART_TX_ABORTED: + LOG_DBG("TX: abort"); + tx_complete(data); + user_callback(dev, evt); + break; + case UART_RX_RDY: + LOG_DBG("RX: Ready buf:%p, offset: %d,len: %d", (void *)evt->data.rx.buf, + evt->data.rx.offset, evt->data.rx.len); + user_callback(dev, evt); + break; + + case UART_RX_BUF_REQUEST: + LOG_DBG("UART_RX_BUF_REQUEST"); + user_callback(dev, evt); + break; + + case UART_RX_BUF_RELEASED: + LOG_DBG("Rx buf released %p", (void *)evt->data.rx_buf.buf); + user_callback(dev, evt); + break; + + case UART_RX_DISABLED: { + LOG_DBG("UART_RX_DISABLED %d", data->dtr_state); + /* When RX disabled because of DTR down, we handle it ourselves. */ + if (data->dtr_state && data->app_rx_enable) { + data->app_rx_enable = false; + user_callback(dev, evt); + } + + if (!data->dtr_state) { + /* RX disabled because of DTR down. */ + k_sem_give(&data->rx_disable_sem); + } + break; + } + case UART_RX_STOPPED: + LOG_DBG("Rx stopped"); + if (data->dtr_state && data->app_rx_enable) { + user_callback(dev, evt); + } + break; + } +} + +static int dtr_uart_init(const struct device *dev) +{ + struct dtr_uart_data *data = get_dev_data(dev); + const struct dtr_uart_config *cfg = get_dev_config(dev); + int err; + + data->dev = dev; + + if (!device_is_ready(cfg->uart)) { + LOG_ERR("UART device not ready"); + return -ENODEV; + } + + /* DTR */ + if (!gpio_is_ready_dt(&cfg->dtr_gpio)) { + LOG_ERR("DTR GPIO not ready"); + return -ENODEV; + } + + /* Configure DTR GPIO as input */ + err = gpio_pin_configure_dt(&cfg->dtr_gpio, GPIO_INPUT); + if (err < 0) { + LOG_ERR("Failed to configure DTR GPIO: %d", err); + return err; + } + + /* RI */ + if (!gpio_is_ready_dt(&cfg->ri_gpio)) { + LOG_ERR("RI GPIO not ready"); + return -ENODEV; + } + /* Configure RI GPIO as output, initially inactive */ + err = gpio_pin_configure_dt(&cfg->ri_gpio, GPIO_OUTPUT_INACTIVE); + if (err < 0) { + LOG_ERR("Failed to configure RI GPIO: %d", err); + return err; + } + + data->rx_timeout = SYS_FOREVER_US; + + k_mutex_init(&data->dtr_mutex); + k_work_init_delayable(&data->dtr_work, dtr_work_handler); + k_work_init_delayable(&data->ri_work, ri_work_fn); + + k_sem_init(&data->rx_disable_sem, 0, 1); + + /* Read initial DTR state */ + int initial_dtr_state = gpio_pin_get_dt(&cfg->dtr_gpio); + + if (initial_dtr_state < 0) { + LOG_ERR("Failed to read initial DTR state: %d", initial_dtr_state); + return initial_dtr_state; + } + /* Map GPIO input level directly to DTR state: + * Input level 0 → DTR deasserted (dtr_state = 0, UART inactive) + * Input level 1 → DTR asserted (dtr_state = 1, UART active) + */ + data->dtr_state = initial_dtr_state; + + /* Set up GPIO interrupt for DTR changes */ + gpio_init_callback(&data->dtr_cb, uart_dtr_input_gpio_callback, BIT(cfg->dtr_gpio.pin)); + + err = gpio_add_callback(cfg->dtr_gpio.port, &data->dtr_cb); + if (err < 0) { + LOG_ERR("Failed to add DTR GPIO callback: %d", err); + return err; + } + + err = gpio_pin_interrupt_configure_dt(&cfg->dtr_gpio, GPIO_INT_EDGE_BOTH); + if (err < 0) { + LOG_ERR("Failed to configure DTR GPIO interrupt: %d", err); + return err; + } + + err = uart_callback_set(cfg->uart, uart_callback, (void *)dev); + if (err < 0) { + return -EINVAL; + } + + LOG_DBG("DTR UART initialized, initial DTR state: %d", data->dtr_state); + + return 0; +} + +/**************************** API ********************************/ + +static int api_callback_set(const struct device *dev, uart_callback_t callback, void *user_data) +{ + struct dtr_uart_data *data = get_dev_data(dev); + + data->user_callback = callback; + data->user_data = user_data; + + return 0; +} + +static int api_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout) +{ + const struct dtr_uart_config *config = get_dev_config(dev); + + if (buf == NULL || len == 0) { + return 0; + } + + LOG_DBG("api_tx: %.*s", len, buf); + + struct dtr_uart_data *data = get_dev_data(dev); + + if (data->tx_buf) { + LOG_ERR("TX scheduled"); + return -EBUSY; + } + + if (data->dtr_state) { + return uart_tx(config->uart, buf, len, timeout); + } + data->tx_buf = buf; + data->tx_len = len; + + /* Start RI pulse. */ + ri_start(data); + + /* Buffer the data until DTR is down. */ + return 0; +} + +static int api_tx_abort(const struct device *dev) +{ + struct dtr_uart_data *data = get_dev_data(dev); + + return deactivate_tx(data); +} + +/* Application calls this when it is ready to receive data. */ +static int api_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout) +{ + const struct dtr_uart_config *config = get_dev_config(dev); + struct dtr_uart_data *data = get_dev_data(dev); + + LOG_DBG("api_rx_enable: %p, %zu", (void *)buf, len); + + if (data->app_rx_enable) { + LOG_ERR("RX already enabled"); + return -EBUSY; + } + data->rx_timeout = timeout; + data->app_rx_enable = true; + + if (!data->dtr_state) { + struct uart_event evt = { + .type = UART_RX_BUF_RELEASED, + .data.rx_buf.buf = buf, + }; + user_callback(dev, &evt); + return 0; + } + data->rx_active = true; + return uart_rx_enable(config->uart, buf, len, timeout); +} + +static int api_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len) +{ + const struct dtr_uart_config *config = get_dev_config(dev); + struct dtr_uart_data *data = get_dev_data(dev); + int err = 0; + + LOG_DBG("api_rx_buf_rsp: %p, %zu", (void *)buf, len); + + if (!data->dtr_state) { + goto release; + } + + if (!data->app_rx_enable) { + goto release; + } + + if (!data->rx_active) { + data->rx_active = true; + err = uart_rx_enable(config->uart, buf, len, data->rx_timeout); + if (err == -EBUSY) { + LOG_ERR("RX: Busy"); + err = 0; + goto release; + } + if (err) { + LOG_ERR("RX: Enable failed (%d).", err); + data->rx_active = false; + goto release; + } + + LOG_DBG("RX: Enabled"); + return 0; + } + + return uart_rx_buf_rsp(config->uart, buf, len); +release: + struct uart_event evt = { + .type = UART_RX_BUF_RELEASED, + .data.rx_buf.buf = buf, + }; + user_callback(dev, &evt); + return err; +} + +static int api_rx_disable(const struct device *dev) +{ + struct dtr_uart_data *data = get_dev_data(dev); + + LOG_DBG("api_rx_disable"); + + if (!data->app_rx_enable) { + LOG_ERR("RX not enabled"); + return -EINVAL; + } + + data->app_rx_enable = false; + deactivate_rx(data); + return 0; +} + +static int api_err_check(const struct device *dev) +{ + const struct dtr_uart_config *config = get_dev_config(dev); + + return uart_err_check(config->uart); +} + +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE +static int api_configure(const struct device *dev, const struct uart_config *cfg) +{ + const struct dtr_uart_config *config = get_dev_config(dev); + + return uart_configure(config->uart, cfg); +} + +static int api_config_get(const struct device *dev, struct uart_config *cfg) +{ + const struct dtr_uart_config *config = get_dev_config(dev); + + return uart_config_get(config->uart, cfg); +} +#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ + +/* Power management */ +#ifdef CONFIG_PM_DEVICE +static int dtr_uart_pm_action(const struct device *dev, enum pm_device_action action) +{ + struct dtr_uart_data *data = get_dev_data(dev); + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + LOG_DBG("PM SUSPEND - Disobey DTR and disable UART"); + data->pm_suspended = true; + dtr_work_handler(&data->dtr_work.work); + break; + case PM_DEVICE_ACTION_RESUME: + LOG_DBG("PM RESUME - Obey DTR"); + data->pm_suspended = false; + dtr_work_handler(&data->dtr_work.work); + break; + default: + return -ENOTSUP; + } + + return 0; +} +#endif + +static const struct uart_driver_api dtr_uart_api = { + .callback_set = api_callback_set, + .tx = api_tx, + .tx_abort = api_tx_abort, + .rx_enable = api_rx_enable, + .rx_buf_rsp = api_rx_buf_rsp, + .rx_disable = api_rx_disable, + .err_check = api_err_check, +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE + .configure = api_configure, + .config_get = api_config_get, +#endif +}; + +/* Device macro */ +#define DTR_UART_INIT(n) \ + static const struct dtr_uart_config dtr_uart_config_##n = { \ + .dtr_gpio = GPIO_DT_SPEC_INST_GET(n, dtr_gpios), \ + .ri_gpio = GPIO_DT_SPEC_INST_GET(n, ri_gpios), \ + .uart = DEVICE_DT_GET(DT_PARENT(DT_DRV_INST(n))), \ + }; \ + static struct dtr_uart_data dtr_uart_data_##n; \ + PM_DEVICE_DT_INST_DEFINE(n, dtr_uart_pm_action); \ + DEVICE_DT_INST_DEFINE(n, dtr_uart_init, PM_DEVICE_DT_INST_GET(n), &dtr_uart_data_##n, \ + &dtr_uart_config_##n, POST_KERNEL, 51, &dtr_uart_api); + +DT_INST_FOREACH_STATUS_OKAY(DTR_UART_INIT) diff --git a/dts/bindings/dte_dtr/nordic,dtr-dte.yaml b/dts/bindings/dte_dtr/nordic,dtr-dte.yaml new file mode 100644 index 00000000..c458c7f6 --- /dev/null +++ b/dts/bindings/dte_dtr/nordic,dtr-dte.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +description: | + DTE side of DTR/RI UART + +compatible: "nordic,dte-dtr" + +properties: + dtr-gpios: + type: phandle-array + description: Output. Data Terminal Ready pin + required: true + + ri-gpios: + type: phandle-array + description: Input. Ring Indicator pin + required: true diff --git a/dts/bindings/dtr_uart/nordic,dtr-uart.yaml b/dts/bindings/dtr_uart/nordic,dtr-uart.yaml new file mode 100644 index 00000000..bb26a3bc --- /dev/null +++ b/dts/bindings/dtr_uart/nordic,dtr-uart.yaml @@ -0,0 +1,22 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +description: | + DTR/RI UART + +compatible: "nordic,dtr-uart" + +include: uart-device.yaml + +properties: + dtr-gpios: + type: phandle-array + description: Data Terminal Ready pin + required: true + + ri-gpios: + type: phandle-array + description: Ring Indicator pin + required: true diff --git a/include/sm_host.h b/include/sm_host.h index 637adf2f..c02741b3 100644 --- a/include/sm_host.h +++ b/include/sm_host.h @@ -57,45 +57,64 @@ enum at_cmd_state { typedef void (*sm_data_handler_t)(const uint8_t *data, size_t datalen); /** - * @typedef sm_ind_handler_t + * @typedef sm_ri_handler_t * - * Handler to handle @kconfig{CONFIG_SM_HOST_INDICATE_PIN} signal from Serial Modem. + * Handler to handle the Ring Indicate (RI) signal from Serial Modem. */ -typedef void (*sm_ind_handler_t)(void); +typedef void (*sm_ri_handler_t)(void); /**@brief Initialize Serial Modem Host library. * * @param handler Pointer to a handler function of type @ref sm_data_handler_t. + * @param automatic_uart If true, DTR and UART are automatically managed by the library. + * @param inactivity_timeout Inactivity timeout for DTR and UART disablement. Only used if @p + * automatic is true. * * @return Zero on success, non-zero otherwise. */ -int sm_host_init(sm_data_handler_t handler); +int sm_host_init(sm_data_handler_t handler, bool automatic_uart, k_timeout_t inactivity_timeout); /**@brief Un-initialize Serial Modem Host */ int sm_host_uninit(void); /** - * @brief Register callback for @kconfig{CONFIG_SM_HOST_INDICATE_PIN} indication + * @brief Register callback for Ring Indicate (RI) pin. * - * @param handler Pointer to a handler function of type @ref sm_ind_handler_t. - * @param wakeup Enable/disable System Off wakeup by GPIO Sense. + * @param handler Pointer to a handler function of type @ref sm_ri_handler_t. * - * @retval Zero Success. - * @retval -EFAULT if @kconfig{CONFIG_SM_HOST_INDICATE_PIN} is not defined. + * @retval Zero on success. Otherwise, a (negative) error code is returned. */ -int sm_host_register_ind(sm_ind_handler_t handler, bool wakeup); +int sm_host_register_ri_handler(sm_ri_handler_t handler); /** - * @brief Toggle power pin of the nRF91 Series device configured with - * @kconfig{CONFIG_SM_HOST_POWER_PIN}. + * @brief Configure automatic DTR UART handling * - * The pin is enabled for the time specified in @kconfig{CONFIG_SM_HOST_POWER_PIN_TIME} - * and then disabled. + * If automatic DTR UART handling is enabled, the library will enable DTR UART when RI + * signal is detected, and disable it after inactivity timeout. + * + * @param automatic If true, DTR UART is automatically managed by the library. + * @param inactivity Inactivity timeout for DTR UART disablement. Only used if @p + * automatic is true. * * @return Zero on success, non-zero otherwise. */ -int sm_host_power_pin_toggle(void); +void sm_host_configure_dtr_uart(bool automatic, k_timeout_t inactivity); + +/** + * @brief Disable DTR UART + * + * Disables DTR UART. Disables automatic DTR UART handling. + * + */ +void sm_host_disable_dtr_uart(void); + +/** @brief Enable DTR UART + * + * Enables DTR UART. Disables automatic DTR UART handling. + * + */ +void sm_host_enable_dtr_uart(void); /** * @brief Function to send an AT command in Serial Modem command mode diff --git a/lib/sm_host/Kconfig b/lib/sm_host/Kconfig index f7337439..9ff56139 100644 --- a/lib/sm_host/Kconfig +++ b/lib/sm_host/Kconfig @@ -7,9 +7,11 @@ menuconfig SM_HOST bool "Serial Modem Host Library" depends on SERIAL - depends on UART_ASYNC_API - depends on RING_BUFFER + select UART_ASYNC_API + select RING_BUFFER select UART_USE_RUNTIME_CONFIGURE + select GPIO_GET_CONFIG + select PM_DEVICE if SM_HOST @@ -72,26 +74,6 @@ config SM_HOST_CR_LF_TERMINATION endchoice -config SM_HOST_POWER_PIN - int "Power pin" - default -1 - help - Interface GPIO to toggle power pin of the nRF91 Series device. - -config SM_HOST_POWER_PIN_TIME - int "Power pin active time" - default 100 - help - GPIO active time in milliseconds. This setting specifies - the period length for the pin to be active. - -config SM_HOST_INDICATE_PIN - int "Indicate pin" - default -1 - help - Interface GPIO pin used by Serial Modem to indicate that data is available or - an unexpected reset has occurred. - module = SM_HOST module-str = SM Host source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" diff --git a/lib/sm_host/sm_host.c b/lib/sm_host/sm_host.c index 57da75ab..68d28793 100644 --- a/lib/sm_host/sm_host.c +++ b/lib/sm_host/sm_host.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -12,16 +13,15 @@ #include #include #include +#include #include LOG_MODULE_REGISTER(sm_host, CONFIG_SM_HOST_LOG_LEVEL); -BUILD_ASSERT(CONFIG_SM_HOST_POWER_PIN >= 0, "Power pin not configured"); - #define UART_RX_MARGIN_MS 10 #define UART_RX_TIMEOUT_US 2000 #define UART_ERROR_DELAY_MS 500 -#define UART_TX_TIMEOUT_US 100000 +#define UART_TX_TIMEOUT_US 1000 * 1000 * 5 /* 5 seconds */ /* Serial Modem has formatted AT response based on TS 27.007 */ #define AT_CMD_OK_STR "\r\nOK\r\n" @@ -57,27 +57,23 @@ K_MSGQ_DEFINE(rx_event_queue, sizeof(struct rx_event_t), UART_RX_EVENT_COUNT, 1) /* Ring buffer for TX data. */ RING_BUF_DECLARE(tx_buf, CONFIG_SM_HOST_UART_TX_BUF_SIZE); static K_SEM_DEFINE(tx_done_sem, 0, 1); +static K_SEM_DEFINE(uart_disabled_sem, 0, 1); -enum uart_recovery_state { - RECOVERY_DISABLED, - RECOVERY_IDLE, - RECOVERY_ONGOING +enum sm_host_uart_state { + SM_HOST_TX_ENABLED_BIT, + SM_HOST_RX_ENABLED_BIT, + SM_HOST_RX_RECOVERY_BIT, + SM_HOST_RX_RECOVERY_DISABLED_BIT }; -static atomic_t recovery_state; +static atomic_t uart_state; + +static sm_ri_handler_t ri_handler; + static K_SEM_DEFINE(at_rsp, 0, 1); static sm_data_handler_t data_handler; static enum at_cmd_state sm_at_state; -#if DT_HAS_CHOSEN(ncs_sm_gpio) -static const struct device *gpio_dev = DEVICE_DT_GET(DT_CHOSEN(ncs_sm_gpio)); -#else -static const struct device *gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpio0)); -#endif -static struct k_work_delayable gpio_power_pin_disable_work; -static sm_ind_handler_t ind_handler; -static sm_ind_handler_t ind_handler_backup; - #if defined(CONFIG_SM_HOST_SHELL) static const struct shell *global_shell; static const char at_usage_str[] = "Usage: sm "; @@ -85,70 +81,35 @@ static const char at_usage_str[] = "Usage: sm "; extern void sm_monitor_dispatch(const char *notif, size_t len); extern char *strnstr(const char *haystack, const char *needle, size_t haystack_sz); +static int dtr_uart_enable(void); -#if (CONFIG_SM_HOST_INDICATE_PIN >= 0) -static bool indicate_pin_enabled; - -static void gpio_cb_func(const struct device *dev, struct gpio_callback *gpio_cb, uint32_t pins); -static struct gpio_callback gpio_cb; -#endif +struct dtr_config { -static int indicate_pin_enable(void) -{ -#if (CONFIG_SM_HOST_INDICATE_PIN >= 0) - int err = 0; + /* DTR */ + const struct gpio_dt_spec dtr_gpio; - if (!indicate_pin_enabled) { - err = gpio_pin_configure(gpio_dev, CONFIG_SM_HOST_INDICATE_PIN, - GPIO_INPUT | GPIO_PULL_UP | GPIO_ACTIVE_LOW); - if (err) { - LOG_ERR("GPIO config error: %d", err); - return err; - } + /* RI */ + const struct gpio_dt_spec ri_gpio; - gpio_init_callback(&gpio_cb, gpio_cb_func, BIT(CONFIG_SM_HOST_INDICATE_PIN)); - err = gpio_add_callback(gpio_dev, &gpio_cb); - if (err) { - LOG_WRN("GPIO add callback error: %d", err); - } - err = gpio_pin_interrupt_configure(gpio_dev, CONFIG_SM_HOST_INDICATE_PIN, - GPIO_INT_LEVEL_LOW); - if (err) { - LOG_WRN("GPIO interrupt configure error: %d", err); - } - indicate_pin_enabled = true; - LOG_DBG("Indicate pin enabled"); - } -#endif - return 0; -} + /* Automatically activate DTR UART from RI and disable it after inactivity. */ + bool automatic; + k_timeout_t inactivity; -static void indicate_pin_disable(void) -{ -#if (CONFIG_SM_HOST_INDICATE_PIN >= 0) - if (indicate_pin_enabled) { - gpio_remove_callback(gpio_dev, &gpio_cb); - gpio_pin_interrupt_configure(gpio_dev, CONFIG_SM_HOST_INDICATE_PIN, - GPIO_INT_DISABLE); - gpio_pin_configure(gpio_dev, CONFIG_SM_HOST_INDICATE_PIN, GPIO_DISCONNECTED); - indicate_pin_enabled = false; - LOG_DBG("Indicate pin disabled"); - } -#endif -} + /* Current DTR state. */ + bool active; -static void gpio_power_pin_disable_work_fn(struct k_work *work) -{ - ARG_UNUSED(work); + /* Work items for enabling and disabling DTR UART. */ + struct k_work dtr_uart_enable_work; + struct k_work_delayable dtr_uart_disable_work; +}; - if (gpio_pin_set(gpio_dev, CONFIG_SM_HOST_POWER_PIN, 0) != 0) { - LOG_WRN("GPIO set error"); - } - /* When Serial Modem is woken up, indicate pin must be enabled */ - (void)indicate_pin_enable(); +static struct dtr_config dtr_config = { + .dtr_gpio = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(dte_dtr), dtr_gpios, {0}), + .ri_gpio = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(dte_dtr), ri_gpios, {0}), +}; - LOG_INF("Disable power pin"); -} +static void gpio_cb_func(const struct device *dev, struct gpio_callback *gpio_cb, uint32_t pins); +static struct gpio_callback gpio_cb; static inline struct rx_buf_t *block_start_get(uint8_t *buf) { @@ -197,6 +158,10 @@ static int rx_enable(void) struct rx_buf_t *buf; int ret; + if (atomic_test_bit(&uart_state, SM_HOST_RX_ENABLED_BIT)) { + return 0; + } + buf = buf_alloc(); if (!buf) { LOG_DBG("Failed to allocate RX buffer"); @@ -206,9 +171,12 @@ static int rx_enable(void) ret = uart_rx_enable(uart_dev, buf->buf, sizeof(buf->buf), UART_RX_TIMEOUT_US); if (ret) { LOG_WRN("uart_rx_enable failed: %d", ret); + buf_unref(buf->buf); return ret; } + atomic_set_bit(&uart_state, SM_HOST_RX_ENABLED_BIT); + return 0; } @@ -216,18 +184,29 @@ static int rx_disable(void) { int err; - /* Wait for possible rx_enable to complete. */ - if (atomic_set(&recovery_state, RECOVERY_DISABLED) == RECOVERY_ONGOING) { + atomic_set_bit(&uart_state, SM_HOST_RX_RECOVERY_DISABLED_BIT); + + while (atomic_test_bit(&uart_state, SM_HOST_RX_RECOVERY_BIT)) { + /* Wait until possible recovery is complete. */ k_sleep(K_MSEC(10)); } + if (!atomic_test_bit(&uart_state, SM_HOST_RX_ENABLED_BIT)) { + return 0; + } + + k_sem_reset(&uart_disabled_sem); + err = uart_rx_disable(uart_dev); if (err) { LOG_ERR("UART RX disable failed: %d", err); - atomic_set(&recovery_state, RECOVERY_IDLE); return err; } + /* Wait until RX is actually disabled. */ + k_sem_take(&uart_disabled_sem, K_MSEC(100)); + atomic_clear_bit(&uart_state, SM_HOST_RX_ENABLED_BIT); + return 0; } @@ -235,17 +214,18 @@ static void rx_recovery(void) { int err; - if (atomic_get(&recovery_state) != RECOVERY_ONGOING) { + if (atomic_test_bit(&uart_state, SM_HOST_RX_RECOVERY_DISABLED_BIT)) { return; } + atomic_set_bit(&uart_state, SM_HOST_RX_RECOVERY_BIT); + err = rx_enable(); if (err) { k_work_schedule(&rx_process_work, K_MSEC(UART_RX_MARGIN_MS)); - return; } - atomic_cas(&recovery_state, RECOVERY_ONGOING, RECOVERY_IDLE); + atomic_clear_bit(&uart_state, SM_HOST_RX_RECOVERY_BIT); } /* Attempts to find AT responses in the UART buffer. */ @@ -360,6 +340,10 @@ static int tx_start(void) size_t len; int err; + if (!atomic_test_bit(&uart_state, SM_HOST_TX_ENABLED_BIT)) { + return -EAGAIN; + } + len = ring_buf_get_claim(&tx_buf, &buf, ring_buf_capacity_get(&tx_buf)); err = uart_tx(uart_dev, buf, len, UART_TX_TIMEOUT_US); if (err) { @@ -380,12 +364,23 @@ static int tx_write(const uint8_t *data, size_t len, bool flush) LOG_HEXDUMP_DBG(data, len, "TX"); + if (dtr_config.automatic && !dtr_config.active) { + err = dtr_uart_enable(); + if (err) { + LOG_ERR("Failed to enable DTR (%d).", err); + } + } + while (sent < len) { ret = ring_buf_put(&tx_buf, data + sent, len - sent); if (ret) { sent += ret; } else { /* Buffer full, block and start TX. */ + if (atomic_test_bit(&uart_state, SM_HOST_TX_ENABLED_BIT) == 0) { + LOG_ERR("TX disabled, %zu bytes dropped", len - sent); + return -EIO; + } k_sem_take(&tx_done_sem, K_FOREVER); err = tx_start(); if (err) { @@ -398,18 +393,66 @@ static int tx_write(const uint8_t *data, size_t len, bool flush) } } - if (flush && k_sem_take(&tx_done_sem, K_NO_WAIT) == 0) { - err = tx_start(); - if (err) { - LOG_ERR("tx_start failed: %d", err); - k_sem_give(&tx_done_sem); - return err; + if (flush) { + if (atomic_test_bit(&uart_state, SM_HOST_TX_ENABLED_BIT) == 0) { + LOG_INF("TX disabled, data will be sent when enabled"); + return -EAGAIN; + } + if (k_sem_take(&tx_done_sem, K_NO_WAIT) == 0) { + err = tx_start(); + if (err) { + LOG_ERR("tx_start failed: %d", err); + k_sem_give(&tx_done_sem); + return err; + } } } return 0; } +static int tx_enable(void) +{ + if (!atomic_test_and_set_bit(&uart_state, SM_HOST_TX_ENABLED_BIT)) { + k_sem_give(&tx_done_sem); + } + return 0; +} + +static int tx_disable(k_timeout_t timeout) +{ + int err; + + if (!atomic_test_and_clear_bit(&uart_state, SM_HOST_TX_ENABLED_BIT)) { + return 0; + } + + if (k_sem_take(&tx_done_sem, timeout) == 0) { + return 0; + } + + err = uart_tx_abort(uart_dev); + if (!err) { + LOG_INF("TX aborted"); + } else if (err != -EFAULT) { + LOG_ERR("uart_tx_abort failed (%d).", err); + return err; + } + + return 0; +} + +static void reschedule_disable(void) +{ + if (dtr_config.active && dtr_config.automatic) { + /* Restart the inactivity timer. */ + k_work_reschedule(&dtr_config.dtr_uart_disable_work, dtr_config.inactivity); + } else { + /* Stop the inactivity timer. */ + k_work_cancel_delayable(&dtr_config.dtr_uart_disable_work); + } +} + static void uart_callback(const struct device*, struct uart_event *evt, void*) { struct rx_buf_t *buf; @@ -431,6 +474,7 @@ static void uart_callback(const struct device*, struct uart_event *evt, void*) LOG_ERR("tx_start failed: %d", err); k_sem_give(&tx_done_sem); } + reschedule_disable(); break; case UART_TX_ABORTED: err = ring_buf_get_finish(&tx_buf, evt->data.tx.len); @@ -452,6 +496,7 @@ static void uart_callback(const struct device*, struct uart_event *evt, void*) break; } k_work_submit((struct k_work *)&rx_process_work); + reschedule_disable(); break; case UART_RX_BUF_REQUEST: buf = buf_alloc(); @@ -470,9 +515,8 @@ static void uart_callback(const struct device*, struct uart_event *evt, void*) } break; case UART_RX_DISABLED: - if (atomic_cas(&recovery_state, RECOVERY_IDLE, RECOVERY_ONGOING)) { - k_work_submit((struct k_work *)&rx_process_work); - } + k_sem_give(&uart_disabled_sem); + k_work_submit((struct k_work *)&rx_process_work); break; default: break; @@ -523,156 +567,330 @@ static int uart_init(const struct device *uart_dev) } /* Enable RX */ - atomic_set(&recovery_state, RECOVERY_IDLE); + atomic_clear(&uart_state); err = rx_enable(); /* Enable TX */ - k_sem_give(&tx_done_sem); + tx_enable(); return err; } -#if (CONFIG_SM_HOST_INDICATE_PIN >= 0) -static struct gpio_callback gpio_cb; +static int uart_power_state_action(enum pm_device_action action) +{ + enum pm_device_state state = PM_DEVICE_STATE_OFF; + int err = pm_device_state_get(uart_dev, &state); + + if (err) { + LOG_ERR("Failed to get PM device state: %d", err); + return err; + } + + if (action != PM_DEVICE_ACTION_RESUME && action != PM_DEVICE_ACTION_SUSPEND) { + return -EOPNOTSUPP; + } + + if ((action == PM_DEVICE_ACTION_RESUME && state == PM_DEVICE_STATE_ACTIVE) || + (action == PM_DEVICE_ACTION_SUSPEND && state == PM_DEVICE_STATE_SUSPENDED)) { + return 0; + } + + err = pm_device_action_run(uart_dev, action); + if (err) { + LOG_ERR("Action %d failed on UART device: %d", action, err); + return err; + } + + return 0; +} + +static int dtr_pin_set(bool level) +{ + int err; + + if (gpio_is_ready_dt(&dtr_config.dtr_gpio)) { + err = gpio_pin_set_dt(&dtr_config.dtr_gpio, level); + if (err) { + LOG_ERR("Failed to set DTR pin: %d", err); + return -EFAULT; + } + } else { + LOG_WRN("DTR pin not configured"); + return -EFAULT; + } + return 0; +} + +static int dtr_uart_disable(void) +{ + int err; + + /* Wait until TX is done and disable TX. */ + err = tx_disable(K_NO_WAIT); + if (err) { + LOG_ERR("TX disable failed (%d).", err); + return err; + } + + /* Ask Serial Modem to disable UART. */ + err = dtr_pin_set(0); + if (err) { + LOG_ERR("Failed to set DTR pin (%d).", err); + return err; + } + + /* Optional: Wait for possible Serial Modem TX to complete. */ + /* k_sleep(K_MSEC(100)); */ + + /* Disable RX. */ + err = rx_disable(); + if (err) { + LOG_ERR("RX disable failed (%d).", err); + return err; + } + + /* Power off UART module */ + err = uart_power_state_action(PM_DEVICE_ACTION_SUSPEND); + if (err) { + LOG_ERR("Failed to suspend UART (%d).", err); + return err; + } + + dtr_config.active = false; + + LOG_DBG("DTR UART disabled"); + return 0; +} + +static void dtr_uart_disable_work_fn(struct k_work *work) +{ + int err; + + ARG_UNUSED(work); + + err = dtr_uart_disable(); + if (err) { + LOG_ERR("Failed to disable DTR UART (%d).", err); + } +} + +static int dtr_uart_enable(void) +{ + int err; + + /* Power on UART module */ + err = uart_power_state_action(PM_DEVICE_ACTION_RESUME); + if (err) { + LOG_ERR("Failed to resume UART (%d).", err); + return err; + } + + /* Enable RX. */ + atomic_clear_bit(&uart_state, SM_HOST_RX_RECOVERY_DISABLED_BIT); + err = rx_enable(); + if (err) { + LOG_ERR("Failed to enable RX (%d).", err); + return err; + } + + /* Ask Serial Modem to enable UART. */ + err = dtr_pin_set(1); + if (err) { + LOG_ERR("Failed to set DTR pin (%d).", err); + return err; + } + + /* Optional: Wait for Serial Modem to be ready */ + /* k_sleep(K_MSEC(100)); */ + + /* Enable TX. */ + err = tx_enable(); + if (err) { + LOG_ERR("Failed to enable TX (%d).", err); + return err; + } + + /* Start TX in case there is pending data. */ + err = tx_start(); + if (err) { + LOG_ERR("Failed to start TX (%d).", err); + return err; + } + + dtr_config.active = true; + + reschedule_disable(); + + LOG_DBG("DTR UART enabled"); + return 0; +} + +static void dtr_uart_enable_work_fn(struct k_work *work) +{ + int err; + + ARG_UNUSED(work); + + err = dtr_uart_enable(); + if (err) { + LOG_ERR("Failed to enable DTR UART (%d).", err); + } +} static void gpio_cb_func(const struct device *dev, struct gpio_callback *gpio_cb, uint32_t pins) { - if ((BIT(CONFIG_SM_HOST_INDICATE_PIN) & pins) == 0) { + if ((BIT(dtr_config.ri_gpio.pin) & pins) == 0) { return; } - if (k_work_delayable_is_pending(&gpio_power_pin_disable_work)) { - (void)k_work_cancel_delayable(&gpio_power_pin_disable_work); - (void)gpio_pin_set(gpio_dev, CONFIG_SM_HOST_POWER_PIN, 0); - } else { - /* Disable indicate pin so that callbacks doesn't keep on coming. */ - indicate_pin_disable(); + if (dtr_config.automatic && !dtr_config.active) { + /* Wake up the application */ + k_work_submit(&dtr_config.dtr_uart_enable_work); } - LOG_INF("Remote indication"); - if (ind_handler) { - ind_handler(); + if (ri_handler) { + ri_handler(); } else { - LOG_WRN("Indicate PIN configured but sm_ind_handler_t not defined"); + LOG_INF("Ring Indicate"); } } -#endif /* CONFIG_SM_HOST_INDICATE_PIN */ + static int gpio_init(void) { int err; - if (!device_is_ready(gpio_dev)) { - LOG_ERR("GPIO controller not ready"); - return -ENODEV; + if (gpio_is_ready_dt(&dtr_config.dtr_gpio)) { + err = gpio_pin_configure_dt(&dtr_config.dtr_gpio, GPIO_OUTPUT_ACTIVE); + if (err) { + LOG_ERR("Failed to configure DTR pin: %d", err); + return -EFAULT; + } + } else { + LOG_WRN("DTR GPIO is not ready"); } - err = gpio_pin_configure(gpio_dev, CONFIG_SM_HOST_POWER_PIN, - GPIO_OUTPUT_INACTIVE | GPIO_ACTIVE_LOW); - if (err) { - LOG_ERR("GPIO config error: %d", err); - return err; - } + if (gpio_is_ready_dt(&dtr_config.ri_gpio)) { + gpio_flags_t flags; + nrf_gpio_pin_sense_t sense; + + err = gpio_pin_configure_dt(&dtr_config.ri_gpio, GPIO_INPUT); + if (err) { + LOG_ERR("GPIO config error: %d", err); + return err; + } - err = indicate_pin_enable(); + err = gpio_pin_get_config_dt(&dtr_config.ri_gpio, &flags); + if (err) { + LOG_ERR("Failed to get RI pin config: %d", err); + return err; + } - return err; + if (flags & GPIO_PULL_DOWN) { + LOG_DBG("Wakeup sense %s", "high"); + sense = NRF_GPIO_PIN_SENSE_HIGH; + } else { + LOG_DBG("Wakeup sense %s", "low"); + sense = NRF_GPIO_PIN_SENSE_LOW; + } + nrf_gpio_cfg_sense_set(dtr_config.ri_gpio.pin, sense); + + + gpio_init_callback(&gpio_cb, gpio_cb_func, BIT(dtr_config.ri_gpio.pin)); + err = gpio_add_callback_dt(&dtr_config.ri_gpio, &gpio_cb); + if (err) { + LOG_WRN("GPIO add callback error: %d", err); + return err; + } + err = gpio_pin_interrupt_configure_dt(&dtr_config.ri_gpio, GPIO_INT_EDGE_TO_ACTIVE); + if (err) { + LOG_WRN("GPIO interrupt configure error: %d", err); + return err; + } + } else { + LOG_WRN("RI GPIO is not ready"); + } + + return 0; } -int sm_host_init(sm_data_handler_t handler) + +int sm_host_init(sm_data_handler_t handler, bool automatic, k_timeout_t inactivity) { int err; + static bool initialized; - if (handler != NULL && data_handler != NULL) { - LOG_ERR("Already initialized"); - return -EFAULT; + if (initialized) { + return -EALREADY; } + initialized = true; data_handler = handler; - ind_handler = NULL; - ind_handler_backup = NULL; + ri_handler = NULL; sm_at_state = AT_CMD_OK; err = gpio_init(); - if (err != 0) { - LOG_WRN("Failed to init gpio"); - return err; + if (err) { + LOG_ERR("GPIO init (%d)", err); + return -EFAULT; } err = uart_init(uart_dev); if (err) { - LOG_ERR("UART could not be initialized: %d", err); + LOG_ERR("UART init (%d)", err); return -EFAULT; } - k_work_init_delayable(&gpio_power_pin_disable_work, gpio_power_pin_disable_work_fn); k_work_init_delayable(&rx_process_work, rx_process); + k_work_init(&dtr_config.dtr_uart_enable_work, dtr_uart_enable_work_fn); + k_work_init_delayable(&dtr_config.dtr_uart_disable_work, dtr_uart_disable_work_fn); + /* Initialize shell pointer so it's available for printing in callbacks */ #if defined(CONFIG_SHELL_BACKEND_SERIAL) global_shell = shell_backend_uart_get_ptr(); #elif defined(CONFIG_SHELL_BACKEND_RTT) global_shell = shell_backend_rtt_get_ptr(); #endif + + dtr_config.automatic = automatic; + dtr_config.inactivity = inactivity; + dtr_config.active = true; + + reschedule_disable(); + return 0; } int sm_host_uninit(void) { - rx_disable(); + int err; - gpio_pin_configure(gpio_dev, CONFIG_SM_HOST_POWER_PIN, GPIO_DISCONNECTED); + err = dtr_uart_disable(); + if (err) { + LOG_ERR("Failed to disable DTR UART (%d).", err); + } - indicate_pin_disable(); + err = gpio_pin_configure_dt(&dtr_config.dtr_gpio, GPIO_DISCONNECTED); + if (err) { + LOG_ERR("Failed to disconnect DTR pin: %d", err); + } data_handler = NULL; - ind_handler = NULL; - ind_handler_backup = NULL; + ri_handler = NULL; sm_at_state = AT_CMD_OK; return 0; } -int sm_host_register_ind(sm_ind_handler_t handler, bool wakeup) +int sm_host_register_ri_handler(sm_ri_handler_t handler) { -#if (CONFIG_SM_HOST_INDICATE_PIN >= 0) - if (ind_handler != NULL) { - ind_handler_backup = ind_handler; - } - ind_handler = handler; - - if (wakeup) { - /* - * Due to errata 4, Always configure PIN_CNF[n].INPUT before PIN_CNF[n].SENSE. - * At this moment indicate pin has already been configured as INPUT at init_gpio(). - */ - nrf_gpio_cfg_sense_set(CONFIG_SM_HOST_INDICATE_PIN, NRF_GPIO_PIN_SENSE_LOW); - } - - return 0; -#else - return -EFAULT; -#endif -} - -int sm_host_power_pin_toggle(void) -{ - int err; - - if (k_work_delayable_is_pending(&gpio_power_pin_disable_work)) { - return 0; + if (!gpio_is_ready_dt(&dtr_config.ri_gpio)) { + LOG_WRN("RI GPIO is not ready"); + return -EFAULT; } - LOG_INF("Enable power pin"); - - err = gpio_pin_set(gpio_dev, CONFIG_SM_HOST_POWER_PIN, 1); - if (err) { - LOG_ERR("GPIO set error: %d", err); - } else { - k_work_reschedule( - &gpio_power_pin_disable_work, - K_MSEC(CONFIG_SM_HOST_POWER_PIN_TIME)); - } + ri_handler = handler; return 0; } @@ -681,11 +899,6 @@ int sm_host_send_cmd(const char *const command, uint32_t timeout) { int ret; - /* Enable indicate pin when command is sent. This should not be needed but is made - * currently to make sure it wouldn't be disabled forever. - */ - (void)indicate_pin_enable(); - sm_at_state = AT_CMD_PENDING; ret = tx_write(command, strlen(command), false); if (ret < 0) { @@ -720,6 +933,31 @@ int sm_host_send_data(const uint8_t *const data, size_t datalen) return tx_write(data, datalen, true); } +void sm_host_configure_dtr_uart(bool automatic, k_timeout_t inactivity) +{ + dtr_config.automatic = automatic; + dtr_config.inactivity = inactivity; + + if (dtr_config.automatic && !dtr_config.active && !ring_buf_is_empty(&tx_buf)) { + /* If automatic DTR UART is enabled and there is data to send, enable DTR UART. */ + k_work_submit(&dtr_config.dtr_uart_enable_work); + } else { + reschedule_disable(); + } +} + +void sm_host_disable_dtr_uart(void) +{ + sm_host_configure_dtr_uart(false, K_NO_WAIT); + k_work_reschedule(&dtr_config.dtr_uart_disable_work, K_NO_WAIT); +} + +void sm_host_enable_dtr_uart(void) +{ + sm_host_configure_dtr_uart(false, K_NO_WAIT); + k_work_submit(&dtr_config.dtr_uart_enable_work); +} + #if defined(CONFIG_SM_HOST_SHELL) int sm_host_shell(const struct shell *shell, size_t argc, char **argv) @@ -732,51 +970,58 @@ int sm_host_shell(const struct shell *shell, size_t argc, char **argv) return sm_host_send_cmd(argv[1], 10); } -int sm_host_shell_smsh_powerpin(const struct shell *shell, size_t argc, char **argv) +int sm_host_shell_smsh_dtr_uart_disable(const struct shell *shell, size_t argc, char **argv) { - int err; + shell_print(shell, "Disable DTR UART."); + sm_host_disable_dtr_uart(); - err = sm_host_power_pin_toggle(); - if (err) { - LOG_ERR("Failed to toggle power pin"); - } return 0; } -int sm_host_shell_smsh_indicate_enable(const struct shell *shell, size_t argc, char **argv) +int sm_host_shell_smsh_dtr_uart_enable(const struct shell *shell, size_t argc, char **argv) { - LOG_INF("Enable indicate pin callback"); - (void)sm_host_register_ind(ind_handler_backup, true); - (void)indicate_pin_enable(); + shell_print(shell, "Enable DTR UART."); + sm_host_enable_dtr_uart(); + return 0; } -int sm_host_shell_smsh_indicate_disable(const struct shell *shell, size_t argc, char **argv) +int sm_host_shell_smsh_dtr_uart_auto(const struct shell *shell, size_t argc, char **argv) { - LOG_INF("Disable indicate pin callback"); - /* indicate_pin_disable() is not called so we get one indication where we just log - * a warning that indications are not coming and then disable the indication pin. - */ - return sm_host_register_ind(NULL, true); + uint32_t timeout = 0; + + if (argc >= 2) { + timeout = strtoul(argv[1], NULL, 10); + } + if (timeout == 0) { + shell_print(shell, "Usage: smsh uart auto "); + return -EINVAL; + } + + sm_host_configure_dtr_uart(true, K_MSEC(timeout)); + + shell_print(shell, "Automatic DTR UART. Inactivity timeout %u ms", timeout); + + return 0; } SHELL_CMD_REGISTER(sm, NULL, "Send AT commands to Serial Modem device", sm_host_shell); SHELL_STATIC_SUBCMD_SET_CREATE( - sub_indicate, - SHELL_CMD(enable, NULL, "Enable/disable indicate pin callback", - sm_host_shell_smsh_indicate_enable), - SHELL_CMD(disable, NULL, "Disable indicate pin callback", - sm_host_shell_smsh_indicate_disable), - SHELL_SUBCMD_SET_END -); + sub_uart, + SHELL_CMD(enable, NULL, "Enable DTR UART. Disable automatic handling.", + sm_host_shell_smsh_dtr_uart_enable), + SHELL_CMD(disable, NULL, "Disable DTR UART. Disable automatic handling.", + sm_host_shell_smsh_dtr_uart_disable), + SHELL_CMD(auto, NULL, + "(Default) Automatically enable DTR UART from RI. Disable DTR UART after " + "inactivity period. Default is 100ms.", + sm_host_shell_smsh_dtr_uart_auto), + SHELL_SUBCMD_SET_END); SHELL_STATIC_SUBCMD_SET_CREATE( sub_smsh, - SHELL_CMD(powerpin, NULL, "Toggle power pin configured with CONFIG_SM_HOST_POWER_PIN", - sm_host_shell_smsh_powerpin), - SHELL_CMD(indicate, &sub_indicate, "Enable/disable indicate pin callback", - NULL), + SHELL_CMD(uart, &sub_uart, "Enable/Disable DTR UART.", NULL), SHELL_SUBCMD_SET_END ); diff --git a/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.conf b/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.conf index b9778776..bf90f99c 100644 --- a/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.conf +++ b/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -11,5 +11,6 @@ CONFIG_NRFX_UARTE2=y CONFIG_UART_2_INTERRUPT_DRIVEN=n CONFIG_UART_2_ASYNC=y -CONFIG_SM_HOST_POWER_PIN=23 -CONFIG_SM_HOST_INDICATE_PIN=28 + +# MARKUS TODO: There is a pointer corruption ongoing. +CONFIG_MEM_SLAB_POINTER_VALIDATE=y diff --git a/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.overlay b/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.overlay index 1ec13be6..81f1b640 100644 --- a/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.overlay +++ b/samples/sm_host_shell/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -7,7 +7,6 @@ / { chosen { ncs,sm-uart = &uart2; - ncs,sm-gpio = &gpio0; }; }; @@ -17,6 +16,20 @@ status = "okay"; }; +/ { + dte_dtr: dte_dtr { + compatible = "nordic,dte-dtr"; + dtr-gpios = <&gpio0 26 GPIO_ACTIVE_LOW>; + ri-gpios = <&gpio0 25 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; + }; +}; + +&gpio0 { + status = "okay"; + /* Use PORT event for DTR and RI pins to ensure lower power consumption. */ + sense-edge-mask = <0x06000000>; +}; + /* Serial Modem Host Shell <-> Serial Modem UART */ &uart2 { compatible = "nordic,nrf-uarte"; diff --git a/samples/sm_host_shell/src/main.c b/samples/sm_host_shell/src/main.c index b84e7351..43a77e13 100644 --- a/samples/sm_host_shell/src/main.c +++ b/samples/sm_host_shell/src/main.c @@ -26,18 +26,10 @@ void sm_host_shell_data_indication(const uint8_t *data, size_t datalen) LOG_INF("Data received (len=%d): %.*s", datalen, datalen, (const char *)data); } -#if (CONFIG_SM_HOST_INDICATE_PIN >= 0) -void sm_host_shell_indication_handler(void) +void sm_host_shell_ri_handler(void) { - int err; - - LOG_INF("Serial Modem indicate pin triggered"); - err = sm_host_power_pin_toggle(); - if (err) { - LOG_ERR("Failed to toggle power pin"); - } + LOG_INF("Ring Indicate (RI) triggered"); } -#endif /* CONFIG_SM_HOST_INDICATE_PIN */ int main(void) { @@ -45,17 +37,15 @@ int main(void) LOG_INF("Serial Modem Host Shell starts on %s", CONFIG_BOARD); - err = sm_host_init(sm_host_shell_data_indication); + err = sm_host_init(sm_host_shell_data_indication, true, K_MSEC(100)); if (err) { LOG_ERR("Failed to initialize Serial Modem: %d", err); } -#if (CONFIG_SM_HOST_INDICATE_PIN >= 0) - err = sm_host_register_ind(sm_host_shell_indication_handler, true); + err = sm_host_register_ri_handler(sm_host_shell_ri_handler); if (err) { - LOG_ERR("Failed to register indication: %d", err); + LOG_ERR("Failed to register RI handler (%d).", err); } -#endif /* CONFIG_SM_HOST_INDICATE_PIN */ return 0; } diff --git a/zephyr/module.yml b/zephyr/module.yml index dd09778c..0a8f954c 100644 --- a/zephyr/module.yml +++ b/zephyr/module.yml @@ -9,3 +9,7 @@ build: # Path to the folder that contains the CMakeLists.txt file to be included by # Zephyr build system. The `.` is the root of this repository. cmake: . + settings: + # Path to the folder containing additional device tree source files. The + # `.` is the root of this repository. + dts_root: .