blob: 220dfe685c8892f7ef3bef7438aa149201726736 [file] [log] [blame] [edit]
/** @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 <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_iface_uart, CONFIG_MODEM_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#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)
{
uint8_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(const struct device *uart_dev,
void *user_data)
{
struct modem_context *ctx;
struct modem_iface_uart_data *data;
int rx = 0, ret;
uint8_t *dst;
uint32_t partial_size = 0;
uint32_t total_size = 0;
ARG_UNUSED(user_data);
/* 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)) {
if (!partial_size) {
partial_size = ring_buf_put_claim(&data->rx_rb, &dst,
UINT32_MAX);
}
if (!partial_size) {
if (data->hw_flow_control) {
uart_irq_rx_disable(ctx->iface.dev);
} else {
LOG_ERR("Rx buffer doesn't have enough space");
modem_iface_uart_flush(&ctx->iface);
}
break;
}
rx = uart_fifo_read(ctx->iface.dev, dst, partial_size);
if (rx <= 0) {
continue;
}
dst += rx;
total_size += rx;
partial_size -= rx;
}
ret = ring_buf_put_finish(&data->rx_rb, total_size);
__ASSERT_NO_MSG(ret == 0);
if (total_size > 0) {
k_sem_give(&data->rx_sem);
}
}
static int modem_iface_uart_read(struct modem_iface *iface,
uint8_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);
if (data->hw_flow_control && *bytes_read == 0) {
uart_irq_rx_enable(iface->dev);
}
return 0;
}
static bool mux_is_active(struct modem_iface *iface)
{
bool active = false;
#if defined(CONFIG_UART_MUX_DEVICE_NAME)
active = strncmp(CONFIG_UART_MUX_DEVICE_NAME, iface->dev->name,
sizeof(CONFIG_UART_MUX_DEVICE_NAME) - 1) == 0;
#endif /* CONFIG_UART_MUX_DEVICE_NAME */
return active;
}
static int modem_iface_uart_write(struct modem_iface *iface,
const uint8_t *buf, size_t size)
{
if (!iface || !iface->iface_data) {
return -EINVAL;
}
if (size == 0) {
return 0;
}
/* If we're using gsm_mux, We don't want to use poll_out because sending
* one byte at a time causes each byte to get wrapped in muxing headers.
* But we can safely call uart_fifo_fill outside of ISR context when
* muxing because uart_mux implements it in software.
*/
if (mux_is_active(iface)) {
uart_fifo_fill(iface->dev, buf, size);
} else {
do {
uart_poll_out(iface->dev, *buf++);
} while (--size);
}
return 0;
}
int modem_iface_uart_init_dev(struct modem_iface *iface,
const struct device *dev)
{
/* get UART device */
const struct device *prev = iface->dev;
if (!device_is_ready(dev)) {
return -ENODEV;
}
/* Check if there's already a device inited to this iface. If so,
* interrupts needs to be disabled on that too before switching to avoid
* race conditions with modem_iface_uart_isr.
*/
if (prev) {
uart_irq_tx_disable(prev);
uart_irq_rx_disable(prev);
}
uart_irq_rx_disable(dev);
uart_irq_tx_disable(dev);
iface->dev = dev;
modem_iface_uart_flush(iface);
uart_irq_callback_set(iface->dev, modem_iface_uart_isr);
uart_irq_rx_enable(iface->dev);
if (prev) {
uart_irq_rx_enable(prev);
}
return 0;
}
int modem_iface_uart_init(struct modem_iface *iface, struct modem_iface_uart_data *data,
const struct modem_iface_uart_config *config)
{
int ret;
if (iface == NULL || data == NULL || config == NULL) {
return -EINVAL;
}
iface->iface_data = data;
iface->read = modem_iface_uart_read;
iface->write = modem_iface_uart_write;
ring_buf_init(&data->rx_rb, config->rx_rb_buf_len, config->rx_rb_buf);
k_sem_init(&data->rx_sem, 0, 1);
/* Configure hardware flow control */
data->hw_flow_control = config->hw_flow_control;
/* Get UART device */
ret = modem_iface_uart_init_dev(iface, config->dev);
if (ret < 0) {
iface->iface_data = NULL;
iface->read = NULL;
iface->write = NULL;
return ret;
}
return 0;
}