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:
parent
947ef4c96b
commit
14138d4a34
31
dts/bindings/net/wireless/gpio-radio-coex.yaml
Normal file
31
dts/bindings/net/wireless/gpio-radio-coex.yaml
Normal 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
|
16
samples/bluetooth/beacon/boards/nrf52840dk_nrf52840.overlay
Normal file
16
samples/bluetooth/beacon/boards/nrf52840dk_nrf52840.overlay
Normal 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>;
|
||||
};
|
7
samples/bluetooth/beacon/prj-coex.conf
Normal file
7
samples/bluetooth/beacon/prj-coex.conf
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
5
subsys/bluetooth/controller/coex/CMakeLists.txt
Normal file
5
subsys/bluetooth/controller/coex/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library_sources_ifdef(
|
||||
CONFIG_BT_CTLR_COEX_TICKER coex_ticker.c
|
||||
)
|
19
subsys/bluetooth/controller/coex/Kconfig
Normal file
19
subsys/bluetooth/controller/coex/Kconfig
Normal 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
|
212
subsys/bluetooth/controller/coex/coex_ticker.c
Normal file
212
subsys/bluetooth/controller/coex/coex_ticker.c
Normal 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
|
22
subsys/bluetooth/controller/coex/readme.rst
Normal file
22
subsys/bluetooth/controller/coex/readme.rst
Normal 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.
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue