Skip to content

Commit 752016b

Browse files
committed
modem: backends: add Quectel I2C modem backend
To be used by the Quectel lx6 GNSS driver. Signed-off-by: Nick Ward <nix.ward@gmail.com>
1 parent 4ec4351 commit 752016b

File tree

4 files changed

+296
-0
lines changed

4 files changed

+296
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2025 Nick Ward
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/drivers/i2c.h>
8+
#include <zephyr/kernel.h>
9+
#include <zephyr/modem/pipe.h>
10+
#include <zephyr/modem/stats.h>
11+
#include <zephyr/sys/atomic.h>
12+
#include <zephyr/sys/ring_buffer.h>
13+
#include <zephyr/types.h>
14+
15+
#ifndef ZEPHYR_MODEM_BACKEND_QUECTEL_I2C_
16+
#define ZEPHYR_MODEM_BACKEND_QUECTEL_I2C_
17+
struct modem_backend_quectel_i2c_config {
18+
const struct i2c_dt_spec i2c;
19+
uint16_t i2c_poll_interval_ms;
20+
uint8_t *receive_buf;
21+
uint8_t *transmit_buf;
22+
uint16_t receive_buf_size;
23+
uint16_t transmit_buf_size;
24+
};
25+
26+
struct modem_backend_quectel_i2c {
27+
struct i2c_dt_spec i2c;
28+
uint16_t i2c_poll_interval_ms;
29+
uint8_t *transmit_buf;
30+
uint16_t transmit_buf_size;
31+
uint16_t transmit_i;
32+
struct modem_pipe pipe;
33+
struct k_work_delayable poll_work;
34+
struct k_work notify_receive_ready_work;
35+
struct k_work notify_transmit_idle_work;
36+
struct k_work notify_closed_work;
37+
struct ring_buf receive_ring_buf;
38+
struct k_spinlock receive_rb_lock;
39+
bool suppress_next_lf;
40+
int64_t next_cmd_earliest_time;
41+
bool open;
42+
43+
#if CONFIG_MODEM_STATS
44+
struct modem_stats_buffer receive_buf_stats;
45+
struct modem_stats_buffer transmit_buf_stats;
46+
#endif
47+
};
48+
49+
struct modem_pipe *
50+
modem_backend_quectel_i2c_init(struct modem_backend_quectel_i2c *backend,
51+
const struct modem_backend_quectel_i2c_config *config);
52+
53+
#endif /* ZEPHYR_MODEM_BACKEND_QUECTEL_I2C_ */

subsys/modem/backends/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
zephyr_library()
55

6+
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_QUECTEL_I2C modem_backend_quectel_i2c.c)
67
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_TTY modem_backend_tty.c)
78
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART modem_backend_uart.c)
89
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART_ISR modem_backend_uart_isr.c)

subsys/modem/backends/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,8 @@ endif # MODEM_BACKEND_UART_ASYNC_HWFC
6464
endif # MODEM_BACKEND_UART_ASYNC
6565

