Skip to content

Commit 14138d4

Browse files
optical-ocarlescufi
authored andcommitted
Bluetooth: Controller: Add coexistence implementation
To enable Bluetooth controller coexistence feature, there is implementation of ticker task, which aborts any ongoing radio events during assertion of the grant pin. This solves the co-existence issue in the role of the subordinate transceiver. Signed-off-by: Tomáš Beneš <[email protected]>
1 parent 947ef4c commit 14138d4

File tree

13 files changed

+378
-2
lines changed

13 files changed

+378
-2
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright (c) 2022-2023 Dronetag s.r.o.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: |
5+
Generic representation of Coexistance pin interface for radios. This
6+
interface is usually available on Wifi/Bluetooth/LTE modules to
7+
interact with each other when sharing same antenna. This prevents
8+
any collisions between transmissions from different modules. The grant
9+
signal should signal that the external transceiver/module is not
10+
transmitting. Therefore you are free to perform any TX operations as
11+
required. When grant pin becomes inactive then you have time to
12+
finish all of the ongoing TX operations before the external
13+
transceiver begins their transmission. This is specified by the
14+
grant-delay-us property.
15+
16+
compatible: "gpio-radio-coex"
17+
18+
include: base.yaml
19+
20+
properties:
21+
grant-gpios:
22+
type: phandle-array
23+
required: true
24+
description: |
25+
GPIO input from the external transceiver
26+
27+
grant-delay-us:
28+
type: int
29+
required: true
30+
description: |
31+
Delay after assertion for the external transceiver operation
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2022 Dronetag
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
/{
7+
coex_gpio: coex {
8+
compatible = "gpio-radio-coex";
9+
grant-gpios = <&gpio1 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_LOW)>;
10+
grant-delay-us = <150>;
11+
};
12+
};
13+
14+
&radio {
15+
coex = <&coex_gpio>;
16+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CONFIG_BT=y
2+
CONFIG_BT_DEBUG_LOG=y
3+
CONFIG_BT_DEVICE_NAME="Test beacon"
4+
5+
CONFIG_BT_LL_SW_SPLIT=y
6+
CONFIG_BT_CTLR_COEX_DRIVERS=y
7+
CONFIG_BT_CTLR_COEX_TICKER=y

samples/bluetooth/beacon/sample.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ tests:
77
tags: bluetooth
88
integration_platforms:
99
- qemu_cortex_m3
10+
11+
sample.bluetooth.beacon-coex:
12+
extra_args: CONF_FILE="prj-coex.conf"
13+
harness: bluetooth
14+
platform_allow: nrf52840dk_nrf52840
15+
tags: bluetooth

subsys/bluetooth/controller/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ if(CONFIG_BT_LL_SW_SPLIT)
155155
CONFIG_BT_HCI_MESH_EXT
156156
ll_sw/ll_mesh.c
157157
)
158+
add_subdirectory_ifdef(CONFIG_BT_CTLR_COEX_DRIVERS coex)
158159
endif()
159160

160161
if(CONFIG_SOC_COMPATIBLE_NRF)

subsys/bluetooth/controller/Kconfig.ll_sw_split

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,8 @@ config BT_CTLR_USER_CPR_ANCHOR_POINT_MOVE
969969

970970
endmenu
971971

972+
source "subsys/bluetooth/controller/coex/Kconfig"
973+
972974
comment "BLE Controller debug configuration"
973975

