diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt index e2246e0b422..1b587df5b08 100644 --- a/drivers/modem/CMakeLists.txt +++ b/drivers/modem/CMakeLists.txt @@ -8,6 +8,8 @@ zephyr_sources_ifdef(CONFIG_MODEM_CONTEXT modem_pin.c ) +zephyr_sources_ifdef(CONFIG_MODEM_IFACE_UART modem_iface_uart.c) + if(CONFIG_MODEM_UBLOX_SARA_R4) zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/ip) zephyr_library_sources(ublox-sara-r4.c) diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig index 3a553f8c9c2..05e0548ee47 100644 --- a/drivers/modem/Kconfig +++ b/drivers/modem/Kconfig @@ -64,6 +64,17 @@ config MODEM_CONTEXT_VERBOSE_DEBUG Enabling this setting will turn on VERY heavy debugging from the modem context helper. Do NOT leave on for production. +config MODEM_IFACE_UART + bool "UART-based modem interface" + depends on SERIAL_SUPPORT_INTERRUPT + select UART_INTERRUPT_DRIVEN + select RING_BUFFER + help + To configure this layer for use, create a modem_iface_uart_data + object and pass it's reference to modem_iface_uart_init() + along with the modem_iface reference from your modem_context object + and the UART device name. + endif # MODEM_CONTEXT config MODEM_SHELL diff --git a/drivers/modem/modem_iface_uart.c b/drivers/modem/modem_iface_uart.c new file mode 100644 index 00000000000..15d731f81b4 --- /dev/null +++ b/drivers/modem/modem_iface_uart.c @@ -0,0 +1,152 @@ +/** @file + * @brief interface for modem context + * + * UART-based modem interface implementation for modem context driver. + */ + +/* + * Copyright (c) 2019 Foundries.io + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(modem_iface_uart, CONFIG_MODEM_LOG_LEVEL); + +#include +#include + +#include "modem_context.h" +#include "modem_iface_uart.h" + +/** + * @brief Drains UART. + * + * @note Discards remaining data. + * + * @param *iface: modem interface. + * + * @retval None. + */ +static void modem_iface_uart_flush(struct modem_iface *iface) +{ + u8_t c; + + while (uart_fifo_read(iface->dev, &c, 1) > 0) { + continue; + } +} + +/** + * @brief Modem interface interrupt handler. + * + * @note Fills interfaces ring buffer with received data. + * When ring buffer is full the data is discarded. + * + * @param *uart_dev: uart device. + * + * @retval None. + */ +static void modem_iface_uart_isr(struct device *uart_dev) +{ + struct modem_context *ctx; + struct modem_iface_uart_data *data; + int rx = 0, ret; + + /* lookup the modem context */ + ctx = modem_context_from_iface_dev(uart_dev); + if (!ctx || !ctx->iface.iface_data) { + return; + } + + data = (struct modem_iface_uart_data *)(ctx->iface.iface_data); + /* get all of the data off UART as fast as we can */ + while (uart_irq_update(ctx->iface.dev) && + uart_irq_rx_ready(ctx->iface.dev)) { + rx = uart_fifo_read(ctx->iface.dev, + data->isr_buf, data->isr_buf_len); + if (rx <= 0) { + continue; + } + + ret = ring_buf_put(&data->rx_rb, data->isr_buf, rx); + if (ret != rx) { + LOG_ERR("Rx buffer doesn't have enough space. " + "Bytes pending: %d, written: %d", + rx, ret); + modem_iface_uart_flush(&ctx->iface); + k_sem_give(&data->rx_sem); + break; + } + + k_sem_give(&data->rx_sem); + } +} + +static int modem_iface_uart_read(struct modem_iface *iface, + u8_t *buf, size_t size, size_t *bytes_read) +{ + struct modem_iface_uart_data *data; + + if (!iface || !iface->iface_data) { + return -EINVAL; + } + + if (size == 0) { + *bytes_read = 0; + return 0; + } + + data = (struct modem_iface_uart_data *)(iface->iface_data); + *bytes_read = ring_buf_get(&data->rx_rb, buf, size); + + return 0; +} + +static int modem_iface_uart_write(struct modem_iface *iface, + const u8_t *buf, size_t size) +{ + if (!iface || !iface->iface_data) { + return -EINVAL; + } + + if (size == 0) { + return 0; + } + + do { + uart_poll_out(iface->dev, *buf++); + } while (--size); + + return 0; +} + +int modem_iface_uart_init(struct modem_iface *iface, + struct modem_iface_uart_data *data, + const char *dev_name) +{ + if (!iface || !data) { + return -EINVAL; + } + + /* get UART device */ + iface->dev = device_get_binding(dev_name); + if (!iface->dev) { + return -ENODEV; + } + + iface->iface_data = data; + iface->read = modem_iface_uart_read; + iface->write = modem_iface_uart_write; + + ring_buf_init(&data->rx_rb, data->rx_rb_buf_len, data->rx_rb_buf); + k_sem_init(&data->rx_sem, 0, 1); + + uart_irq_rx_disable(iface->dev); + uart_irq_tx_disable(iface->dev); + modem_iface_uart_flush(iface); + uart_irq_callback_set(iface->dev, modem_iface_uart_isr); + uart_irq_rx_enable(iface->dev); + + return 0; +} diff --git a/drivers/modem/modem_iface_uart.h b/drivers/modem/modem_iface_uart.h new file mode 100644 index 00000000000..b74fe8beeef --- /dev/null +++ b/drivers/modem/modem_iface_uart.h @@ -0,0 +1,54 @@ +/** @file + * @brief Modem interface for UART header file. + * + * Modem interface UART handling for modem context driver. + */ + +/* + * Copyright (c) 2019 Foundries.io + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_IFACE_UART_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_IFACE_UART_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct modem_iface_uart_data { + /* ISR char buffer */ + char *isr_buf; + size_t isr_buf_len; + + /* ring buffer char buffer */ + char *rx_rb_buf; + size_t rx_rb_buf_len; + + /* ring buffer */ + struct ring_buf rx_rb; + + /* rx semaphore */ + struct k_sem rx_sem; +}; + +/** + * @brief Init modem interface for UART + * + * @param *iface: modem interface to initialize. + * @param *data: modem interface data to use + * + * @retval 0 if ok, < 0 if error. + */ +int modem_iface_uart_init(struct modem_iface *iface, + struct modem_iface_uart_data *data, + const char *dev_name); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_IFACE_UART_H_ */