6666
endif # MODEM_BACKEND_UART
67+
68+
config MODEM_BACKEND_QUECTEL_I2C
69+
bool "Modem Quectel I2C backend module"
70+
select MODEM_PIPE
71+
select RING_BUFFER
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* Copyright (c) 2025 Nick Ward
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/modem/backend/quectel_i2c.h>
8+
9+
#include <string.h>
10+
11+
/* I2C peripheral buffer capacity */
12+
#define READ_I2C_DATA_LENGTH 255
13+
14+
#include <zephyr/logging/log.h>
15+
LOG_MODULE_REGISTER(modem_backend_quectel_i2c, CONFIG_MODEM_MODULES_LOG_LEVEL);
16+
17+
static void modem_backend_quectel_i2c_receive_ready_handler(struct k_work *work)
18+
{
19+
struct modem_backend_quectel_i2c *backend =
20+
CONTAINER_OF(work, struct modem_backend_quectel_i2c, notify_receive_ready_work);
21+
22+
modem_pipe_notify_receive_ready(&backend->pipe);
23+
}
24+
25+
static void modem_backend_quectel_i2c_transmit_idle_handler(struct k_work *work)
26+
{
27+
struct modem_backend_quectel_i2c *backend =
28+
CONTAINER_OF(work, struct modem_backend_quectel_i2c, notify_transmit_idle_work);
29+
30+
modem_pipe_notify_transmit_idle(&backend->pipe);
31+
}
32+
33+
static void modem_backend_quectel_i2c_notify_closed_handler(struct k_work *work)
34+
{
35+
struct modem_backend_quectel_i2c *backend =
36+
CONTAINER_OF(work, struct modem_backend_quectel_i2c, notify_closed_work);
37+
38+
modem_pipe_notify_closed(&backend->pipe);
39+
}
40+
41+
static void modem_backend_quectel_i2c_poll_work_handler(struct k_work *work)
42+
{
43+
struct modem_backend_quectel_i2c *backend =
44+
CONTAINER_OF(k_work_delayable_from_work(work), struct modem_backend_quectel_i2c, poll_work);
45+
uint8_t buf[READ_I2C_DATA_LENGTH];
46+
bool receive_ready = false;
47+
k_spinlock_key_t key;
48+
int ret;
49+
50+
if (!backend->open) {
51+
/* Then we previously couldn't immediately stop this work */
52+
k_work_submit(&backend->notify_closed_work);
53+
return;
54+
}
55+
56+
ret = i2c_read_dt(&backend->i2c, buf, sizeof(buf));
57+
if (ret < 0) {
58+
LOG_ERR("i2c_read: %d", ret);
59+
modem_pipe_notify_closed(&backend->pipe);
60+
return;
61+
}
62+
63+
key = k_spin_lock(&backend->receive_rb_lock);
64+
65+
/* Determine if we have received data and filter out consecutive LFs */
66+
for (size_t i = 0; i < sizeof(buf); i++) {
67+
if (buf[i] == '\0') {
68+
/* After exiting backup mode the read often contains zeros */
69+
continue;
70+
}
71+
bool lf = (buf[i] == '\n');
72+
if (!lf || (lf && !backend->suppress_next_lf)) {
73+
ring_buf_put(&backend->receive_ring_buf, &buf[i], 1);
74+
receive_ready = true;
75+
}
76+
backend->suppress_next_lf = lf;
77+
}
78+
79+
k_spin_unlock(&backend->receive_rb_lock, key);
80+
81+
if (receive_ready) {
82+
modem_pipe_notify_receive_ready(&backend->pipe);
83+
}
84+
85+
k_work_schedule(&backend->poll_work, K_MSEC(backend->i2c_poll_interval_ms));
86+
}
87+
88+
static int modem_backend_quectel_i2c_open(void *data)
89+
{
90+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
91+
92+
backend->open = true;
93+
k_work_schedule(&backend->poll_work, K_NO_WAIT);
94+
95+
modem_pipe_notify_opened(&backend->pipe);
96+
97+
return 0;
98+
}
99+
100+
static int modem_backend_quectel_i2c_transmit(void *data, const uint8_t *buf, size_t size)
101+
{
102+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
103+
int ret;
104+
105+
for (size_t i = 0; i < size; i++) {
106+
backend->transmit_buf[backend->transmit_i] = buf[i];
107+
backend->transmit_i++;
108+
if (buf[i] == '\n') {
109+
k_work_cancel(&backend->notify_transmit_idle_work);
110+
111+
#if CONFIG_MODEM_STATS
112+
modem_stats_buffer_advertise_length(&backend->receive_buf_stats, backend->transmit_i);
113+
#endif
114+
115+
k_sleep(K_TIMEOUT_ABS_MS(backend->next_cmd_earliest_time));
116+
117+
ret = i2c_write_dt(&backend->i2c, backend->transmit_buf, backend->transmit_i);
118+
if (ret < 0) {
119+
LOG_ERR("i2c_write: %d", ret);
120+
k_work_submit(&backend->notify_closed_work);
121+
return ret;
122+
}
123+
124+
backend->next_cmd_earliest_time = k_uptime_get() + 10;
125+
126+
k_work_submit(&backend->notify_transmit_idle_work);
127+
128+
backend->transmit_i = 0;
129+
} else if (backend->transmit_i >= backend->transmit_buf_size) {
130+
LOG_ERR("%u bytes of TX data dropped:", backend->transmit_i);
131+
backend->transmit_i = 0;
132+
}
133+
}
134+
135+
#if CONFIG_MODEM_STATS
136+
modem_stats_buffer_advertise_length(&backend->receive_buf_stats, backend->transmit_i);
137+
#endif
138+
139+
return size;
140+
}
141+
142+
static int modem_backend_quectel_i2c_receive(void *data, uint8_t *buf, size_t size)
143+
{
144+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
145+
k_spinlock_key_t key;
146+
uint32_t received;
147+
bool empty;
148+
149+
key = k_spin_lock(&backend->receive_rb_lock);
150+
151+
#if CONFIG_MODEM_STATS
152+
uint32_t length = ring_buf_size_get(&backend->receive_ring_buf);
153+
modem_stats_buffer_advertise_length(&backend->receive_buf_stats, length);
154+
#endif
155+
156+
received = ring_buf_get(&backend->receive_ring_buf, buf, size);
157+
empty = ring_buf_is_empty(&backend->receive_ring_buf);
158+
k_spin_unlock(&backend->receive_rb_lock, key);
159+
160+
if (!empty) {
161+
k_work_submit(&backend->notify_receive_ready_work);
162+
}
163+
164+
return (int)received;
165+
}
166+
167+
static int modem_backend_quectel_i2c_close(void *data)
168+
{
169+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
170+
int ret;
171+
172+
ret = k_work_cancel_delayable(&backend->poll_work);
173+
if (ret == 0) {
174+
k_work_submit(&backend->notify_closed_work);
175+
}
176+
backend->open = false;
177+
178+
return 0;
179+
}
180+
181+
static const struct modem_pipe_api modem_backend_quectel_i2c_api = {
182+
.open = modem_backend_quectel_i2c_open,
183+
.transmit = modem_backend_quectel_i2c_transmit,
184+
.receive = modem_backend_quectel_i2c_receive,
185+
.close = modem_backend_quectel_i2c_close,
186+
};
187+
188+
#if CONFIG_MODEM_STATS
189+
static void init_stats(struct modem_backend_quectel_i2c *backend)
190+
{
191+
char name[CONFIG_MODEM_STATS_BUFFER_NAME_SIZE];
192+
uint32_t receive_buf_size;
193+
194+
receive_buf_size = ring_buf_capacity_get(&backend->receive_ring_buf);
195+
196+
snprintk(name, sizeof(name), "%s_%s", backend->i2c->name, "rx");
197+
modem_stats_buffer_init(&backend->receive_buf_stats, name, receive_buf_size);
198+
199+
snprintk(name, sizeof(name), "%s_%s", backend->i2c->name, "tx");
200+
modem_stats_buffer_init(&backend->transmit_buf_stats, name, transmit_buf_stats);
201+
}
202+
#endif
203+
204+
struct modem_pipe *modem_backend_quectel_i2c_init(struct modem_backend_quectel_i2c *backend,
205+
const struct modem_backend_quectel_i2c_config *config)
206+
{
207+
__ASSERT_NO_MSG(config != NULL);
208+
__ASSERT_NO_MSG(config->i2c.dev != NULL);
209+
__ASSERT_NO_MSG(config->receive_buf != NULL);
210+
__ASSERT_NO_MSG(config->receive_buf_size > 0);
211+
212+
memset(backend, 0x00, sizeof(*backend));
213+
214+
backend->i2c = config->i2c;
215+
backend->i2c_poll_interval_ms = config->i2c_poll_interval_ms;
216+
backend->transmit_buf = config->transmit_buf;
217+
backend->transmit_buf_size = config->transmit_buf_size;
218+
backend->suppress_next_lf = true;
219+
backend->next_cmd_earliest_time = 0;
220+
221+
ring_buf_init(&backend->receive_ring_buf, config->receive_buf_size, config->receive_buf);
222+
backend->transmit_i = 0;
223+
backend->open = false;
224+
225+
k_work_init_delayable(&backend->poll_work, modem_backend_quectel_i2c_poll_work_handler);
226+
k_work_init(&backend->notify_receive_ready_work, modem_backend_quectel_i2c_receive_ready_handler);
227+
k_work_init(&backend->notify_transmit_idle_work, modem_backend_quectel_i2c_transmit_idle_handler);
228+
k_work_init(&backend->notify_closed_work, modem_backend_quectel_i2c_notify_closed_handler);
229+
230+
#if CONFIG_MODEM_STATS
231+
init_stats(backend);
232+
#endif
233+
234+
modem_pipe_init(&backend->pipe, backend, &modem_backend_quectel_i2c_api);
235+
236+
return &backend->pipe;
237+
}

0 commit comments

Comments
 (0)