blob: 7d4fc2f83db06a9088541f5852fff10efd9d8aaa [file] [log] [blame]
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <errno.h>
#include <soc.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/util_macro.h>
#include <hal/nrf_radio.h>
#include <hal/nrf_gpio.h>
#include <hal/ccm.h>
#include "ll_sw/pdu.h"
#include "radio_nrf5.h"
#include "radio.h"
#include "radio_df.h"
#include "radio_internal.h"
/* Devicetree node identifier for the radio node. */
#define RADIO_NODE DT_NODELABEL(radio)
/* Value to set for unconnected antenna GPIO pins. */
#define DFE_PSEL_NOT_SET 0xFF
/* Number of PSEL_DFEGPIO[n] registers in the radio peripheral. */
#define MAX_DFE_GPIO 8
/* Run a macro 'fn' on each available DFE GPIO index, from 0 to
* MAX_DFE_GPIO-1, with the given parenthesized separator.
*/
#define FOR_EACH_DFE_GPIO(fn, sep) \
FOR_EACH(fn, sep, 0, 1, 2, 3, 4, 5, 6, 7)
/* Index of antenna id in antenna switching pattern used for GUARD and REFERENCE period */
#define GUARD_REF_ANTENNA_PATTERN_IDX 0U
/* Direction Finding antenna matrix configuration */
struct df_ant_cfg {
uint8_t ant_num;
/* Selection of GPIOs to be used to switch antennas by Radio */
uint8_t dfe_gpio[MAX_DFE_GPIO];
};
#define DFE_GPIO_PSEL(idx) \
NRF_DT_GPIOS_TO_PSEL_OR(RADIO_NODE, dfegpio##idx##_gpios, \
DFE_PSEL_NOT_SET)
#define DFE_GPIO_PIN_DISCONNECT (RADIO_PSEL_DFEGPIO_CONNECT_Disconnected << \
RADIO_PSEL_DFEGPIO_CONNECT_Pos)
#define HAS_DFE_GPIO(idx) DT_NODE_HAS_PROP(RADIO_NODE, dfegpio##idx##_gpios)
/* The number of dfegpio[n]-gpios properties which are set. */
#define DFE_GPIO_NUM (FOR_EACH_DFE_GPIO(HAS_DFE_GPIO, (+)))
/* The minimum number of antennas required to enable antenna switching. */
#define MIN_ANTENNA_NUM 2
/* The maximum number of antennas supported by the number of
* dfegpio[n]-gpios properties which are set.
*/
#if (DFE_GPIO_NUM > 0)
#define MAX_ANTENNA_NUM BIT(DFE_GPIO_NUM)
#else
#define MAX_ANTENNA_NUM 0
#endif
uint8_t radio_df_pdu_antenna_switch_pattern_get(void)
{
return PDU_ANTENNA;
}
#if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_TX) || \
defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX)
/*
* Check that we have an antenna switch pattern for the DFE idle
* state. (In DFE idle state, the radio peripheral transmits or
* receives PDUs.)
*/
#define HAS_PDU_ANTENNA DT_NODE_HAS_PROP(RADIO_NODE, dfe_pdu_antenna)
BUILD_ASSERT(HAS_PDU_ANTENNA,
"Missing antenna pattern used to select antenna for PDU Tx "
"during the DFE Idle state. "
"Set the dfe-pdu-antenna devicetree property.");
void radio_df_ant_switch_pattern_set(const uint8_t *patterns, uint8_t len)
{
/* SWITCHPATTERN is like a moving pointer to an underlying buffer.
* Each write stores a value and moves the pointer to new free position.
* When read it returns number of stored elements since last write to
* CLEARPATTERN. There is no need to use a subscript operator.
*
* Some storage entries in the buffer has special purpose for DFE
* extension in radio:
* - SWITCHPATTERN[0] for idle period (PDU Tx/Rx),
* - SWITCHPATTERN[1] for guard and reference period,
* - SWITCHPATTERN[2] and following for switch-sampling slots.
* Due to that in SWITCHPATTER[0] there is stored a pattern provided by
* DTS property dfe_pdu_antenna. This limits number of supported antenna
* switch patterns by one.
*/
NRF_RADIO->SWITCHPATTERN = PDU_ANTENNA;
for (uint8_t idx = 0; idx < len; ++idx) {
NRF_RADIO->SWITCHPATTERN = patterns[idx];
}
/* Store antenna id used for GUARD and REFERENCE period at the end of SWITCHPATTERN buffer.
* It is required to apply reference antenna id when user provided switchpattern is
* exhausted.
* Maximum length of the switch pattern provided to this function is at maximum lower by one
* than capacity of SWITCHPATTERN buffer. Hence there is always space for reference antenna
* id after end of switch pattern.
*/
NRF_RADIO->SWITCHPATTERN = patterns[GUARD_REF_ANTENNA_PATTERN_IDX];
}
/*
* Check that the number of antennas has been set, and that enough
* pins are configured to represent each pattern for the given number
* of antennas.
*/
#define HAS_ANTENNA_NUM DT_NODE_HAS_PROP(RADIO_NODE, dfe_antenna_num)
BUILD_ASSERT(HAS_ANTENNA_NUM,
"You must set the dfe-antenna-num property in the radio node "
"to enable antenna switching.");
#define ANTENNA_NUM DT_PROP_OR(RADIO_NODE, dfe_antenna_num, 0)
BUILD_ASSERT(!HAS_ANTENNA_NUM || (ANTENNA_NUM <= MAX_ANTENNA_NUM),
"Insufficient number of GPIO pins configured. "
"Set more dfegpio[n]-gpios properties.");
BUILD_ASSERT(!HAS_ANTENNA_NUM || (ANTENNA_NUM >= MIN_ANTENNA_NUM),
"Insufficient number of antennas provided. "
"Increase the dfe-antenna-num property.");
/*
* Check that each dfegpio[n]-gpios property has a zero flags cell.
*/
#define ASSERT_DFE_GPIO_FLAGS_ARE_ZERO(idx) \
BUILD_ASSERT(DT_GPIO_FLAGS(RADIO_NODE, dfegpio##idx##_gpios) == 0, \
"The flags cell in each dfegpio[n]-gpios " \
"property must be zero.")
FOR_EACH_DFE_GPIO(ASSERT_DFE_GPIO_FLAGS_ARE_ZERO, (;));
/* Stores the dfegpio[n]-gpios property values.
*/
const static struct df_ant_cfg ant_cfg = {
.ant_num = ANTENNA_NUM,
.dfe_gpio = { FOR_EACH_DFE_GPIO(DFE_GPIO_PSEL, (,)) }
};
/* @brief Function configures Radio with information about GPIO pins that may be
* used to drive antenna switching during CTE Tx/RX.
*
* Sets up DF related PSEL.DFEGPIO registers to give possibility to Radio
* to drive antennas switches.
*
*/
void radio_df_ant_switching_pin_sel_cfg(void)
{
uint8_t pin_sel;
for (uint8_t idx = 0; idx < MAX_DFE_GPIO; ++idx) {
pin_sel = ant_cfg.dfe_gpio[idx];
if (pin_sel != DFE_PSEL_NOT_SET) {
nrf_radio_dfe_pattern_pin_set(NRF_RADIO,
pin_sel,
idx);
} else {
nrf_radio_dfe_pattern_pin_set(NRF_RADIO,
DFE_GPIO_PIN_DISCONNECT,
idx);
}
}
}
#if defined(CONFIG_BT_CTLR_DF_INIT_ANT_SEL_GPIOS)
/* @brief Function configures GPIO pins that will be used by Direction Finding
* Extension for antenna switching.
*
* Function configures antenna selection GPIO pins in GPIO peripheral.
* Also sets pin outputs to match state in SWITCHPATTERN[0] that is used
* to enable antenna for PDU Rx/Tx. That has to prevent glitches when
* DFE is powered off after end of transmission.
*
* @return Zero in case of success, other value in case of failure.
*/
void radio_df_ant_switching_gpios_cfg(void)
{
uint8_t pin_sel;
for (uint8_t idx = 0; idx < MAX_DFE_GPIO; ++idx) {
pin_sel = ant_cfg.dfe_gpio[idx];
if (pin_sel != DFE_PSEL_NOT_SET) {
nrf_gpio_cfg_output(pin_sel);
if (BIT(idx) & PDU_ANTENNA) {
nrf_gpio_pin_set(pin_sel);
} else {
nrf_gpio_pin_clear(pin_sel);
}
}
}
}
#endif /* CONFIG_BT_CTLR_DF_INIT_ANT_SEL_GPIOS */
#endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_TX || CONFIG_BT_CTLR_DF_ANT_SWITCH_RX */
/* @brief Function provides number of available antennas for Direction Finding.
*
* The number of antennas is hardware defined. It is provided via devicetree.
*
* If antenna switching is not enabled then there must be a single antenna
* responsible for PDU reception and transmission.
*
* @return Number of available antennas.
*/
uint8_t radio_df_ant_num_get(void)
{
#if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_TX) || \
defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX)
return ant_cfg.ant_num;
#else
return 1U;
#endif
}
static inline void radio_df_mode_set(uint8_t mode)
{
NRF_RADIO->DFEMODE &= ~RADIO_DFEMODE_DFEOPMODE_Msk;
NRF_RADIO->DFEMODE |= ((mode << RADIO_DFEMODE_DFEOPMODE_Pos)
& RADIO_DFEMODE_DFEOPMODE_Msk);
}
void radio_df_mode_set_aoa(void)
{
radio_df_mode_set(NRF_RADIO_DFE_OP_MODE_AOA);
}
void radio_df_mode_set_aod(void)
{
radio_df_mode_set(NRF_RADIO_DFE_OP_MODE_AOD);
}
static inline void radio_df_ctrl_set(uint8_t cte_len,
uint8_t switch_spacing,
uint8_t sample_spacing,
uint8_t phy)
{
uint16_t sample_offset;
uint32_t conf;
/* Complete setup is done on purpose, to be sure that there isn't left
* any unexpected state in the register.
*/
conf = ((((uint32_t)cte_len << RADIO_DFECTRL1_NUMBEROF8US_Pos) &
RADIO_DFECTRL1_NUMBEROF8US_Msk) |
((uint32_t)RADIO_DFECTRL1_DFEINEXTENSION_CRC <<
RADIO_DFECTRL1_DFEINEXTENSION_Pos) |
((uint32_t)switch_spacing << RADIO_DFECTRL1_TSWITCHSPACING_Pos) |
((uint32_t)NRF_RADIO_DFECTRL_SAMPLE_SPACING_1US <<
RADIO_DFECTRL1_TSAMPLESPACINGREF_Pos) |
((uint32_t)NRF_RADIO_DFECTRL_SAMPLE_TYPE_IQ <<
RADIO_DFECTRL1_SAMPLETYPE_Pos) |
((uint32_t)sample_spacing << RADIO_DFECTRL1_TSAMPLESPACING_Pos) |
(((uint32_t)0 << RADIO_DFECTRL1_AGCBACKOFFGAIN_Pos) &
RADIO_DFECTRL1_AGCBACKOFFGAIN_Msk));
NRF_RADIO->DFECTRL1 = conf;
switch (phy) {
case PHY_1M:
if (switch_spacing == RADIO_DFECTRL1_TSWITCHSPACING_2us) {
sample_offset = CONFIG_BT_CTLR_DF_SAMPLE_OFFSET_PHY_1M_SAMPLING_1US;
} else if (switch_spacing == RADIO_DFECTRL1_TSWITCHSPACING_4us) {
sample_offset = CONFIG_BT_CTLR_DF_SAMPLE_OFFSET_PHY_1M_SAMPLING_2US;
} else {
sample_offset = 0;
}
break;
case PHY_2M:
if (switch_spacing == RADIO_DFECTRL1_TSWITCHSPACING_2us) {
sample_offset = CONFIG_BT_CTLR_DF_SAMPLE_OFFSET_PHY_2M_SAMPLING_1US;
} else if (switch_spacing == RADIO_DFECTRL1_TSWITCHSPACING_4us) {
sample_offset = CONFIG_BT_CTLR_DF_SAMPLE_OFFSET_PHY_2M_SAMPLING_2US;
} else {
sample_offset = 0;
}
break;
case PHY_LEGACY:
default:
/* If phy is set to legacy, the function is called in TX context and actual value
* does not matter, hence it is set to default zero.
*/
sample_offset = 0;
}
conf = ((((uint32_t)sample_offset << RADIO_DFECTRL2_TSAMPLEOFFSET_Pos) &
RADIO_DFECTRL2_TSAMPLEOFFSET_Msk) |
(((uint32_t)CONFIG_BT_CTLR_DF_SWITCH_OFFSET << RADIO_DFECTRL2_TSWITCHOFFSET_Pos) &
RADIO_DFECTRL2_TSWITCHOFFSET_Msk));
NRF_RADIO->DFECTRL2 = conf;
}
void radio_df_cte_tx_aod_2us_set(uint8_t cte_len)
{
/* Sample spacing does not matter for AoD Tx. It is set to value
* that is in DFECTRL1 register after reset. That is done instead of
* adding conditions on the value and masking of the field before
* storing configuration in the register. Also values in DFECTRL2,
* that depend on PHY, are irrelevant for AoD Tx, hence use of
* PHY_LEGACY here.
*/
radio_df_ctrl_set(cte_len, RADIO_DFECTRL1_TSWITCHSPACING_2us,
RADIO_DFECTRL1_TSAMPLESPACING_2us, PHY_LEGACY);
}
void radio_df_cte_tx_aod_4us_set(uint8_t cte_len)
{
/* Sample spacing does not matter for AoD Tx. It is set to value
* that is in DFECTRL1 register after reset. That is done instead of
* adding conditions on the value and masking of the field before
* storing configuration in the register. Also values in DFECTRL2,
* that depend on PHY, are irrelevant for AoD Tx, hence use of
* PHY_LEGACY here.
*/
radio_df_ctrl_set(cte_len, RADIO_DFECTRL1_TSWITCHSPACING_4us,
RADIO_DFECTRL1_TSAMPLESPACING_2us, PHY_LEGACY);
}
void radio_df_cte_tx_aoa_set(uint8_t cte_len)
{
/* Switch and sample spacing does not matter for AoA Tx. It is set to
* value that is in DFECTRL1 register after reset. That is done instead
* of adding conditions on the value and masking of the field before
* storing configuration in the register. Also values in DFECTRL2,
* that depend on PHY, are irrelevant for AoA Tx, hence use of
* PHY_LEGACY here.
*/
radio_df_ctrl_set(cte_len, RADIO_DFECTRL1_TSWITCHSPACING_4us,
RADIO_DFECTRL1_TSAMPLESPACING_2us, PHY_LEGACY);
}
void radio_df_cte_rx_2us_switching(bool cte_info_in_s1, uint8_t phy)
{
/* BT spec requires single sample for a single switching slot, so
* spacing for slot and samples is the same.
* CTE duration is used only when CTEINLINE config is disabled.
*/
radio_df_ctrl_set(0, RADIO_DFECTRL1_TSWITCHSPACING_2us,
RADIO_DFECTRL1_TSAMPLESPACING_2us, phy);
radio_df_cte_inline_set_enabled(cte_info_in_s1);
}
void radio_df_cte_rx_4us_switching(bool cte_info_in_s1, uint8_t phy)
{
/* BT spec requires single sample for a single switching slot, so
* spacing for slot and samples is the same.
* CTE duration is used only when CTEINLINE config is disabled.
*/
radio_df_ctrl_set(0, RADIO_DFECTRL1_TSWITCHSPACING_4us,
RADIO_DFECTRL1_TSAMPLESPACING_4us, phy);
radio_df_cte_inline_set_enabled(cte_info_in_s1);
}
void radio_df_ant_switch_pattern_clear(void)
{
NRF_RADIO->CLEARPATTERN = RADIO_CLEARPATTERN_CLEARPATTERN_Clear;
}
void radio_df_reset(void)
{
/* Initialize to NRF_RADIO reset values
* Note: Only registers that turn off the DF feature and those
* registers whose bits are partially modified across functions
* are assigned back the power-on reset values.
*/
NRF_RADIO->DFEMODE = HAL_RADIO_RESET_VALUE_DFEMODE;
NRF_RADIO->CTEINLINECONF = HAL_RADIO_RESET_VALUE_CTEINLINECONF;
radio_df_ant_switch_pattern_clear();
}
void radio_switch_complete_and_phy_end_b2b_tx(uint8_t phy_curr, uint8_t flags_curr,
uint8_t phy_next, uint8_t flags_next)
{
#if defined(CONFIG_BT_CTLR_TIFS_HW)
NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk |
RADIO_SHORTS_DISABLED_TXEN_Msk;
#else /* !CONFIG_BT_CTLR_TIFS_HW */
NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk | NRF_RADIO_SHORTS_PDU_END_DISABLE;
sw_switch(SW_SWITCH_TX, SW_SWITCH_TX, phy_curr, flags_curr, phy_next, flags_next,
END_EVT_DELAY_DISABLED);
#endif /* !CONFIG_BT_CTLR_TIFS_HW */
}
void radio_df_iq_data_packet_set(uint8_t *buffer, size_t len)
{
nrf_radio_dfe_buffer_set(NRF_RADIO, (uint32_t *)buffer, len);
}
uint32_t radio_df_iq_samples_amount_get(void)
{
return nrf_radio_dfe_amount_get(NRF_RADIO);
}
uint8_t radio_df_cte_status_get(void)
{
return NRF_RADIO->CTESTATUS;
}
bool radio_df_cte_ready(void)
{
return (NRF_RADIO->EVENTS_CTEPRESENT != 0);
}