| /* |
| * Copyright (c) 2020 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdint.h> |
| #include <soc.h> |
| #include <zephyr/bluetooth/hci.h> |
| |
| #include "util/util.h" |
| #include "util/memq.h" |
| #include "util/mem.h" |
| #include "util/dbuf.h" |
| |
| #include "hal/cpu.h" |
| #include "hal/ccm.h" |
| #include "hal/radio_df.h" |
| |
| #include "pdu.h" |
| |
| #include "lll.h" |
| #include "lll_adv_types.h" |
| #include "lll_adv.h" |
| #include "lll_adv_pdu.h" |
| #include "lll_df.h" |
| #include "lll_df_types.h" |
| #include "lll_df_internal.h" |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_CTLR_DF_DEBUG_ENABLE) |
| #define LOG_MODULE_NAME bt_ctlr_lll_df |
| #include "common/log.h" |
| #include <soc.h> |
| #include "hal/debug.h" |
| |
| /* Minimum number of antenna switch patterns required by Direction Finding Extension to be |
| * configured in SWTICHPATTER register. The value is set to 2, even though the radio peripheral |
| * specification requires 3. |
| * Radio always configures three antenna patterns. First pattern is set implicitly in |
| * radio_df_ant_switch_pattern_set. It is provided by DTS radio.dfe_pdu_antenna property. |
| * There is a need for two more patterns to be provided by an application. |
| * They are aimed for: reference period and switch-sample period. |
| */ |
| #define DF_MIN_ANT_NUM_REQUIRED 2 |
| |
| static int init_reset(void); |
| |
| /* @brief Function performs Direction Finding initialization |
| * |
| * @return Zero in case of success, other value in case of failure. |
| */ |
| int lll_df_init(void) |
| { |
| #if defined(CONFIG_BT_CTLR_DF_INIT_ANT_SEL_GPIOS) |
| radio_df_ant_switching_gpios_cfg(); |
| #endif /* CONFIG_BT_CTLR_DF_INIT_ANT_SEL_GPIOS */ |
| return init_reset(); |
| } |
| |
| /* @brief Function performs Direction Finding reset |
| * |
| * @return Zero in case of success, other value in case of failure. |
| */ |
| int lll_df_reset(void) |
| { |
| return init_reset(); |
| } |
| |
| /* @brief Function provides number of available antennas. |
| * |
| * The number of antenna is hardware defined and it is provided via devicetree. |
| * |
| * @return Number of available antennas. |
| */ |
| uint8_t lll_df_ant_num_get(void) |
| { |
| return radio_df_ant_num_get(); |
| } |
| |
| #if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) |
| /* @brief Function enables transmission of Constant Tone Extension. |
| * |
| * @param lll_sync Pointer to LLL sync. object associated with |
| * periodic advertising event. |
| * @param pdu Pointer to PDU that will be transmitted. |
| * @param[out] cte_len_us Pointer to store actual CTE length in [us] |
| */ |
| void lll_df_cte_tx_enable(struct lll_adv_sync *lll_sync, const struct pdu_adv *pdu, |
| uint32_t *cte_len_us) |
| { |
| if (pdu->adv_ext_ind.ext_hdr_len) { |
| const struct pdu_adv_ext_hdr *ext_hdr; |
| |
| ext_hdr = &pdu->adv_ext_ind.ext_hdr; |
| |
| /* Check if particular extended PDU has cte_info. It is possible that |
| * not all PDUs in a periodic advertising chain have CTE attached. |
| * Nevertheless whole periodic advertising chain has the same CTE |
| * configuration. Checking for existence of CTE configuration in lll_sync |
| * is not enough. |
| */ |
| if (ext_hdr->cte_info) { |
| const struct lll_df_adv_cfg *df_cfg; |
| |
| df_cfg = lll_adv_sync_extra_data_curr_get(lll_sync); |
| LL_ASSERT(df_cfg); |
| |
| lll_df_cte_tx_configure(df_cfg->cte_type, df_cfg->cte_length, |
| df_cfg->ant_sw_len, df_cfg->ant_ids); |
| |
| lll_sync->cte_started = 1U; |
| *cte_len_us = CTE_LEN_US(df_cfg->cte_length); |
| } else { |
| if (lll_sync->cte_started) { |
| lll_df_conf_cte_tx_disable(); |
| lll_sync->cte_started = 0U; |
| } |
| *cte_len_us = 0U; |
| } |
| } else { |
| if (lll_sync->cte_started) { |
| lll_df_conf_cte_tx_disable(); |
| lll_sync->cte_started = 0U; |
| } |
| *cte_len_us = 0U; |
| } |
| } |
| |
| void lll_df_conf_cte_tx_disable(void) |
| { |
| radio_df_reset(); |
| } |
| #endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ |
| |
| #if defined(CONFIG_BT_CTLR_DF_SCAN_CTE_RX) |
| struct lll_df_sync_cfg *lll_df_sync_cfg_alloc(struct lll_df_sync *df_cfg, |
| uint8_t *idx) |
| { |
| uint8_t first, last; |
| |
| /* TODO: Make this unique mechanism to update last element in double |
| * buffer a re-usable utility function. |
| */ |
| first = df_cfg->first; |
| last = df_cfg->last; |
| if (first == last) { |
| /* Return the index of next free PDU in the double buffer */ |
| last++; |
| if (last == DOUBLE_BUFFER_SIZE) { |
| last = 0U; |
| } |
| } else { |
| uint8_t first_latest; |
| |
| /* LLL has not consumed the first PDU. Revert back the `last` so |
| * that LLL still consumes the first PDU while the caller of |
| * this function updates/modifies the latest PDU. |
| * |
| * Under race condition: |
| * 1. LLL runs before `pdu->last` is reverted, then `pdu->first` |
| * has changed, hence restore `pdu->last` and return index of |
| * next free PDU in the double buffer. |
| * 2. LLL runs after `pdu->last` is reverted, then `pdu->first` |
| * will not change, return the saved `last` as the index of |
| * the next free PDU in the double buffer. |
| */ |
| df_cfg->last = first; |
| cpu_dmb(); |
| first_latest = df_cfg->first; |
| if (first_latest != first) { |
| df_cfg->last = last; |
| last++; |
| if (last == DOUBLE_BUFFER_SIZE) { |
| last = 0U; |
| } |
| } |
| } |
| |
| *idx = last; |
| |
| return &df_cfg->cfg[last]; |
| } |
| |
| struct lll_df_sync_cfg *lll_df_sync_cfg_latest_get(struct lll_df_sync *df_cfg, |
| uint8_t *is_modified) |
| { |
| uint8_t first; |
| |
| first = df_cfg->first; |
| if (first != df_cfg->last) { |
| uint8_t cfg_idx; |
| |
| cfg_idx = first; |
| |
| first += 1U; |
| if (first == DOUBLE_BUFFER_SIZE) { |
| first = 0U; |
| } |
| df_cfg->first = first; |
| |
| if (is_modified) { |
| *is_modified = 1U; |
| } |
| |
| df_cfg->cfg[cfg_idx].is_enabled = 0U; /* mark as disabled - not used anymore */ |
| } |
| |
| return &df_cfg->cfg[first]; |
| } |
| #endif /* CONFIG_BT_CTLR_DF_SCAN_CTE_RX */ |
| |
| #if defined(CONFIG_BT_CTLR_DF_SCAN_CTE_RX) || defined(CONFIG_BT_CTLR_DF_CONN_CTE_RX) |
| /* @brief Function initializes reception of Constant Tone Extension. |
| * |
| * @param slot_duration Switching and sampling slots duration (1us or 2us). |
| * @param ant_num Number of antennas in switch pattern. |
| * @param ant_ids Antenna identifiers that create switch pattern. |
| * @param chan_idx Channel used to receive PDU with CTE |
| * @param cte_info_in_s1 Inform if CTEInfo is in S1 byte for conn. PDU or in extended advertising |
| * header of per. adv. PDU. |
| * @param phy Current PHY |
| * |
| * In case of AoA mode ant_num and ant_ids parameters are not used. |
| */ |
| void lll_df_conf_cte_rx_enable(uint8_t slot_duration, uint8_t ant_num, const uint8_t *ant_ids, |
| uint8_t chan_idx, bool cte_info_in_s1, uint8_t phy) |
| { |
| struct node_rx_iq_report *node_rx; |
| |
| /* ToDo change to appropriate HCI constant */ |
| #if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_1US) |
| if (slot_duration == 0x1) { |
| radio_df_cte_rx_2us_switching(cte_info_in_s1, phy); |
| } else |
| #endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_1US */ |
| { |
| radio_df_cte_rx_4us_switching(cte_info_in_s1, phy); |
| } |
| |
| #if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX) |
| radio_df_ant_switching_pin_sel_cfg(); |
| radio_df_ant_switch_pattern_clear(); |
| radio_df_ant_switch_pattern_set(ant_ids, ant_num); |
| #endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_RX */ |
| |
| node_rx = ull_df_iq_report_alloc_peek(1); |
| LL_ASSERT(node_rx); |
| |
| radio_df_iq_data_packet_set(node_rx->pdu, IQ_SAMPLE_TOTAL_CNT); |
| node_rx->chan_idx = chan_idx; |
| } |
| #endif /* CONFIG_BT_CTLR_DF_SCAN_CTE_RX || CONFIG_BT_CTLR_DF_CONN_CTE_RX */ |
| |
| #if defined(CONFIG_BT_CTLR_DF_CONN_CTE_RX) |
| /** |
| * @brief Function initializes parsing of received PDU for CTEInfo. |
| * |
| * Parsing a PDU for CTEInfo is required to successfully receive a PDU that may include CTE. |
| * It makes possible to correctly interpret PDU content by Radio peripheral. |
| */ |
| void lll_df_conf_cte_info_parsing_enable(void) |
| { |
| /* Use of mandatory 2 us switching and sampling slots for CTEInfo parsing. |
| * The configuration here does not matter for actual IQ sampling. |
| * The collected data will not be reported to host. |
| * Also sampling offset does not matter so, provided PHY is legacy to setup |
| * the offset to default zero value. |
| */ |
| radio_df_cte_rx_4us_switching(true, PHY_LEGACY); |
| |
| #if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX) |
| /* Use PDU_ANTENNA so no actual antenna change will be done. */ |
| static uint8_t ant_ids[DF_MIN_ANT_NUM_REQUIRED] = { PDU_ANTENNA, PDU_ANTENNA }; |
| |
| radio_df_ant_switching_pin_sel_cfg(); |
| radio_df_ant_switch_pattern_clear(); |
| radio_df_ant_switch_pattern_set(ant_ids, DF_MIN_ANT_NUM_REQUIRED); |
| #endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_RX */ |
| |
| /* Do not set storage for IQ samples, it is irrelevant for parsing of a PDU for CTEInfo. */ |
| radio_df_iq_data_packet_set(NULL, 0); |
| } |
| #endif /* CONFIG_BT_CTLR_DF_CONN_CTE_RX */ |
| |
| /* @brief Function performs common steps for initialization and reset |
| * of Direction Finding LLL module. |
| * |
| * @return Zero in case of success, other value in case of failure. |
| */ |
| static int init_reset(void) |
| { |
| return 0; |
| } |
| |
| #if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) || defined(CONFIG_BT_CTLR_DF_CONN_CTE_TX) |
| /** |
| * @brief Function configures transmission of Constant Tone Extension. |
| * |
| * @param cte_type Type of the CTE |
| * @param cte_length Length of CTE in units of 8 us |
| * @param num_ant_ids Length of @p ant_ids |
| * @param ant_ids Pointer to antenna identifiers array |
| * |
| * In case of AoA mode ant_sw_len and ant_ids members are not used. |
| */ |
| void lll_df_cte_tx_configure(uint8_t cte_type, uint8_t cte_length, uint8_t num_ant_ids, |
| const uint8_t *ant_ids) |
| { |
| if (cte_type == BT_HCI_LE_AOA_CTE) { |
| radio_df_mode_set_aoa(); |
| radio_df_cte_tx_aoa_set(cte_length); |
| } |
| #if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_TX) |
| else { |
| radio_df_mode_set_aod(); |
| |
| if (cte_type == BT_HCI_LE_AOD_CTE_1US) { |
| radio_df_cte_tx_aod_2us_set(cte_length); |
| } else { |
| radio_df_cte_tx_aod_4us_set(cte_length); |
| } |
| |
| radio_df_ant_switching_pin_sel_cfg(); |
| radio_df_ant_switch_pattern_clear(); |
| radio_df_ant_switch_pattern_set(ant_ids, num_ant_ids); |
| } |
| #endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_TX */ |
| } |
| #endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX || CONFIG_BT_CTLR_DF_CONN_CTE_TX */ |