diff --git a/dts/bindings/net/wireless/gpio-radio-coex.yaml b/dts/bindings/net/wireless/gpio-radio-coex.yaml new file mode 100644 index 0000000000..e21cc53d43 --- /dev/null +++ b/dts/bindings/net/wireless/gpio-radio-coex.yaml @@ -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 diff --git a/samples/bluetooth/beacon/boards/nrf52840dk_nrf52840.overlay b/samples/bluetooth/beacon/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000000..d7a5571e6c --- /dev/null +++ b/samples/bluetooth/beacon/boards/nrf52840dk_nrf52840.overlay @@ -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>; +}; diff --git a/samples/bluetooth/beacon/prj-coex.conf b/samples/bluetooth/beacon/prj-coex.conf new file mode 100644 index 0000000000..6dafb5b261 --- /dev/null +++ b/samples/bluetooth/beacon/prj-coex.conf @@ -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 diff --git a/samples/bluetooth/beacon/sample.yaml b/samples/bluetooth/beacon/sample.yaml index fca28387d2..fce3d96b19 100644 --- a/samples/bluetooth/beacon/sample.yaml +++ b/samples/bluetooth/beacon/sample.yaml @@ -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 diff --git a/subsys/bluetooth/controller/CMakeLists.txt b/subsys/bluetooth/controller/CMakeLists.txt index d2e083a008..9e3a382f2e 100644 --- a/subsys/bluetooth/controller/CMakeLists.txt +++ b/subsys/bluetooth/controller/CMakeLists.txt @@ -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) diff --git a/subsys/bluetooth/controller/Kconfig.ll_sw_split b/subsys/bluetooth/controller/Kconfig.ll_sw_split index d5a2c99a04..acd2fd848c 100644 --- a/subsys/bluetooth/controller/Kconfig.ll_sw_split +++ b/subsys/bluetooth/controller/Kconfig.ll_sw_split @@ -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 diff --git a/subsys/bluetooth/controller/coex/CMakeLists.txt b/subsys/bluetooth/controller/coex/CMakeLists.txt new file mode 100644 index 0000000000..eaa89c2b4f --- /dev/null +++ b/subsys/bluetooth/controller/coex/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_sources_ifdef( + CONFIG_BT_CTLR_COEX_TICKER coex_ticker.c +) diff --git a/subsys/bluetooth/controller/coex/Kconfig b/subsys/bluetooth/controller/coex/Kconfig new file mode 100644 index 0000000000..31754fbf52 --- /dev/null +++ b/subsys/bluetooth/controller/coex/Kconfig @@ -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 diff --git a/subsys/bluetooth/controller/coex/coex_ticker.c b/subsys/bluetooth/controller/coex/coex_ticker.c new file mode 100644 index 0000000000..fa5c22f383 --- /dev/null +++ b/subsys/bluetooth/controller/coex/coex_ticker.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2022 Dronetag + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/subsys/bluetooth/controller/coex/readme.rst b/subsys/bluetooth/controller/coex/readme.rst new file mode 100644 index 0000000000..066b3e61af --- /dev/null +++ b/subsys/bluetooth/controller/coex/readme.rst @@ -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 `_ + +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. diff --git a/subsys/bluetooth/controller/include/ll.h b/subsys/bluetooth/controller/include/ll.h index 1e446ced2c..9facc5452b 100644 --- a/subsys/bluetooth/controller/include/ll.h +++ b/subsys/bluetooth/controller/include/ll.h @@ -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); diff --git a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/debug.h b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/debug.h index 4fa2e9d804..34cef5f9f6 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/debug.h +++ b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/debug.h @@ -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 */ diff --git a/subsys/bluetooth/controller/ll_sw/ull.c b/subsys/bluetooth/controller/ll_sw/ull.c index 9d6c962c94..ef7f2533c5 100644 --- a/subsys/bluetooth/controller/ll_sw/ull.c +++ b/subsys/bluetooth/controller/ll_sw/ull.c @@ -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)