Skip to content

Commit e38e997

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 e38e997

File tree

4 files changed

+303
-0
lines changed

4 files changed

+303
-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: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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),
45+
struct modem_backend_quectel_i2c, poll_work);
46+
uint8_t buf[READ_I2C_DATA_LENGTH];
47+
bool receive_ready = false;
48+
k_spinlock_key_t key;
49+
bool lf;
50+
int ret;
51+
52+
if (!backend->open) {
53+
/* Then we previously couldn't immediately stop this work */
54+
k_work_submit(&backend->notify_closed_work);
55+
return;
56+
}
57+
58+
ret = i2c_read_dt(&backend->i2c, buf, sizeof(buf));
59+
if (ret < 0) {
60+
LOG_ERR("i2c_read: %d", ret);
61+
modem_pipe_notify_closed(&backend->pipe);
62+
return;
63+
}
64+
65+
key = k_spin_lock(&backend->receive_rb_lock);
66+
67+
/* Determine if we have received data and filter out consecutive LFs */
68+
for (size_t i = 0; i < sizeof(buf); i++) {
69+
if (buf[i] == '\0') {
70+
/* After exiting backup mode the read often contains zeros */
71+
continue;
72+
}
73+
lf = (buf[i] == '\n');
74+
if (!lf || (lf && !backend->suppress_next_lf)) {
75+
ring_buf_put(&backend->receive_ring_buf, &buf[i], 1);
76+
receive_ready = true;
77+
}
78+
backend->suppress_next_lf = lf;
79+
}
80+
81+
k_spin_unlock(&backend->receive_rb_lock, key);
82+
83+
if (receive_ready) {
84+
modem_pipe_notify_receive_ready(&backend->pipe);
85+
}
86+
87+
k_work_schedule(&backend->poll_work, K_MSEC(backend->i2c_poll_interval_ms));
88+
}
89+
90+
static int modem_backend_quectel_i2c_open(void *data)
91+
{
92+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
93+
94+
backend->open = true;
95+
k_work_schedule(&backend->poll_work, K_NO_WAIT);
96+
97+
modem_pipe_notify_opened(&backend->pipe);
98+
99+
return 0;
100+
}
101+
102+
static int modem_backend_quectel_i2c_transmit(void *data, const uint8_t *buf, size_t size)
103+
{
104+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
105+
int ret;
106+
107+
for (size_t i = 0; i < size; i++) {
108+
backend->transmit_buf[backend->transmit_i] = buf[i];
109+
backend->transmit_i++;
110+
if (buf[i] == '\n') {
111+
k_work_cancel(&backend->notify_transmit_idle_work);
112+
113+
#if CONFIG_MODEM_STATS
114+
modem_stats_buffer_advertise_length(&backend->receive_buf_stats,
115+
backend->transmit_i);
116+
#endif
117+
118+
k_sleep(K_TIMEOUT_ABS_MS(backend->next_cmd_earliest_time));
119+
120+
ret = i2c_write_dt(&backend->i2c, backend->transmit_buf,
121+
backend->transmit_i);
122+
if (ret < 0) {
123+
LOG_ERR("i2c_write: %d", ret);
124+
k_work_submit(&backend->notify_closed_work);
125+
return ret;
126+
}
127+
128+
backend->next_cmd_earliest_time = k_uptime_get() + 10;
129+
130+
k_work_submit(&backend->notify_transmit_idle_work);
131+
132+
backend->transmit_i = 0;
133+
} else if (backend->transmit_i >= backend->transmit_buf_size) {
134+
LOG_ERR("%u bytes of TX data dropped:", backend->transmit_i);
135+
backend->transmit_i = 0;
136+
}
137+
}
138+
139+
#if CONFIG_MODEM_STATS
140+
modem_stats_buffer_advertise_length(&backend->receive_buf_stats, backend->transmit_i);
141+
#endif
142+
143+
return size;
144+
}
145+
146+
static int modem_backend_quectel_i2c_receive(void *data, uint8_t *buf, size_t size)
147+
{
148+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
149+
k_spinlock_key_t key;
150+
uint32_t received;
151+
bool empty;
152+
153+
key = k_spin_lock(&backend->receive_rb_lock);
154+
155+
#if CONFIG_MODEM_STATS
156+
uint32_t length = ring_buf_size_get(&backend->receive_ring_buf);
157+
158+
modem_stats_buffer_advertise_length(&backend->receive_buf_stats, length);
159+
#endif
160+
161+
received = ring_buf_get(&backend->receive_ring_buf, buf, size);
162+
empty = ring_buf_is_empty(&backend->receive_ring_buf);
163+
k_spin_unlock(&backend->receive_rb_lock, key);
164+
165+
if (!empty) {
166+
k_work_submit(&backend->notify_receive_ready_work);
167+
}
168+
169+
return (int)received;
170+
}
171+
172+
static int modem_backend_quectel_i2c_close(void *data)
173+
{
174+
struct modem_backend_quectel_i2c *backend = (struct modem_backend_quectel_i2c *)data;
175+
int ret;
176+
177+
ret = k_work_cancel_delayable(&backend->poll_work);
178+
if (ret == 0) {
179+
k_work_submit(&backend->notify_closed_work);
180+
}
181+
backend->open = false;
182+
183+
return 0;
184+
}
185+
186+
static const struct modem_pipe_api modem_backend_quectel_i2c_api = {
187+
.open = modem_backend_quectel_i2c_open,
188+
.transmit = modem_backend_quectel_i2c_transmit,
189+
.receive = modem_backend_quectel_i2c_receive,
190+
.close = modem_backend_quectel_i2c_close,
191+
};
192+
193+
#if CONFIG_MODEM_STATS
194+
static void init_stats(struct modem_backend_quectel_i2c *backend)
195+
{
196+
char name[CONFIG_MODEM_STATS_BUFFER_NAME_SIZE];
197+
uint32_t receive_buf_size;
198+
199+
receive_buf_size = ring_buf_capacity_get(&backend->receive_ring_buf);
200+
201+
snprintk(name, sizeof(name), "%s_%s", backend->i2c->name, "rx");
202+
modem_stats_buffer_init(&backend->receive_buf_stats, name, receive_buf_size);
203+
204+
snprintk(name, sizeof(name), "%s_%s", backend->i2c->name, "tx");
205+
modem_stats_buffer_init(&backend->transmit_buf_stats, name, transmit_buf_stats);
206+
}
207+
#endif
208+
209+
struct modem_pipe *modem_backend_quectel_i2c_init(struct modem_backend_quectel_i2c *backend,
210+
const struct modem_backend_quectel_i2c_config *config)
211+
{
212+
__ASSERT_NO_MSG(config != NULL);
213+
__ASSERT_NO_MSG(config->i2c.bus != NULL);
214+
__ASSERT_NO_MSG(config->receive_buf != NULL);
215+
__ASSERT_NO_MSG(config->receive_buf_size > 0);
216+
217+
memset(backend, 0x00, sizeof(*backend));
218+
219+
backend->i2c = config->i2c;
220+
backend->i2c_poll_interval_ms = config->i2c_poll_interval_ms;
221+
backend->transmit_buf = config->transmit_buf;
222+
backend->transmit_buf_size = config->transmit_buf_size;
223+
backend->suppress_next_lf = true;
224+
backend->next_cmd_earliest_time = 0;
225+
226+
ring_buf_init(&backend->receive_ring_buf, config->receive_buf_size, config->receive_buf);
227+
backend->transmit_i = 0;
228+
backend->open = false;
229+
230+
k_work_init_delayable(&backend->poll_work, modem_backend_quectel_i2c_poll_work_handler);
231+
k_work_init(&backend->notify_receive_ready_work,
232+
modem_backend_quectel_i2c_receive_ready_handler);
233+
k_work_init(&backend->notify_transmit_idle_work,
234+
modem_backend_quectel_i2c_transmit_idle_handler);
235+
k_work_init(&backend->notify_closed_work, modem_backend_quectel_i2c_notify_closed_handler);
236+
237+
#if CONFIG_MODEM_STATS
238+
init_stats(backend);
239+
#endif
240+
241+
modem_pipe_init(&backend->pipe, backend, &modem_backend_quectel_i2c_api);
242+
243+
return &backend->pipe;
244+
}

0 commit comments

Comments
 (0)