974976
config BT_CTLR_PROFILE_ISR
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
zephyr_library_sources_ifdef(
4+
CONFIG_BT_CTLR_COEX_TICKER coex_ticker.c
5+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Bluetooth co-existence configuration options
2+
3+
# Copyright (c) 2022 Dronetag
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
menuconfig BT_CTLR_COEX_DRIVERS
7+
bool "Bluetooth Co-existence Drivers"
8+
default n
9+
depends on BT_CTLR
10+
11+
if BT_CTLR_COEX_DRIVERS
12+
13+
config BT_CTLR_COEX_TICKER
14+
bool "Coexistence Ticker"
15+
help
16+
When enabled Coexistence device implementation is included in
17+
the controller. Which abort any radio states, when coex pin is asserted.
18+
19+
endif # BT_CTLR_COEX_DRIVERS
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/*
2+
* Copyright (c) 2022 Dronetag
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <errno.h>
8+
#include <soc.h>
9+
10+
#include <zephyr/kernel.h>
11+
#include <zephyr/device.h>
12+
#include <zephyr/init.h>
13+
#include <zephyr/logging/log.h>
14+
#include <zephyr/drivers/gpio.h>
15+
#include <zephyr/sys/__assert.h>
16+
#include <zephyr/bluetooth/hci.h>
17+
18+
#include "controller/hal/ticker.h"
19+
#include "controller/ticker/ticker.h"
20+
#include "controller/include/ll.h"
21+
#include "controller/ll_sw/nordic/hal/nrf5/debug.h"
22+
23+
LOG_MODULE_REGISTER(coex_ticker);
24+
25+
#define COEX_RADIO_WORK_DELAY_US 50
26+
#define COEX_RADIO_WORK_RESCHEDULE_DELAY_US 200
27+
28+
struct coex_ticker_config {
29+
struct gpio_dt_spec grant_spec;
30+
size_t grant_delay_us;
31+
};
32+
33+
struct coex_ticker_data {
34+
const struct device *dev;
35+
struct gpio_callback grant_irq_cb;
36+
};
37+
38+
static int time_slot_delay(uint32_t ticks_at_expire, uint32_t ticks_delay,
39+
ticker_timeout_func callback, void *context)
40+
{
41+
uint8_t instance_index;
42+
uint8_t ticker_id;
43+
int err;
44+
45+
ll_coex_ticker_id_get(&instance_index, &ticker_id);
46+
47+
/* start a secondary one-shot ticker after ticks_delay,
48+
* this will let any radio role to gracefully abort and release the
49+
* Radio h/w.
50+
*/
51+
err = ticker_start(instance_index, /* Radio instance ticker */
52+
1, /* user id for link layer ULL_HIGH */
53+
/* (MAYFLY_CALL_ID_WORKER) */
54+
ticker_id, /* ticker_id */
55+
ticks_at_expire, /* current tick */
56+
ticks_delay, /* one-shot delayed timeout */
57+
0, /* periodic timeout */
58+
0, /* periodic remainder */
59+
0, /* lazy, voluntary skips */
60+
0,
61+
callback, /* handler for executing radio abort or */
62+
/* coex work */
63+
context, /* the context for the coex operation */
64+
NULL, /* no op callback */
65+
NULL);
66+
67+
return err;
68+
}
69+
70+
71+
static void time_slot_callback_work(uint32_t ticks_at_expire,
72+
uint32_t ticks_drift,
73+
uint32_t remainder,
74+
uint16_t lazy, uint8_t force,
75+
void *context)
76+
{
77+
int ret;
78+
uint8_t instance_index;
79+
uint8_t ticker_id;
80+
const struct device *dev = context;
81+
const struct coex_ticker_config *config = dev->config;
82+
83+
__ASSERT(ll_radio_state_is_idle(),
84+
"Radio is on during coex operation.\n");
85+
86+
/* Read grant pin */
87+
if (gpio_pin_get_dt(&config->grant_spec) == 0) {
88+
DEBUG_COEX_GRANT(1);
89+
90+
if (!ll_radio_state_is_idle()) {
91+
ll_radio_state_abort();
92+
}
93+
/* Schedule another check for grant pin and abort any events scheduled */
94+
time_slot_delay(ticker_ticks_now_get(),
95+
HAL_TICKER_US_TO_TICKS(COEX_RADIO_WORK_RESCHEDULE_DELAY_US),
96+
time_slot_callback_work,
97+
context);
98+
} else {
99+
LOG_DBG("COEX Inhibit Off");
100+
DEBUG_COEX_IRQ(0);
101+
DEBUG_COEX_GRANT(0);
102+
103+
/* Enable coex pin interrupt */
104+
gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_EDGE_TO_INACTIVE);
105+
106+
/* Stop the time slot ticker */
107+
ll_coex_ticker_id_get(&instance_index, &ticker_id);
108+
109+
ret = ticker_stop(instance_index, 0, ticker_id, NULL, NULL);
110+
if (ret != TICKER_STATUS_SUCCESS &&
111+
ret != TICKER_STATUS_BUSY) {
112+
__ASSERT(0, "Failed to stop ticker.\n");
113+
}
114+
}
115+
}
116+
117+
static void time_slot_callback_abort(uint32_t ticks_at_expire,
118+
uint32_t ticks_drift,
119+
uint32_t remainder,
120+
uint16_t lazy, uint8_t force,
121+
void *context)
122+
{
123+
ll_radio_state_abort();
124+
time_slot_delay(ticks_at_expire,
125+
HAL_TICKER_US_TO_TICKS(COEX_RADIO_WORK_DELAY_US),
126+
time_slot_callback_work,
127+
context);
128+
}
129+
130+
static int coex_ticker_grant_start(const struct device *dev)
131+
{
132+
const struct coex_ticker_config *cfg = dev->config;
133+
uint32_t err;
134+
135+
err = time_slot_delay(
136+
ticker_ticks_now_get(),
137+
HAL_TICKER_US_TO_TICKS(cfg->grant_delay_us - COEX_RADIO_WORK_DELAY_US),
138+
time_slot_callback_abort,
139+
(void *)dev
140+
);
141+
142+
if (err != TICKER_STATUS_SUCCESS && err != TICKER_STATUS_BUSY) {
143+
return -EBUSY;
144+
}
145+
146+
return 0;
147+
}
148+
149+
150+
static void coex_ticker_grant_irq_handler(const struct device *dev,
151+
struct gpio_callback *cb, uint32_t pins)
152+
{
153+
ARG_UNUSED(dev);
154+
ARG_UNUSED(pins);
155+
struct coex_ticker_data *data = CONTAINER_OF(cb, struct coex_ticker_data, grant_irq_cb);
156+
const struct coex_ticker_config *config = data->dev->config;
157+
158+
LOG_DBG("COEX Inhibit IRQ Detected");
159+
DEBUG_COEX_IRQ(1);
160+
DEBUG_COEX_GRANT(0);
161+
162+
gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_DISABLE);
163+
coex_ticker_grant_start(data->dev);
164+
}
165+
166+
167+
static int coex_ticker_init(const struct device *dev)
168+
{
169+
const struct coex_ticker_config *config = dev->config;
170+
struct coex_ticker_data *data = dev->data;
171+
int res;
172+
173+
data->dev = dev;
174+
175+
DEBUG_COEX_INIT();
176+
res = gpio_pin_configure_dt(&config->grant_spec, GPIO_INPUT);
177+
if (res) {
178+
return res;
179+
}
180+
181+
gpio_init_callback(&data->grant_irq_cb,
182+
coex_ticker_grant_irq_handler,
183+
BIT(config->grant_spec.pin));
184+
185+
res = gpio_add_callback(config->grant_spec.port, &data->grant_irq_cb);
186+
if (res) {
187+
return res;
188+
}
189+
190+
res = gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_EDGE_TO_INACTIVE);
191+
if (res) {
192+
return res;
193+
}
194+
195+
return 0;
196+
}
197+
198+
#define RADIO_NODE DT_NODELABEL(radio)
199+
#define COEX_NODE DT_PROP(RADIO_NODE, coex)
200+
201+
#if DT_NODE_EXISTS(COEX_NODE)
202+
static struct coex_ticker_config config = {
203+
.grant_spec = GPIO_DT_SPEC_GET(COEX_NODE, grant_gpios),
204+
.grant_delay_us = DT_PROP(COEX_NODE, grant_delay_us)
205+
};
206+
static struct coex_ticker_data data;
207+
208+
DEVICE_DEFINE(coex_ticker, "COEX_TICKER", &coex_ticker_init, NULL,
209+
&data, &config,
210+
APPLICATION, 90,
211+
NULL);
212+
#endif
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
******************************
2+
Bluetooth co-existence drivers
3+
******************************
4+
5+
Co-existence Ticker
6+
###################
7+
8+
Implementation :file:`coex_ticker.c` is designed to utilize co-existence with another transmitter. Chips such as nordic nRF9160 provide a 1-wire co-existence interface, which allows the Bluetooth controller to suspend its activity until the other transceiver suspends its operation.
9+
10+
Nordic connect SDK provides detailed description of the 1-wire and 3-wire co-existence interface for the `SoftDevice Bluetooth controller <https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrfxlib/mpsl/doc/bluetooth_coex.html>`_
11+
12+
Similarly, as in the nordic implementation of the 1-wire interface, the coexistence ticker utilizes a single pin called BLE_GRANT, which active level (high or low) is programmable by the device tree definition.
13+
14+
.. code-block:: DTS
15+
16+
coex_gpio: coex {
17+
compatible = "gpio-radio-coex";
18+
grant-gpios = <&gpio0 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
19+
grant-delay-us = <150>;
20+
};
21+
22+
Whenever the grant pin transitions into non-active (such as 1 for the nRF9160). state the implementation starts a ticker job, which in predefined intervals cancels any radio events. This way all advertisements and other radioactivities are suspended until the grant pin transitions into an active state.

0 commit comments

Comments
 (0)