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š <tomas@dronetag.cz>
This commit is contained in:
Tomáš Beneš 2023-03-15 13:43:51 +01:00 committed by Carles Cufí
parent 947ef4c96b
commit 14138d4a34
13 changed files with 378 additions and 2 deletions

View file

@ -0,0 +1,31 @@
# Copyright (c) 2022-2023 Dronetag s.r.o.
# SPDX-License-Identifier: Apache-2.0
description: |
Generic representation of Coexistance pin interface for radios. This
interface is usually available on Wifi/Bluetooth/LTE modules to
interact with each other when sharing same antenna. This prevents
any collisions between transmissions from different modules. The grant
signal should signal that the external transceiver/module is not
transmitting. Therefore you are free to perform any TX operations as
required. When grant pin becomes inactive then you have time to
finish all of the ongoing TX operations before the external
transceiver begins their transmission. This is specified by the
grant-delay-us property.
compatible: "gpio-radio-coex"
include: base.yaml
properties:
grant-gpios:
type: phandle-array
required: true
description: |
GPIO input from the external transceiver
grant-delay-us:
type: int
required: true
description: |
Delay after assertion for the external transceiver operation

View file

@ -0,0 +1,16 @@
/*
* Copyright 2022 Dronetag
*
* SPDX-License-Identifier: Apache-2.0
*/
/{
coex_gpio: coex {
compatible = "gpio-radio-coex";
grant-gpios = <&gpio1 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_LOW)>;
grant-delay-us = <150>;
};
};
&radio {
coex = <&coex_gpio>;
};

View file

@ -0,0 +1,7 @@
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_DEVICE_NAME="Test beacon"
CONFIG_BT_LL_SW_SPLIT=y
CONFIG_BT_CTLR_COEX_DRIVERS=y
CONFIG_BT_CTLR_COEX_TICKER=y

View file

@ -7,3 +7,9 @@ tests:
tags: bluetooth
integration_platforms:
- qemu_cortex_m3
sample.bluetooth.beacon-coex:
extra_args: CONF_FILE="prj-coex.conf"
harness: bluetooth
platform_allow: nrf52840dk_nrf52840
tags: bluetooth

View file

@ -155,6 +155,7 @@ if(CONFIG_BT_LL_SW_SPLIT)
CONFIG_BT_HCI_MESH_EXT
ll_sw/ll_mesh.c
)
add_subdirectory_ifdef(CONFIG_BT_CTLR_COEX_DRIVERS coex)
endif()
if(CONFIG_SOC_COMPATIBLE_NRF)

View file

@ -969,6 +969,8 @@ config BT_CTLR_USER_CPR_ANCHOR_POINT_MOVE
endmenu
source "subsys/bluetooth/controller/coex/Kconfig"
comment "BLE Controller debug configuration"
config BT_CTLR_PROFILE_ISR

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library_sources_ifdef(
CONFIG_BT_CTLR_COEX_TICKER coex_ticker.c
)

View file

@ -0,0 +1,19 @@
# Bluetooth co-existence configuration options
# Copyright (c) 2022 Dronetag
# SPDX-License-Identifier: Apache-2.0
menuconfig BT_CTLR_COEX_DRIVERS
bool "Bluetooth Co-existence Drivers"
default n
depends on BT_CTLR
if BT_CTLR_COEX_DRIVERS
config BT_CTLR_COEX_TICKER
bool "Coexistence Ticker"
help
When enabled Coexistence device implementation is included in
the controller. Which abort any radio states, when coex pin is asserted.
endif # BT_CTLR_COEX_DRIVERS

View file

