1 /** @file
2  * @brief interface for modem context
3  *
4  * UART-based modem interface implementation for modem context driver.
5  */
6 
7 /*
8  * Copyright (c) 2019 Foundries.io
9  *
10  * SPDX-License-Identifier: Apache-2.0
11  */
12 
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(modem_iface_uart, CONFIG_MODEM_LOG_LEVEL);
15 
16 #include <zephyr/kernel.h>
17 #include <zephyr/drivers/uart.h>
18 
19 #include "modem_context.h"
20 #include "modem_iface_uart.h"
21 
22 /**
23  * @brief  Drains UART.
24  *
25  * @note   Discards remaining data.
26  *
27  * @param  iface: modem interface.
28  *
29  * @retval None.
30  */
modem_iface_uart_flush(struct modem_iface * iface)31 static void modem_iface_uart_flush(struct modem_iface *iface)
32 {
33 	uint8_t c;
34 
35 	while (uart_fifo_read(iface->dev, &c, 1) > 0) {
36 		continue;
37 	}
38 }
39 
40 /**
41  * @brief  Modem interface interrupt handler.
42  *
43  * @note   Fills interfaces ring buffer with received data.
44  *         When ring buffer is full the data is discarded.
45  *
46  * @param  uart_dev: uart device.
47  *
48  * @retval None.
49  */
modem_iface_uart_isr(const struct device * uart_dev,void * user_data)50 static void modem_iface_uart_isr(const struct device *uart_dev,
51 				 void *user_data)
52 {
53 	struct modem_context *ctx;
54 	struct modem_iface_uart_data *data;
55 	int rx = 0, ret;
56 	uint8_t *dst;
57 	uint32_t partial_size = 0;
58 	uint32_t total_size = 0;
59 
60 	ARG_UNUSED(user_data);
61 
62 	/* lookup the modem context */
63 	ctx = modem_context_from_iface_dev(uart_dev);
64 	if (!ctx || !ctx->iface.iface_data) {
65 		return;
66 	}
67 
68 	data = (struct modem_iface_uart_data *)(ctx->iface.iface_data);
69 	/* get all of the data off UART as fast as we can */
70 	while (uart_irq_update(ctx->iface.dev) &&
71 	       uart_irq_rx_ready(ctx->iface.dev)) {
72 		if (!partial_size) {
73 			partial_size = ring_buf_put_claim(&data->rx_rb, &dst,
74 							  UINT32_MAX);
75 		}
76 		if (!partial_size) {
77 			if (data->hw_flow_control) {
78 				uart_irq_rx_disable(ctx->iface.dev);
79 			} else {
80 				LOG_ERR("Rx buffer doesn't have enough space");
81 				modem_iface_uart_flush(&ctx->iface);
82 			}
83 			break;
84 		}
85 
86 		rx = uart_fifo_read(ctx->iface.dev, dst, partial_size);
87 		if (rx <= 0) {
88 			continue;
89 		}
90 
91 		dst += rx;
92 		total_size += rx;
93 		partial_size -= rx;
94 	}
95 
96 	ret = ring_buf_put_finish(&data->rx_rb, total_size);
97 	__ASSERT_NO_MSG(ret == 0);
98 
99 	if (total_size > 0) {
100 		k_sem_give(&data->rx_sem);
101 	}
102 }
103 
modem_iface_uart_read(struct modem_iface * iface,uint8_t * buf,size_t size,size_t * bytes_read)104 static int modem_iface_uart_read(struct modem_iface *iface,
105 				 uint8_t *buf, size_t size, size_t *bytes_read)
106 {
107 	struct modem_iface_uart_data *data;
108 
109 	if (!iface || !iface->iface_data) {
110 		return -EINVAL;
111 	}
112 
113 	if (size == 0) {
114 		*bytes_read = 0;
115 		return 0;
116 	}
117 
118 	data = (struct modem_iface_uart_data *)(iface->iface_data);
119 	*bytes_read = ring_buf_get(&data->rx_rb, buf, size);
120 
121 	if (data->hw_flow_control && *bytes_read == 0) {
122 		uart_irq_rx_enable(iface->dev);
123 	}
124 
125 	return 0;
126 }
127 
modem_iface_uart_write(struct modem_iface * iface,const uint8_t * buf,size_t size)128 static int modem_iface_uart_write(struct modem_iface *iface,
129 				  const uint8_t *buf, size_t size)
130 {
131 	if (!iface || !iface->iface_data) {
132 		return -EINVAL;
133 	}
134 
135 	if (size == 0) {
136 		return 0;
137 	}
138 
139 	do {
140 		uart_poll_out(iface->dev, *buf++);
141 	} while (--size);
142 
143 	return 0;
144 }
145 
modem_iface_uart_init_dev(struct modem_iface * iface,const struct device * dev)146 int modem_iface_uart_init_dev(struct modem_iface *iface,
147 			      const struct device *dev)
148 {
149 	/* get UART device */
150 	const struct device *prev = iface->dev;
151 
152 	if (!device_is_ready(dev)) {
153 		return -ENODEV;
154 	}
155 
156 	/* Check if there's already a device inited to this iface. If so,
157 	 * interrupts needs to be disabled on that too before switching to avoid
158 	 * race conditions with modem_iface_uart_isr.
159 	 */
160 	if (prev) {
161 		uart_irq_tx_disable(prev);
162 		uart_irq_rx_disable(prev);
163 	}
164 
165 	uart_irq_rx_disable(dev);
166 	uart_irq_tx_disable(dev);
167 	iface->dev = dev;
168 
169 	modem_iface_uart_flush(iface);
170 	uart_irq_callback_set(iface->dev, modem_iface_uart_isr);
171 	uart_irq_rx_enable(iface->dev);
172 
173 	if (prev) {
174 		uart_irq_rx_enable(prev);
175 	}
176 
177 	return 0;
178 }
179 
modem_iface_uart_init(struct modem_iface * iface,struct modem_iface_uart_data * data,const struct modem_iface_uart_config * config)180 int modem_iface_uart_init(struct modem_iface *iface, struct modem_iface_uart_data *data,
181 			  const struct modem_iface_uart_config *config)
182 {
183 	int ret;
184 
185 	if (iface == NULL || data == NULL || config == NULL) {
186 		return -EINVAL;
187 	}
188 
189 	iface->iface_data = data;
190 	iface->read = modem_iface_uart_read;
191 	iface->write = modem_iface_uart_write;
192 
193 	ring_buf_init(&data->rx_rb, config->rx_rb_buf_len, config->rx_rb_buf);
194 	k_sem_init(&data->rx_sem, 0, 1);
195 
196 	/* Configure hardware flow control */
197 	data->hw_flow_control = config->hw_flow_control;
198 
199 	/* Get UART device */
200 	ret = modem_iface_uart_init_dev(iface, config->dev);
201 	if (ret < 0) {
202 		iface->iface_data = NULL;
203 		iface->read = NULL;
204 		iface->write = NULL;
205 
206 		return ret;
207 	}
208 
209 	return 0;
210 }
211