/** @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;
}