@ -0,0 +1,212 @@
/*
* Copyright (c) 2022 Dronetag
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <soc.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/bluetooth/hci.h>
#include "controller/hal/ticker.h"
#include "controller/ticker/ticker.h"
#include "controller/include/ll.h"
#include "controller/ll_sw/nordic/hal/nrf5/debug.h"
LOG_MODULE_REGISTER(coex_ticker);
#define COEX_RADIO_WORK_DELAY_US 50
#define COEX_RADIO_WORK_RESCHEDULE_DELAY_US 200
struct coex_ticker_config {
struct gpio_dt_spec grant_spec;
size_t grant_delay_us;
};
struct coex_ticker_data {
const struct device *dev;
struct gpio_callback grant_irq_cb;
};
static int time_slot_delay(uint32_t ticks_at_expire, uint32_t ticks_delay,
ticker_timeout_func callback, void *context)
{
uint8_t instance_index;
uint8_t ticker_id;
int err;
ll_coex_ticker_id_get(&instance_index, &ticker_id);
/* start a secondary one-shot ticker after ticks_delay,
* this will let any radio role to gracefully abort and release the
* Radio h/w.
*/
err = ticker_start(instance_index, /* Radio instance ticker */
1, /* user id for link layer ULL_HIGH */
/* (MAYFLY_CALL_ID_WORKER) */
ticker_id, /* ticker_id */
ticks_at_expire, /* current tick */
ticks_delay, /* one-shot delayed timeout */
0, /* periodic timeout */
0, /* periodic remainder */
0, /* lazy, voluntary skips */
0,
callback, /* handler for executing radio abort or */
/* coex work */
context, /* the context for the coex operation */
NULL, /* no op callback */
NULL);
return err;
}
static void time_slot_callback_work(uint32_t ticks_at_expire,
uint32_t ticks_drift,
uint32_t remainder,
uint16_t lazy, uint8_t force,
void *context)
{
int ret;
uint8_t instance_index;
uint8_t ticker_id;
const struct device *dev = context;
const struct coex_ticker_config *config = dev->config;
__ASSERT(ll_radio_state_is_idle(),
"Radio is on during coex operation.\n");
/* Read grant pin */
if (gpio_pin_get_dt(&config->grant_spec) == 0) {
DEBUG_COEX_GRANT(1);
if (!ll_radio_state_is_idle()) {
ll_radio_state_abort();
}
/* Schedule another check for grant pin and abort any events scheduled */
time_slot_delay(ticker_ticks_now_get(),
HAL_TICKER_US_TO_TICKS(COEX_RADIO_WORK_RESCHEDULE_DELAY_US),
time_slot_callback_work,
context);
} else {
LOG_DBG("COEX Inhibit Off");
DEBUG_COEX_IRQ(0);
DEBUG_COEX_GRANT(0);
/* Enable coex pin interrupt */
gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_EDGE_TO_INACTIVE);
/* Stop the time slot ticker */
ll_coex_ticker_id_get(&instance_index, &ticker_id);
ret = ticker_stop(instance_index, 0, ticker_id, NULL, NULL);
if (ret != TICKER_STATUS_SUCCESS &&
ret != TICKER_STATUS_BUSY) {
__ASSERT(0, "Failed to stop ticker.\n");
}
}
}
static void time_slot_callback_abort(uint32_t ticks_at_expire,
uint32_t ticks_drift,
uint32_t remainder,
uint16_t lazy, uint8_t force,
void *context)
{
ll_radio_state_abort();
time_slot_delay(ticks_at_expire,
HAL_TICKER_US_TO_TICKS(COEX_RADIO_WORK_DELAY_US),
time_slot_callback_work,
context);
}
static int coex_ticker_grant_start(const struct device *dev)
{
const struct coex_ticker_config *cfg = dev->config;
uint32_t err;
err = time_slot_delay(
ticker_ticks_now_get(),
HAL_TICKER_US_TO_TICKS(cfg->grant_delay_us - COEX_RADIO_WORK_DELAY_US),
time_slot_callback_abort,
(void *)dev
);
if (err != TICKER_STATUS_SUCCESS && err != TICKER_STATUS_BUSY) {
return -EBUSY;
}
return 0;
}
static void coex_ticker_grant_irq_handler(const struct device *dev,
struct gpio_callback *cb, uint32_t pins)
{
ARG_UNUSED(dev);
ARG_UNUSED(pins);
struct coex_ticker_data *data = CONTAINER_OF(cb, struct coex_ticker_data, grant_irq_cb);
const struct coex_ticker_config *config = data->dev->config;
LOG_DBG("COEX Inhibit IRQ Detected");
DEBUG_COEX_IRQ(1);
DEBUG_COEX_GRANT(0);
gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_DISABLE);
coex_ticker_grant_start(data->dev);
}
static int coex_ticker_init(const struct device *dev)
{
const struct coex_ticker_config *config = dev->config;
struct coex_ticker_data *data = dev->data;
int res;
data->dev = dev;
DEBUG_COEX_INIT();
res = gpio_pin_configure_dt(&config->grant_spec, GPIO_INPUT);
if (res) {
return res;
}
gpio_init_callback(&data->grant_irq_cb,
coex_ticker_grant_irq_handler,
BIT(config->grant_spec.pin));
res = gpio_add_callback(config->grant_spec.port, &data->grant_irq_cb);
if (res) {
return res;
}
res = gpio_pin_interrupt_configure_dt(&config->grant_spec, GPIO_INT_EDGE_TO_INACTIVE);
if (res) {
return res;
}
return 0;
}
#define RADIO_NODE DT_NODELABEL(radio)
#define COEX_NODE DT_PROP(RADIO_NODE, coex)
#if DT_NODE_EXISTS(COEX_NODE)
static struct coex_ticker_config config = {
.grant_spec = GPIO_DT_SPEC_GET(COEX_NODE, grant_gpios),
.grant_delay_us = DT_PROP(COEX_NODE, grant_delay_us)
};
static struct coex_ticker_data data;
DEVICE_DEFINE(coex_ticker, "COEX_TICKER", &coex_ticker_init, NULL,
&data, &config,
APPLICATION, 90,
NULL);
#endif

View file

@ -0,0 +1,22 @@
******************************
Bluetooth co-existence drivers
******************************
Co-existence Ticker
###################
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.
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>`_
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.
.. code-block:: DTS
coex_gpio: coex {
compatible = "gpio-radio-coex";
grant-gpios = <&gpio0 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
grant-delay-us = <150>;
};
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.

View file

@ -331,5 +331,7 @@ uint8_t ll_conn_iso_accept_timeout_set(uint16_t timeout);
/* External co-operation */
void ll_timeslice_ticker_id_get(uint8_t * const instance_index,
uint8_t * const ticker_id);
void ll_coex_ticker_id_get(uint8_t * const instance_index,
uint8_t * const ticker_id);
void ll_radio_state_abort(void);
uint32_t ll_radio_state_is_idle(void);

View file

@ -343,3 +343,39 @@
#define DEBUG_RADIO_START_M(flag)
#define DEBUG_RADIO_CLOSE_M(flag)
#endif /* CONFIG_BT_CTLR_DEBUG_PINS */
#if defined(CONFIG_BT_CTLR_DEBUG_PINS) || \
defined(CONFIG_BT_CTLR_DEBUG_PINS_CPUAPP)
#define DEBUG_COEX_PORT NRF_P1
#define DEBUG_COEX_PIN_GRANT BIT(12)
#define DEBUG_COEX_PIN_IRQ BIT(13)
#define DEBUG_COEX_PIN_MASK (DEBUG_COEX_PIN_IRQ | DEBUG_COEX_PIN_GRANT)
#define DEBUG_COEX_INIT() \
do { \
DEBUG_COEX_PORT->DIRSET = DEBUG_COEX_PIN_MASK; \
DEBUG_COEX_PORT->OUTCLR = DEBUG_COEX_PIN_MASK; \
} while (0)
#define DEBUG_COEX_GRANT(flag) \
do { \
if (flag) { \
DEBUG_COEX_PORT->OUTSET = DEBUG_COEX_PIN_GRANT; \
} else { \
DEBUG_COEX_PORT->OUTCLR = DEBUG_COEX_PIN_GRANT; \
} \
} while (0)
#define DEBUG_COEX_IRQ(flag) \
do { \
if (flag) { \
DEBUG_COEX_PORT->OUTSET = DEBUG_COEX_PIN_IRQ; \
} else { \
DEBUG_COEX_PORT->OUTCLR = DEBUG_COEX_PIN_IRQ; \
} \
} while (0)
#else
#define DEBUG_COEX_INIT()
#define DEBUG_COEX_GRANT(flag)
#define DEBUG_COEX_IRQ(flag)
#endif /* CONFIG_BT_CTLR_DEBUG_PINS */

View file

@ -168,6 +168,15 @@
#define USER_TICKER_NODES 0
#endif
#if defined(CONFIG_BT_CTLR_COEX_TICKER)
#define COEX_TICKER_NODES 1
/* No. of tickers reserved for coex drivers */
#else
#define COEX_TICKER_NODES 0
#endif
#if defined(CONFIG_SOC_FLASH_NRF_RADIO_SYNC_TICKER)
#define FLASH_TICKER_NODES 2 /* No. of tickers reserved for flash
* driver
@ -201,7 +210,8 @@
BT_CONN_TICKER_NODES + \
BT_CIG_TICKER_NODES + \
USER_TICKER_NODES + \
FLASH_TICKER_NODES)
FLASH_TICKER_NODES + \
COEX_TICKER_NODES)
/* When both central and peripheral are supported, one each Rx node will be
* needed by connectable advertising and the initiator to generate connection
@ -1772,7 +1782,14 @@ void ll_timeslice_ticker_id_get(uint8_t * const instance_index,
uint8_t * const ticker_id)
{
*instance_index = TICKER_INSTANCE_ID_CTLR;
*ticker_id = (TICKER_NODES - FLASH_TICKER_NODES);
*ticker_id = (TICKER_NODES - FLASH_TICKER_NODES - COEX_TICKER_NODES);
}
void ll_coex_ticker_id_get(uint8_t * const instance_index,
uint8_t * const ticker_id)
{
*instance_index = TICKER_INSTANCE_ID_CTLR;
*ticker_id = (TICKER_NODES - COEX_TICKER_NODES);
}
void ll_radio_state_abort(void)