text: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *  http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  
  #include <stdint.h>
  #include <string.h>
  #include <assert.h>
  #include <syscfg/syscfg.h>
  #include <os/os.h>
  #include <bsp/bsp.h>
  #include <nimble/ble.h>
  #include <nimble/nimble_opt.h>
  #include <nimble/nimble_npl.h>
  #include <controller/ble_phy.h>
  #include "controller/ble_ll_plna.h"
  
  #include <ble/xcvr.h>
  #include <controller/ble_phy_trace.h>
  #include <controller/ble_ll.h>
  #include <mcu/nrf5340_net_clock.h>
  #include <mcu/cmsis_nvic.h>
  
  #define DPPI_CH_PUB(_ch)        (((DPPI_CH_ ## _ch) & 0xff) | (1 << 31))
  #define DPPI_CH_SUB(_ch)        (((DPPI_CH_ ## _ch) & 0xff) | (1 << 31))
  #define DPPI_CH_UNSUB(_ch)      (((DPPI_CH_ ## _ch) & 0xff) | (0 << 31))
  #define DPPI_CH_MASK(_ch)       (1 << (DPPI_CH_ ## _ch))
  
  /* Channels 0..5 are always used.
   * Channels 6 and 7 are used for PA/LNA (optionally).
   * Channels 6..8 are used for GPIO debugging (optionally).
   */
  
  #define DPPI_CH_TIMER0_EVENTS_COMPARE_0         0
  #define DPPI_CH_TIMER0_EVENTS_COMPARE_3         1
  #define DPPI_CH_RADIO_EVENTS_END                2
  #define DPPI_CH_RADIO_EVENTS_BCMATCH            3
  #define DPPI_CH_RADIO_EVENTS_ADDRESS            4
  #define DPPI_CH_RTC0_EVENTS_COMPARE_0           5
  #define DPPI_CH_RADIO_EVENTS_READY              6
  #define DPPI_CH_RADIO_EVENTS_DISABLED           7
  #define DPPI_CH_RADIO_EVENTS_RXREADY            8
  
  #define DPPI_CH_ENABLE_ALL (DPPIC_CHEN_CH0_Msk | DPPIC_CHEN_CH1_Msk | DPPIC_CHEN_CH2_Msk | \
                              DPPIC_CHEN_CH3_Msk |  DPPIC_CHEN_CH4_Msk | DPPIC_CHEN_CH5_Msk)
  
  #define DPPI_CH_MASK_PLNA   (DPPI_CH_MASK(RADIO_EVENTS_READY) | \
                               DPPI_CH_MASK(RADIO_EVENTS_DISABLED))
  
  extern uint8_t g_nrf_num_irks;
  extern uint32_t g_nrf_irk_list[];
  
  /* To disable all radio interrupts */
  #define NRF_RADIO_IRQ_MASK_ALL (0x34FF)
  
  /*
   * We configure the nrf with a 1 byte S0 field, 8 bit length field, and
   * zero bit S1 field. The preamble is 8 bits long.
   */
  #define NRF_LFLEN_BITS      (8)
  #define NRF_S0LEN           (1)
  #define NRF_S1LEN_BITS      (0)
  #define NRF_CILEN_BITS      (2)
  #define NRF_TERMLEN_BITS    (3)
  
  /* Maximum length of frames */
  #define NRF_MAXLEN          (255)
  #define NRF_BALEN           (3)     /* For base address of 3 bytes */
  
  /* NRF_RADIO_NS->PCNF0 configuration values */
  #define NRF_PCNF0           (NRF_LFLEN_BITS << RADIO_PCNF0_LFLEN_Pos) | \
                              (RADIO_PCNF0_S1INCL_Msk) | \
                              (NRF_S0LEN << RADIO_PCNF0_S0LEN_Pos) | \
                              (NRF_S1LEN_BITS << RADIO_PCNF0_S1LEN_Pos)
  #define NRF_PCNF0_1M        (NRF_PCNF0) | \
                              (RADIO_PCNF0_PLEN_8bit << RADIO_PCNF0_PLEN_Pos)
  #define NRF_PCNF0_2M        (NRF_PCNF0) | \
                              (RADIO_PCNF0_PLEN_16bit << RADIO_PCNF0_PLEN_Pos)
  #define NRF_PCNF0_CODED     (NRF_PCNF0) | \
                              (RADIO_PCNF0_PLEN_LongRange << RADIO_PCNF0_PLEN_Pos) | \
                              (NRF_CILEN_BITS << RADIO_PCNF0_CILEN_Pos) | \
                              (NRF_TERMLEN_BITS << RADIO_PCNF0_TERMLEN_Pos)
  
  /* BLE PHY data structure */
  struct ble_phy_obj {
      uint8_t phy_stats_initialized;
      int8_t phy_txpwr_dbm;
      uint8_t phy_chan;
      uint8_t phy_state;
      uint8_t phy_transition;
      uint8_t phy_transition_late;
      uint8_t phy_rx_started;
      uint8_t phy_encrypted;
      uint8_t phy_privacy;
      uint8_t phy_tx_pyld_len;
      uint8_t phy_cur_phy_mode;
      uint8_t phy_tx_phy_mode;
      uint8_t phy_rx_phy_mode;
      uint8_t phy_bcc_offset;
      int8_t rx_pwr_compensation;
      uint32_t phy_aar_scratch;
      uint32_t phy_access_address;
      struct ble_mbuf_hdr rxhdr;
      void *txend_arg;
      ble_phy_tx_end_func txend_cb;
      uint32_t phy_start_cputime;
  };
  struct ble_phy_obj g_ble_phy_data;
  
  /* XXX: if 27 byte packets desired we can make this smaller */
  /* Global transmit/receive buffer */
  static uint32_t g_ble_phy_tx_buf[(BLE_PHY_MAX_PDU_LEN + 3) / 4];
  static uint32_t g_ble_phy_rx_buf[(BLE_PHY_MAX_PDU_LEN + 3) / 4];
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
  /* Make sure word-aligned for faster copies */
  static uint32_t g_ble_phy_enc_buf[(BLE_PHY_MAX_PDU_LEN + 3) / 4];
  #endif
  
  /* RF center frequency for each channel index (offset from 2400 MHz) */
  static const uint8_t g_ble_phy_chan_freq[BLE_PHY_NUM_CHANS] = {
      4,  6,  8, 10, 12, 14, 16, 18, 20, 22, /* 0-9 */
      24, 28, 30, 32, 34, 36, 38, 40, 42, 44, /* 10-19 */
      46, 48, 50, 52, 54, 56, 58, 60, 62, 64, /* 20-29 */
      66, 68, 70, 72, 74, 76, 78,  2, 26, 80, /* 30-39 */
  };
  
  #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
  /* packet start offsets (in usecs) */
  static const uint16_t g_ble_phy_mode_pkt_start_off[BLE_PHY_NUM_MODE] = {
      [BLE_PHY_MODE_1M] = 40,
      [BLE_PHY_MODE_2M] = 24,
      [BLE_PHY_MODE_CODED_125KBPS] = 376,
      [BLE_PHY_MODE_CODED_500KBPS] = 376
  };
  #endif
  
  /* Various radio timings */
  /* Radio ramp-up times in usecs (fast mode) */
  #define BLE_PHY_T_TXENFAST      (XCVR_TX_RADIO_RAMPUP_USECS)
  #define BLE_PHY_T_RXENFAST      (XCVR_RX_RADIO_RAMPUP_USECS)
  
  /* delay between EVENTS_READY and start of tx */
  static const uint8_t g_ble_phy_t_txdelay[BLE_PHY_NUM_MODE] = {
      [BLE_PHY_MODE_1M] = 4,
      [BLE_PHY_MODE_2M] = 3,
      [BLE_PHY_MODE_CODED_125KBPS] = 5,
      [BLE_PHY_MODE_CODED_500KBPS] = 5
  };
  /* delay between EVENTS_END and end of txd packet */
  static const uint8_t g_ble_phy_t_txenddelay[BLE_PHY_NUM_MODE] = {
      [BLE_PHY_MODE_1M] = 4,
      [BLE_PHY_MODE_2M] = 3,
      [BLE_PHY_MODE_CODED_125KBPS] = 9,
      [BLE_PHY_MODE_CODED_500KBPS] = 3
  };
  /* delay between rxd access address (w/ TERM1 for coded) and EVENTS_ADDRESS */
  static const uint8_t g_ble_phy_t_rxaddrdelay[BLE_PHY_NUM_MODE] = {
      [BLE_PHY_MODE_1M] = 6,
      [BLE_PHY_MODE_2M] = 2,
      [BLE_PHY_MODE_CODED_125KBPS] = 17,
      [BLE_PHY_MODE_CODED_500KBPS] = 17
  };
  /* delay between end of rxd packet and EVENTS_END */
  static const uint8_t g_ble_phy_t_rxenddelay[BLE_PHY_NUM_MODE] = {
      [BLE_PHY_MODE_1M] = 6,
      [BLE_PHY_MODE_2M] = 2,
      [BLE_PHY_MODE_CODED_125KBPS] = 27,
      [BLE_PHY_MODE_CODED_500KBPS] = 22
  };
  
  /* Statistics */
  STATS_SECT_START(ble_phy_stats)
  STATS_SECT_ENTRY(phy_isrs)
  STATS_SECT_ENTRY(tx_good)
  STATS_SECT_ENTRY(tx_fail)
  STATS_SECT_ENTRY(tx_late)
  STATS_SECT_ENTRY(tx_bytes)
  STATS_SECT_ENTRY(rx_starts)
  STATS_SECT_ENTRY(rx_aborts)
  STATS_SECT_ENTRY(rx_valid)
  STATS_SECT_ENTRY(rx_crc_err)
  STATS_SECT_ENTRY(rx_late)
  STATS_SECT_ENTRY(radio_state_errs)
  STATS_SECT_ENTRY(rx_hw_err)
  STATS_SECT_ENTRY(tx_hw_err)
  STATS_SECT_END
  STATS_SECT_DECL(ble_phy_stats) ble_phy_stats;
  
  STATS_NAME_START(ble_phy_stats)
  STATS_NAME(ble_phy_stats, phy_isrs)
  STATS_NAME(ble_phy_stats, tx_good)
  STATS_NAME(ble_phy_stats, tx_fail)
  STATS_NAME(ble_phy_stats, tx_late)
  STATS_NAME(ble_phy_stats, tx_bytes)
  STATS_NAME(ble_phy_stats, rx_starts)
  STATS_NAME(ble_phy_stats, rx_aborts)
  STATS_NAME(ble_phy_stats, rx_valid)
  STATS_NAME(ble_phy_stats, rx_crc_err)
  STATS_NAME(ble_phy_stats, rx_late)
  STATS_NAME(ble_phy_stats, radio_state_errs)
  STATS_NAME(ble_phy_stats, rx_hw_err)
  STATS_NAME(ble_phy_stats, tx_hw_err)
  STATS_NAME_END(ble_phy_stats)
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
  /*
   * Per nordic, the number of bytes needed for scratch is 16 + MAX_PKT_SIZE.
   * However, when I used a smaller size it still overwrote the scratchpad. Until
   * I figure this out I am just going to allocate 67 words so we have enough
   * space for 267 bytes of scratch. I used 268 bytes since not sure if this
   * needs to be aligned and burning a byte is no big deal.
   *
   *#define NRF_ENC_SCRATCH_WORDS (((MYNEWT_VAL(BLE_LL_MAX_PKT_SIZE) + 16) + 3) / 4)
   */
  #define NRF_ENC_SCRATCH_WORDS   (67)
  
  static uint32_t nrf_encrypt_scratchpad[NRF_ENC_SCRATCH_WORDS];
  
  struct nrf_ccm_data {
      uint8_t key[16];
      uint64_t pkt_counter;
      uint8_t dir_bit;
      uint8_t iv[8];
  } __attribute__((packed));
  
  static struct nrf_ccm_data nrf_ccm_data;
  #endif
  
  static int g_ble_phy_gpiote_idx;
  
  #if MYNEWT_VAL(BLE_LL_PA) || MYNEWT_VAL(BLE_LL_LNA)
  
  #define PLNA_SINGLE_GPIO \
          (!MYNEWT_VAL(BLE_LL_PA) || !MYNEWT_VAL(BLE_LL_LNA) || \
           (MYNEWT_VAL(BLE_LL_PA_GPIO) == MYNEWT_VAL(BLE_LL_LNA_GPIO)))
  
  #if PLNA_SINGLE_GPIO
  static uint8_t plna_idx;
  #else
  #if MYNEWT_VAL(BLE_LL_PA)
  static uint8_t plna_pa_idx;
  #endif
  #if MYNEWT_VAL(BLE_LL_LNA)
  static uint8_t plna_lna_idx;
  #endif
  #endif
  
  #endif
  
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_TXRXEN_READY_PIN) >= 0
  static uint8_t phy_dbg_txrxen_ready_idx;
  #endif
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_ADDRESS_END_PIN) >= 0
  static uint8_t phy_dbg_address_end_idx;
  #endif
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_WFR_PIN) >= 0
  static uint8_t phy_dbg_wfr_idx;
  #endif
  
  #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
  
  uint32_t
  ble_phy_mode_pdu_start_off(int phy_mode)
  {
      return g_ble_phy_mode_pkt_start_off[phy_mode];
  }
  
  static void
  ble_phy_mode_apply(uint8_t phy_mode)
  {
      if (phy_mode == g_ble_phy_data.phy_cur_phy_mode) {
          return;
      }
  
      switch (phy_mode) {
      case BLE_PHY_MODE_1M:
          NRF_RADIO_NS->MODE = RADIO_MODE_MODE_Ble_1Mbit;
          *((volatile uint32_t *)0x41008588) = *((volatile uint32_t *)0x01FF0080);
          NRF_RADIO_NS->PCNF0 = NRF_PCNF0_1M;
          break;
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY)
      case BLE_PHY_MODE_2M:
          NRF_RADIO_NS->MODE = RADIO_MODE_MODE_Ble_2Mbit;
          *((volatile uint32_t *)0x41008588) = *((volatile uint32_t *)0x01FF0084);
          NRF_RADIO_NS->PCNF0 = NRF_PCNF0_2M;
          break;
  #endif
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
      case BLE_PHY_MODE_CODED_125KBPS:
          NRF_RADIO_NS->MODE = RADIO_MODE_MODE_Ble_LR125Kbit;
          *((volatile uint32_t *)0x41008588) = *((volatile uint32_t *)0x01FF0080);
          NRF_RADIO_NS->PCNF0 = NRF_PCNF0_CODED;
          break;
      case BLE_PHY_MODE_CODED_500KBPS:
          NRF_RADIO_NS->MODE = RADIO_MODE_MODE_Ble_LR500Kbit;
          *((volatile uint32_t *)0x41008588) = *((volatile uint32_t *)0x01FF0080);
          NRF_RADIO_NS->PCNF0 = NRF_PCNF0_CODED;
          break;
  #endif
      default:
          assert(0);
      }
  
      g_ble_phy_data.phy_cur_phy_mode = phy_mode;
  }
  #endif
  
  void
  ble_phy_mode_set(uint8_t tx_phy_mode, uint8_t rx_phy_mode)
  {
      g_ble_phy_data.phy_tx_phy_mode = tx_phy_mode;
      g_ble_phy_data.phy_rx_phy_mode = rx_phy_mode;
  }
  
  static void
  ble_phy_plna_enable_pa(void)
  {
  #if MYNEWT_VAL(BLE_LL_PA)
      ble_ll_plna_pa_enable();
  
  #if PLNA_SINGLE_GPIO
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_idx] = DPPI_CH_SUB(RADIO_EVENTS_READY);
  #else
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_pa_idx] = DPPI_CH_SUB(RADIO_EVENTS_READY);
  #endif
  #endif
  }
  
  static void
  ble_phy_plna_disable_pa(void)
  {
  #if MYNEWT_VAL(BLE_LL_PA)
      ble_ll_plna_pa_disable();
  
  #if PLNA_SINGLE_GPIO
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
  #else
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_pa_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
  #endif
  #endif
  }
  
  static void
  ble_phy_plna_enable_lna(void)
  {
  #if MYNEWT_VAL(BLE_LL_LNA)
      ble_ll_plna_lna_enable();
  
  #if PLNA_SINGLE_GPIO
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_idx] = DPPI_CH_SUB(RADIO_EVENTS_READY);
  #else
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_lna_idx] = DPPI_CH_SUB(RADIO_EVENTS_READY);
  #endif
  #endif
  }
  
  static void
  ble_phy_plna_disable_lna(void)
  {
  #if MYNEWT_VAL(BLE_LL_LNA)
      ble_ll_plna_lna_disable();
  
  #if PLNA_SINGLE_GPIO
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
  #else
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_lna_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
  #endif
  #endif
  }
  
  int
  ble_phy_get_cur_phy(void)
  {
  #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
      switch (g_ble_phy_data.phy_cur_phy_mode) {
      case BLE_PHY_MODE_1M:
          return BLE_PHY_1M;
      case BLE_PHY_MODE_2M:
          return BLE_PHY_2M;
      case BLE_PHY_MODE_CODED_125KBPS:
      case BLE_PHY_MODE_CODED_500KBPS:
          return BLE_PHY_CODED;
      default:
          assert(0);
          return -1;
      }
  #else
      return BLE_PHY_1M;
  #endif
  }
  
  void
  ble_phy_rxpdu_copy(uint8_t *dptr, struct os_mbuf *rxpdu)
  {
      uint32_t rem_len;
      uint32_t copy_len;
      uint32_t block_len;
      uint32_t block_rem_len;
      void *dst;
      void *src;
      struct os_mbuf * om;
  
      /* Better be aligned */
      assert(((uint32_t)dptr & 3) == 0);
  
      block_len = rxpdu->om_omp->omp_databuf_len;
      rem_len = OS_MBUF_PKTHDR(rxpdu)->omp_len;
      src = dptr;
  
      /*
       * Setup for copying from first mbuf which is shorter due to packet header
       * and extra leading space
       */
      copy_len = block_len - rxpdu->om_pkthdr_len - 4;
      om = rxpdu;
      dst = om->om_data;
  
      while (true) {
          /*
           * Always copy blocks of length aligned to word size, only last mbuf
           * will have remaining non-word size bytes appended.
           */
          block_rem_len = copy_len;
          copy_len = min(copy_len, rem_len);
          copy_len &= ~3;
  
          dst = om->om_data;
          om->om_len = copy_len;
          rem_len -= copy_len;
          block_rem_len -= copy_len;
  
          __asm__ volatile (".syntax unified              \n"
                            "   mov  r4, %[len]           \n"
                            "   b    2f                   \n"
                            "1: ldr  r3, [%[src], %[len]] \n"
                            "   str  r3, [%[dst], %[len]] \n"
                            "2: subs %[len], #4           \n"
                            "   bpl  1b                   \n"
                            "   adds %[src], %[src], r4   \n"
                            "   adds %[dst], %[dst], r4   \n"
                            : [dst] "+r" (dst), [src] "+r" (src),
                            [len] "+r" (copy_len)
                            :
                            : "r3", "r4", "memory"
                            );
  
          if ((rem_len < 4) && (block_rem_len >= rem_len)) {
              break;
          }
  
          /* Move to next mbuf */
          om = SLIST_NEXT(om, om_next);
          copy_len = block_len;
      }
  
      /* Copy remaining bytes, if any, to last mbuf */
      om->om_len += rem_len;
      __asm__ volatile (".syntax unified              \n"
                        "   b    2f                   \n"
                        "1: ldrb r3, [%[src], %[len]] \n"
                        "   strb r3, [%[dst], %[len]] \n"
                        "2: subs %[len], #1           \n"
                        "   bpl  1b                   \n"
                        : [len] "+r" (rem_len)
                        : [dst] "r" (dst), [src] "r" (src)
                        : "r3", "memory"
                        );
  
      /* Copy header */
      memcpy(BLE_MBUF_HDR_PTR(rxpdu), &g_ble_phy_data.rxhdr,
             sizeof(struct ble_mbuf_hdr));
  }
  
  /**
   * Called when we want to wait if the radio is in either the rx or tx
   * disable states. We want to wait until that state is over before doing
   * anything to the radio
   */
  static void
  nrf_wait_disabled(void)
  {
      uint32_t state;
  
      state = NRF_RADIO_NS->STATE;
      if (state != RADIO_STATE_STATE_Disabled) {
          if ((state == RADIO_STATE_STATE_RxDisable) ||
              (state == RADIO_STATE_STATE_TxDisable)) {
              /* This will end within a short time (6 usecs). Just poll */
              while (NRF_RADIO_NS->STATE == state) {
                  /* If this fails, something is really wrong. Should last
                   * no more than 6 usecs
                   */
              }
          }
      }
  }
  
  static int
  ble_phy_set_start_time(uint32_t cputime, uint8_t rem_usecs, bool tx)
  {
      uint32_t next_cc;
      uint32_t cur_cc;
      uint32_t cntr;
      uint32_t delta;
  
      /*
       * We need to adjust start time to include radio ramp-up and TX pipeline
       * delay (the latter only if applicable, so only for TX).
       *
       * Radio ramp-up time is 40 usecs and TX delay is 3 or 5 usecs depending on
       * phy, thus we'll offset RTC by 2 full ticks (61 usecs) and then compensate
       * using TIMER0 with 1 usec precision.
       */
  
      cputime -= 2;
      rem_usecs += 61;
      if (tx) {
          rem_usecs -= BLE_PHY_T_TXENFAST;
          rem_usecs -= g_ble_phy_t_txdelay[g_ble_phy_data.phy_cur_phy_mode];
      } else {
          rem_usecs -= BLE_PHY_T_RXENFAST;
      }
  
      /*
       * rem_usecs will be no more than 2 ticks, but if it is more than single
       * tick then we should better count one more low-power tick rather than
       * 30 high-power usecs. Also make sure we don't set TIMER0 CC to 0 as the
       * compare won't occur.
       */
  
      if (rem_usecs > 30) {
          cputime++;
          rem_usecs -= 30;
      }
  
      /*
       * Can we set the RTC compare to start TIMER0? We can do it if:
       *      a) Current compare value is not N+1 or N+2 ticks from current
       *      counter.
       *      b) The value we want to set is not at least N+2 from current
       *      counter.
       *
       * NOTE: since the counter can tick 1 while we do these calculations we
       * need to account for it.
       */
      next_cc = cputime & 0xffffff;
      cur_cc = NRF_RTC0_NS->CC[0];
      cntr = NRF_RTC0_NS->COUNTER;
  
      delta = (cur_cc - cntr) & 0xffffff;
      if ((delta <= 3) && (delta != 0)) {
          return -1;
      }
      delta = (next_cc - cntr) & 0xffffff;
      if ((delta & 0x800000) || (delta < 3)) {
          return -1;
      }
  
      /* Clear and set TIMER0 to fire off at proper time */
      NRF_TIMER0_NS->TASKS_CLEAR = 1;
      NRF_TIMER0_NS->CC[0] = rem_usecs;
      NRF_TIMER0_NS->EVENTS_COMPARE[0] = 0;
  
      /* Set RTC compare to start TIMER0 */
      NRF_RTC0_NS->EVENTS_COMPARE[0] = 0;
      NRF_RTC0_NS->CC[0] = next_cc;
      NRF_RTC0_NS->EVTENSET = RTC_EVTENSET_COMPARE0_Msk;
  
      /* Enable PPI */
      NRF_TIMER0_NS->SUBSCRIBE_START = DPPI_CH_SUB(RTC0_EVENTS_COMPARE_0);
  
      /* Store the cputime at which we set the RTC */
      g_ble_phy_data.phy_start_cputime = cputime;
  
      return 0;
  }
  
  static int
  ble_phy_set_start_now(void)
  {
      os_sr_t sr;
      uint32_t now;
  
      OS_ENTER_CRITICAL(sr);
  
      /*
       * Set TIMER0 to fire immediately. We can't set CC to 0 as compare will not
       * occur in such case.
       */
      NRF_TIMER0_NS->TASKS_CLEAR = 1;
      NRF_TIMER0_NS->CC[0] = 1;
      NRF_TIMER0_NS->EVENTS_COMPARE[0] = 0;
  
      /*
       * Set RTC compare to start TIMER0. We need to set it to at least N+2 ticks
       * from current value to guarantee triggering compare event, but let's set
       * it to N+3 to account for possible extra tick on RTC0 during these
       * operations.
       */
      now = os_cputime_get32();
      NRF_RTC0_NS->EVENTS_COMPARE[0] = 0;
      NRF_RTC0_NS->CC[0] = now + 3;
      NRF_RTC0_NS->EVTENSET = RTC_EVTENSET_COMPARE0_Msk;
  
      /* Enable PPI */
      NRF_TIMER0_NS->SUBSCRIBE_START = DPPI_CH_SUB(RTC0_EVENTS_COMPARE_0);
  
      /*
       * Store the cputime at which we set the RTC
       *
       * XXX Compare event may be triggered on previous CC value (if it was set to
       * less than N+2) so in rare cases actual start time may be 2 ticks earlier
       * than what we expect. Since this is only used on RX, it may cause AUX scan
       * to be scheduled 1 or 2 ticks too late so we'll miss it - it's acceptable
       * for now.
       */
      g_ble_phy_data.phy_start_cputime = now + 3;
  
      OS_EXIT_CRITICAL(sr);
  
      return 0;
  }
  
  void
  ble_phy_wfr_enable(int txrx, uint8_t tx_phy_mode, uint32_t wfr_usecs)
  {
      uint32_t end_time;
      uint8_t phy;
  
      phy = g_ble_phy_data.phy_cur_phy_mode;
  
      if (txrx == BLE_PHY_WFR_ENABLE_TXRX) {
          /* RX shall start exactly T_IFS after TX end captured in CC[2] */
          end_time = NRF_TIMER0_NS->CC[2] + BLE_LL_IFS;
          /* Adjust for delay between EVENT_END and actual TX end time */
          end_time += g_ble_phy_t_txenddelay[tx_phy_mode];
          /* Wait a bit longer due to allowed active clock accuracy */
          end_time += 2;
          /*
           * It's possible that we'll capture PDU start time at the end of timer
           * cycle and since wfr expires at the beginning of calculated timer
           * cycle it can be almost 1 usec too early. Let's compensate for this
           * by waiting 1 usec more.
           */
          end_time += 1;
      } else {
          /*
           * RX shall start no later than wfr_usecs after RX enabled.
           * CC[0] is the time of RXEN so adjust for radio ram-up.
           * Do not add jitter since this is already covered by LL.
           */
          end_time = NRF_TIMER0_NS->CC[0] + BLE_PHY_T_RXENFAST + wfr_usecs;
      }
  
      /*
       * Note: on LE Coded EVENT_ADDRESS is fired after TERM1 is received, so
       *       we are actually calculating relative to start of packet payload
       *       which is fine.
       */
  
      /* Adjust for receiving access address since this triggers EVENT_ADDRESS */
      end_time += ble_phy_mode_pdu_start_off(phy);
      /* Adjust for delay between actual access address RX and EVENT_ADDRESS */
      end_time += g_ble_phy_t_rxaddrdelay[phy];
  
      /* wfr_secs is the time from rxen until timeout */
      NRF_TIMER0_NS->CC[3] = end_time;
      NRF_TIMER0_NS->EVENTS_COMPARE[3] = 0;
  
      /* Subscribe for wait for response events  */
      NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[3] = DPPI_CH_SUB(RADIO_EVENTS_ADDRESS);
      NRF_RADIO_NS->SUBSCRIBE_DISABLE = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_3);
  
      /*
       * It may happen that if CPU is halted for a brief moment (e.g. during flash
       * erase or write), TIMER0 already counted past CC[3] and thus wfr will not
       * fire as expected. In case this happened, let's just disable PPIs for wfr
       * and trigger wfr manually (i.e. disable radio).
       *
       * Note that the same applies to RX start time set in CC[0] but since it
       * should fire earlier than wfr, fixing wfr is enough.
       *
       * CC[1] is only used as a reference on RX start, we do not need it here so
       * it can be used to read TIMER0 counter.
       */
      NRF_TIMER0_NS->TASKS_CAPTURE[1] = 1;
      if (NRF_TIMER0_NS->CC[1] > NRF_TIMER0_NS->CC[3]) {
          /* Unsubscribe from wfr events */
          NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[3] = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
          NRF_RADIO_NS->SUBSCRIBE_DISABLE = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_3);
  
          NRF_RADIO_NS->TASKS_DISABLE = 1;
      }
  }
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
  static uint32_t
  ble_phy_get_ccm_datarate(void)
  {
  #if BLE_LL_BT5_PHY_SUPPORTED
      switch (g_ble_phy_data.phy_cur_phy_mode) {
      case BLE_PHY_MODE_1M:
          return CCM_MODE_DATARATE_1Mbit << CCM_MODE_DATARATE_Pos;
      case BLE_PHY_MODE_2M:
          return CCM_MODE_DATARATE_2Mbit << CCM_MODE_DATARATE_Pos;
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
      case BLE_PHY_MODE_CODED_125KBPS:
          return CCM_MODE_DATARATE_125Kbps << CCM_MODE_DATARATE_Pos;
      case BLE_PHY_MODE_CODED_500KBPS:
          return CCM_MODE_DATARATE_500Kbps << CCM_MODE_DATARATE_Pos;
  #endif
      }
  
      assert(0);
      return 0;
  #else
      return CCM_MODE_DATARATE_1Mbit << CCM_MODE_DATARATE_Pos;
  #endif
  }
  #endif
  
  /**
   * Setup transceiver for receive.
   */
  static void
  ble_phy_rx_xcvr_setup(void)
  {
      uint8_t *dptr;
  
      dptr = (uint8_t *)&g_ble_phy_rx_buf[0];
      dptr += 3;
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
      if (g_ble_phy_data.phy_encrypted) {
          NRF_RADIO_NS->PACKETPTR = (uint32_t)&g_ble_phy_enc_buf[0];
          NRF_CCM_NS->INPTR = (uint32_t)&g_ble_phy_enc_buf[0];
          NRF_CCM_NS->OUTPTR = (uint32_t)dptr;
          NRF_CCM_NS->SCRATCHPTR = (uint32_t)&nrf_encrypt_scratchpad[0];
          NRF_CCM_NS->MODE = CCM_MODE_LENGTH_Msk | CCM_MODE_MODE_Decryption |
                             ble_phy_get_ccm_datarate();
          NRF_CCM_NS->CNFPTR = (uint32_t)&nrf_ccm_data;
          NRF_CCM_NS->SHORTS = 0;
          NRF_CCM_NS->EVENTS_ERROR = 0;
          NRF_CCM_NS->EVENTS_ENDCRYPT = 0;
          NRF_CCM_NS->TASKS_KSGEN = 1;
  
          /* Subscribe to radio address event */
          NRF_CCM_NS->SUBSCRIBE_CRYPT = DPPI_CH_SUB(RADIO_EVENTS_ADDRESS);
      } else {
          NRF_RADIO_NS->PACKETPTR = (uint32_t)dptr;
      }
  #else
      NRF_RADIO_NS->PACKETPTR = (uint32_t)dptr;
  #endif
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
      if (g_ble_phy_data.phy_privacy) {
          NRF_AAR_NS->ENABLE = AAR_ENABLE_ENABLE_Enabled;
          NRF_AAR_NS->IRKPTR = (uint32_t)&g_nrf_irk_list[0];
          NRF_AAR_NS->SCRATCHPTR = (uint32_t)&g_ble_phy_data.phy_aar_scratch;
          NRF_AAR_NS->EVENTS_END = 0;
          NRF_AAR_NS->EVENTS_RESOLVED = 0;
          NRF_AAR_NS->EVENTS_NOTRESOLVED = 0;
      } else {
          if (g_ble_phy_data.phy_encrypted == 0) {
              NRF_AAR_NS->ENABLE = AAR_ENABLE_ENABLE_Disabled;
          }
      }
  #endif
  
      /* Turn off trigger TXEN on output compare match and AAR on bcmatch */
      NRF_RADIO_NS->SUBSCRIBE_TXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
      NRF_AAR_NS->SUBSCRIBE_START = DPPI_CH_UNSUB(RADIO_EVENTS_BCMATCH);
  
      /* Reset the rx started flag. Used for the wait for response */
      g_ble_phy_data.phy_rx_started = 0;
      g_ble_phy_data.phy_state = BLE_PHY_STATE_RX;
  
  #if BLE_LL_BT5_PHY_SUPPORTED
      /*
       * On Coded PHY there are CI and TERM1 fields before PDU starts so we need
       * to take this into account when setting up BCC.
       */
      if (g_ble_phy_data.phy_cur_phy_mode == BLE_PHY_MODE_CODED_125KBPS ||
          g_ble_phy_data.phy_cur_phy_mode == BLE_PHY_MODE_CODED_500KBPS) {
          g_ble_phy_data.phy_bcc_offset = 5;
      } else {
          g_ble_phy_data.phy_bcc_offset = 0;
      }
  #else
      g_ble_phy_data.phy_bcc_offset = 0;
  #endif
  
      /* I want to know when 1st byte received (after address) */
      NRF_RADIO_NS->BCC = 8 + g_ble_phy_data.phy_bcc_offset; /* in bits */
      NRF_RADIO_NS->EVENTS_ADDRESS = 0;
      NRF_RADIO_NS->EVENTS_DEVMATCH = 0;
      NRF_RADIO_NS->EVENTS_BCMATCH = 0;
      NRF_RADIO_NS->EVENTS_RSSIEND = 0;
      NRF_RADIO_NS->EVENTS_CRCOK = 0;
      NRF_RADIO_NS->SHORTS = RADIO_SHORTS_END_DISABLE_Msk |
                             RADIO_SHORTS_READY_START_Msk |
                             RADIO_SHORTS_ADDRESS_BCSTART_Msk |
                             RADIO_SHORTS_ADDRESS_RSSISTART_Msk |
                             RADIO_SHORTS_DISABLED_RSSISTOP_Msk;
  
      NRF_RADIO_NS->INTENSET = RADIO_INTENSET_ADDRESS_Msk |
                               RADIO_INTENSET_DISABLED_Msk;
  }
  
  /**
   * Called from interrupt context when the transmit ends
   *
   */
  static void
  ble_phy_tx_end_isr(void)
  {
      uint8_t tx_phy_mode;
      uint8_t was_encrypted;
      uint8_t transition;
      uint32_t rx_time;
  
      /* Store PHY on which we've just transmitted smth */
      tx_phy_mode = g_ble_phy_data.phy_cur_phy_mode;
  
      /* If this transmission was encrypted we need to remember it */
      was_encrypted = g_ble_phy_data.phy_encrypted;
      (void)was_encrypted;
  
      /* Better be in TX state! */
      assert(g_ble_phy_data.phy_state == BLE_PHY_STATE_TX);
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
      /*
       * XXX: not sure what to do. We had a HW error during transmission.
       * For now I just count a stat but continue on like all is good.
       */
      if (was_encrypted) {
          if (NRF_CCM_NS->EVENTS_ERROR) {
              STATS_INC(ble_phy_stats, tx_hw_err);
              NRF_CCM_NS->EVENTS_ERROR = 0;
          }
      }
  #endif
  
      /* Call transmit end callback */
      if (g_ble_phy_data.txend_cb) {
          g_ble_phy_data.txend_cb(g_ble_phy_data.txend_arg);
      }
  
      transition = g_ble_phy_data.phy_transition;
      if (transition == BLE_PHY_TRANSITION_TX_RX) {
  
  #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
          ble_phy_mode_apply(g_ble_phy_data.phy_rx_phy_mode);
  #endif
  
          /* Packet pointer needs to be reset. */
          ble_phy_rx_xcvr_setup();
  
          ble_phy_wfr_enable(BLE_PHY_WFR_ENABLE_TXRX, tx_phy_mode, 0);
  
          /* Schedule RX exactly T_IFS after TX end captured in CC[2] */
          rx_time = NRF_TIMER0_NS->CC[2] + BLE_LL_IFS;
          /* Adjust for delay between EVENT_END and actual TX end time */
          rx_time += g_ble_phy_t_txenddelay[tx_phy_mode];
          /* Adjust for radio ramp-up */
          rx_time -= BLE_PHY_T_RXENFAST;
          /* Start listening a bit earlier due to allowed active clock accuracy */
          rx_time -= 2;
  
          NRF_TIMER0_NS->CC[0] = rx_time;
          NRF_TIMER0_NS->EVENTS_COMPARE[0] = 0;
  
          /* Start radio on timer */
          NRF_RADIO_NS->SUBSCRIBE_RXEN = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_0);
  
          ble_phy_plna_enable_lna();
      } else {
          NRF_TIMER0_NS->TASKS_STOP = 1;
          NRF_TIMER0_NS->TASKS_SHUTDOWN = 1;
  
          NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[3] = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
          NRF_RADIO_NS->SUBSCRIBE_DISABLE = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_3);
          NRF_RADIO_NS->SUBSCRIBE_TXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
          NRF_TIMER0_NS->SUBSCRIBE_START = DPPI_CH_UNSUB(RTC0_EVENTS_COMPARE_0);
  
          assert(transition == BLE_PHY_TRANSITION_NONE);
      }
  }
  
  static inline uint8_t
  ble_phy_get_cur_rx_phy_mode(void)
  {
      uint8_t phy;
  
      phy = g_ble_phy_data.phy_cur_phy_mode;
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
      /*
       * For Coded PHY mode can be set to either codings since actual coding is
       * set in packet header. However, here we need actual coding of received
       * packet as this determines pipeline delays so need to figure this out
       * using CI field.
       */
      if ((phy == BLE_PHY_MODE_CODED_125KBPS) ||
          (phy == BLE_PHY_MODE_CODED_500KBPS)) {
          phy = NRF_RADIO_NS->PDUSTAT & RADIO_PDUSTAT_CISTAT_Msk ?
                BLE_PHY_MODE_CODED_500KBPS : BLE_PHY_MODE_CODED_125KBPS;
      }
  #endif
  
      return phy;
  }
  
  static void
  ble_phy_rx_end_isr(void)
  {
      int rc;
      uint8_t *dptr;
      uint8_t crcok;
      uint32_t tx_time;
      struct ble_mbuf_hdr *ble_hdr;
  
      /* Disable automatic RXEN */
      NRF_RADIO_NS->SUBSCRIBE_RXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
  
      /* Set RSSI and CRC status flag in header */
      ble_hdr = &g_ble_phy_data.rxhdr;
      assert(NRF_RADIO_NS->EVENTS_RSSIEND != 0);
      ble_hdr->rxinfo.rssi = (-1 * NRF_RADIO_NS->RSSISAMPLE) +
                             g_ble_phy_data.rx_pwr_compensation;
  
      dptr = (uint8_t *)&g_ble_phy_rx_buf[0];
      dptr += 3;
  
      /* Count PHY crc errors and valid packets */
      crcok = NRF_RADIO_NS->EVENTS_CRCOK;
      if (!crcok) {
          STATS_INC(ble_phy_stats, rx_crc_err);
      } else {
          STATS_INC(ble_phy_stats, rx_valid);
          ble_hdr->rxinfo.flags |= BLE_MBUF_HDR_F_CRC_OK;
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
          if (g_ble_phy_data.phy_encrypted) {
              /* Only set MIC failure flag if frame is not zero length */
              if ((dptr[1] != 0) && (NRF_CCM_NS->MICSTATUS == 0)) {
                  ble_hdr->rxinfo.flags |= BLE_MBUF_HDR_F_MIC_FAILURE;
              }
  
              /*
               * XXX: not sure how to deal with this. This should not
               * be a MIC failure but we should not hand it up. I guess
               * this is just some form of rx error and that is how we
               * handle it? For now, just set CRC error flags
               */
              if (NRF_CCM_NS->EVENTS_ERROR) {
                  STATS_INC(ble_phy_stats, rx_hw_err);
                  ble_hdr->rxinfo.flags &= ~BLE_MBUF_HDR_F_CRC_OK;
              }
  
              /*
               * XXX: This is a total hack work-around for now but I dont
               * know what else to do. If ENDCRYPT is not set and we are
               * encrypted we need to not trust this frame and drop it.
               */
              if (NRF_CCM_NS->EVENTS_ENDCRYPT == 0) {
                  STATS_INC(ble_phy_stats, rx_hw_err);
                  ble_hdr->rxinfo.flags &= ~BLE_MBUF_HDR_F_CRC_OK;
              }
          }
  #endif
      }
  
  #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
      ble_phy_mode_apply(g_ble_phy_data.phy_tx_phy_mode);
  #endif
  
      /*
       * Let's schedule TX now and we will just cancel it after processing RXed
       * packet if we don't need TX.
       *
       * We need this to initiate connection in case AUX_CONNECT_REQ was sent on
       * LE Coded S8. In this case the time we process RXed packet is roughly the
       * same as the limit when we need to have TX scheduled (i.e. TIMER0 and PPI
       * armed) so we may simply miss the slot and set the timer in the past.
       *
       * When TX is scheduled in advance, we may event process packet a bit longer
       * during radio ramp-up - this gives us extra 40 usecs which is more than
       * enough.
       */
  
      /* Schedule TX exactly T_IFS after RX end captured in CC[2] */
      tx_time = NRF_TIMER0_NS->CC[2] + BLE_LL_IFS;
      /* Adjust for delay between actual RX end time and EVENT_END */
      tx_time -= g_ble_phy_t_rxenddelay[ble_hdr->rxinfo.phy_mode];
      /* Adjust for radio ramp-up */
      tx_time -= BLE_PHY_T_TXENFAST;
      /* Adjust for delay between EVENT_READY and actual TX start time */
      tx_time -= g_ble_phy_t_txdelay[g_ble_phy_data.phy_cur_phy_mode];
  
      NRF_TIMER0_NS->CC[0] = tx_time;
      NRF_TIMER0_NS->EVENTS_COMPARE[0] = 0;
  
      /* Enable automatic TX */
      NRF_RADIO_NS->SUBSCRIBE_TXEN = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_0);
  
      ble_phy_plna_enable_pa();
  
      /*
       * XXX: Hack warning!
       *
       * It may happen (during flash erase) that CPU is stopped for a moment and
       * TIMER0 already counted past CC[0]. In such case we will be stuck waiting
       * for TX to start since EVENTS_COMPARE[0] will not happen any time soon.
       * For now let's set a flag denoting that we are late in RX-TX transition so
       * ble_phy_tx() will fail - this allows everything to cleanup nicely without
       * the need for extra handling in many places.
       *
       * Note: CC[3] is used only for wfr which we do not need here.
       */
      NRF_TIMER0_NS->TASKS_CAPTURE[3] = 1;
      if (NRF_TIMER0_NS->CC[3] > NRF_TIMER0_NS->CC[0]) {
          NRF_RADIO_NS->SUBSCRIBE_TXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
  
          g_ble_phy_data.phy_transition_late = 1;
      }
  
      /*
       * XXX: This is a horrible ugly hack to deal with the RAM S1 byte
       * that is not sent over the air but is present here. Simply move the
       * data pointer to deal with it. Fix this later.
       */
      dptr[2] = dptr[1];
      dptr[1] = dptr[0];
      rc = ble_ll_rx_end(dptr + 1, ble_hdr);
      if (rc < 0) {
          ble_phy_disable();
      }
  }
  
  static bool
  ble_phy_rx_start_isr(void)
  {
      int rc;
      uint32_t state;
      uint32_t usecs;
      uint32_t pdu_usecs;
      uint32_t ticks;
      struct ble_mbuf_hdr *ble_hdr;
      uint8_t *dptr;
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
      int adva_offset;
  #endif
  
      dptr = (uint8_t *)&g_ble_phy_rx_buf[0];
  
      /* Clear events and clear interrupt */
      NRF_RADIO_NS->EVENTS_ADDRESS = 0;
      NRF_RADIO_NS->INTENCLR = RADIO_INTENCLR_ADDRESS_Msk;
  
      /* Clear wfr timer channels and DISABLED interrupt */
      NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[3] = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
      NRF_RADIO_NS->SUBSCRIBE_DISABLE = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_3);
  
      /* Initialize the ble mbuf header */
      ble_hdr = &g_ble_phy_data.rxhdr;
      ble_hdr->rxinfo.flags = ble_ll_state_get();
      ble_hdr->rxinfo.channel = g_ble_phy_data.phy_chan;
      ble_hdr->rxinfo.handle = 0;
      ble_hdr->rxinfo.phy = ble_phy_get_cur_phy();
      ble_hdr->rxinfo.phy_mode = ble_phy_get_cur_rx_phy_mode();
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
      ble_hdr->rxinfo.user_data = NULL;
  #endif
  
      /*
       * Calculate accurate packets start time (with remainder)
       *
       * We may start receiving packet somewhere during preamble in which case
       * it is possible that actual transmission started before TIMER0 was
       * running - need to take this into account.
       */
      ble_hdr->beg_cputime = g_ble_phy_data.phy_start_cputime;
  
      usecs = NRF_TIMER0_NS->CC[1];
      pdu_usecs = ble_phy_mode_pdu_start_off(ble_hdr->rxinfo.phy_mode) +
                  g_ble_phy_t_rxaddrdelay[ble_hdr->rxinfo.phy_mode];
      if (usecs < pdu_usecs) {
          g_ble_phy_data.phy_start_cputime--;
          usecs += 30;
      }
      usecs -= pdu_usecs;
  
      ticks = os_cputime_usecs_to_ticks(usecs);
      usecs -= os_cputime_ticks_to_usecs(ticks);
      if (usecs == 31) {
          usecs = 0;
          ++ticks;
      }
  
      ble_hdr->beg_cputime += ticks;
      ble_hdr->rem_usecs = usecs;
  
      /* Wait to get 1st byte of frame */
      while (1) {
          state = NRF_RADIO_NS->STATE;
          if (NRF_RADIO_NS->EVENTS_BCMATCH != 0) {
              break;
          }
  
          /*
           * If state is disabled, we should have the BCMATCH. If not,
           * something is wrong!
           */
          if (state == RADIO_STATE_STATE_Disabled) {
              NRF_RADIO_NS->INTENCLR = NRF_RADIO_IRQ_MASK_ALL;
              NRF_RADIO_NS->SHORTS = 0;
              return false;
          }
      }
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
      /*
       * If privacy is enabled and received PDU has TxAdd bit set (i.e. random
       * address) we try to resolve address using AAR.
       */
      if (g_ble_phy_data.phy_privacy && (dptr[3] & 0x40)) {
          /*
           * AdvA is located at 4th octet in RX buffer (after S0, length an S1
           * fields). In case of extended advertising PDU we need to add 2 more
           * octets for extended header.
           */
          adva_offset = (dptr[3] & 0x0f) == 0x07 ? 2 : 0;
          NRF_AAR_NS->ADDRPTR = (uint32_t)(dptr + 3 + adva_offset);
  
          /* Trigger AAR after last bit of AdvA is received */
          NRF_RADIO_NS->EVENTS_BCMATCH = 0;
          NRF_AAR_NS->SUBSCRIBE_START = DPPI_CH_SUB(RADIO_EVENTS_BCMATCH);
          NRF_RADIO_NS->BCC = (BLE_LL_PDU_HDR_LEN + adva_offset + BLE_DEV_ADDR_LEN) * 8 +
                              g_ble_phy_data.phy_bcc_offset;
      }
  #endif
  
      /* Call Link Layer receive start function */
      rc = ble_ll_rx_start(dptr + 3,
                           g_ble_phy_data.phy_chan,
                           &g_ble_phy_data.rxhdr);
      if (rc >= 0) {
          /* Set rx started flag and enable rx end ISR */
          g_ble_phy_data.phy_rx_started = 1;
      } else {
          /* Disable PHY */
          ble_phy_disable();
          STATS_INC(ble_phy_stats, rx_aborts);
      }
  
      /* Count rx starts */
      STATS_INC(ble_phy_stats, rx_starts);
  
      return true;
  }
  
  static void
  ble_phy_isr(void)
  {
      uint32_t irq_en;
  
      os_trace_isr_enter();
  
      /* Read irq register to determine which interrupts are enabled */
      irq_en = NRF_RADIO_NS->INTENCLR;
  
      /*
       * NOTE: order of checking is important! Possible, if things get delayed,
       * we have both an ADDRESS and DISABLED interrupt in rx state. If we get
       * an address, we disable the DISABLED interrupt.
       */
  
      /* We get this if we have started to receive a frame */
      if ((irq_en & RADIO_INTENCLR_ADDRESS_Msk) && NRF_RADIO_NS->EVENTS_ADDRESS) {
          /*
           * wfr timer is calculated to expire at the exact time we should start
           * receiving a packet (with 1 usec precision) so it is possible  it will
           * fire at the same time as EVENT_ADDRESS. If this happens, radio will
           * be disabled while we are waiting for EVENT_BCCMATCH after 1st byte
           * of payload is received and ble_phy_rx_start_isr() will fail. In this
           * case we should not clear DISABLED irq mask so it will be handled as
           * regular radio disabled event below. In other case radio was disabled
           * on purpose and there's nothing more to handle so we can clear mask.
           */
          if (ble_phy_rx_start_isr()) {
              irq_en &= ~RADIO_INTENCLR_DISABLED_Msk;
          }
      }
  
      /* Handle disabled event. This is enabled for both TX and RX. On RX, we
       * need to check phy_rx_started flag to make sure we actually were receiving
       * a PDU, otherwise this is due to wfr.
       */
      if ((irq_en & RADIO_INTENCLR_DISABLED_Msk) &&
          NRF_RADIO_NS->EVENTS_DISABLED) {
          BLE_LL_ASSERT(NRF_RADIO_NS->EVENTS_END ||
                        ((g_ble_phy_data.phy_state == BLE_PHY_STATE_RX) &&
                         !g_ble_phy_data.phy_rx_started));
          NRF_RADIO_NS->EVENTS_END = 0;
          NRF_RADIO_NS->EVENTS_DISABLED = 0;
          NRF_RADIO_NS->INTENCLR = RADIO_INTENCLR_DISABLED_Msk;
  
          switch (g_ble_phy_data.phy_state) {
              case BLE_PHY_STATE_RX:
                  ble_phy_plna_disable_lna();
                  if (g_ble_phy_data.phy_rx_started) {
                      ble_phy_rx_end_isr();
                  } else {
                      ble_ll_wfr_timer_exp(NULL);
                  }
                  break;
              case BLE_PHY_STATE_TX:
                  ble_phy_plna_disable_pa();
                  ble_phy_tx_end_isr();
                  break;
              default:
                  BLE_LL_ASSERT(0);
          }
      }
  
      g_ble_phy_data.phy_transition_late = 0;
      /* Count # of interrupts */
      STATS_INC(ble_phy_stats, phy_isrs);
  
      os_trace_isr_exit();
  }
  
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_TXRXEN_READY_PIN) >= 0 || \
      MYNEWT_VAL(BLE_PHY_DBG_TIME_ADDRESS_END_PIN) >= 0 || \
      MYNEWT_VAL(BLE_PHY_DBG_TIME_WFR_PIN) >= 0 || \
      MYNEWT_VAL(BLE_LL_PA) || \
      MYNEWT_VAL(BLE_LL_LNA)
  static int
  ble_phy_gpiote_configure(int pin)
  {
      NRF_GPIO_Type *port;
  
      g_ble_phy_gpiote_idx--;
  
      port = pin > 31 ? NRF_P1_NS : NRF_P0_NS;
      pin &= 0x1f;
  
      /* Configure GPIO directly to avoid dependency to hal_gpio (for porting) */
      port->DIRSET = (1 << pin);
      port->OUTCLR = (1 << pin);
  
      NRF_GPIOTE_NS->CONFIG[g_ble_phy_gpiote_idx] =
              (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
              ((pin & 0x1F) << GPIOTE_CONFIG_PSEL_Pos) |
              ((port == NRF_P1_NS) << GPIOTE_CONFIG_PORT_Pos);
  
      BLE_LL_ASSERT(g_ble_phy_gpiote_idx >= 0);
  
      return g_ble_phy_gpiote_idx;
  }
  #endif
  
  static void
  ble_phy_dbg_time_setup(void)
  {
      int idx __attribute__((unused)) = 8;
  
      /*
       * We setup GPIOTE starting from last configuration index to minimize risk
       * of conflict with GPIO setup via hal. It's not great solution, but since
       * this is just debugging code we can live with this.
       */
  
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_TXRXEN_READY_PIN) >= 0
      idx = ble_phy_gpiote_configure(MYNEWT_VAL(BLE_PHY_DBG_TIME_TXRXEN_READY_PIN));
      phy_dbg_txrxen_ready_idx = idx;
  
      NRF_GPIOTE_NS->SUBSCRIBE_SET[idx] = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_0);
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[idx] = DPPI_CH_SUB(RADIO_EVENTS_READY);
  
      /* Publish RADIO->EVENTS_READY */
      NRF_RADIO_NS->PUBLISH_READY = DPPI_CH_PUB(RADIO_EVENTS_READY);
      NRF_DPPIC_NS->CHENSET = DPPI_CH_MASK(RADIO_EVENTS_READY);
  #endif
  
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_ADDRESS_END_PIN) >= 0
      idx = ble_phy_gpiote_configure(MYNEWT_VAL(BLE_PHY_DBG_TIME_ADDRESS_END_PIN));
      phy_dbg_address_end_idx = idx;
  
      NRF_GPIOTE_NS->SUBSCRIBE_SET[idx] = DPPI_CH_SUB(RADIO_EVENTS_ADDRESS);
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[idx] = DPPI_CH_SUB(RADIO_EVENTS_END);
  #endif
  
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_WFR_PIN) >= 0
      idx = ble_phy_gpiote_configure(MYNEWT_VAL(BLE_PHY_DBG_TIME_WFR_PIN));
      phy_dbg_wfr_idx = idx;
  
      NRF_GPIOTE_NS->SUBSCRIBE_SET[idx] = DPPI_CH_SUB(RADIO_EVENTS_RXREADY);
  
      /* TODO figure out how (if?) to subscribe task to multiple DPPI channels
       * Currently only last one is working. Also using multiple GPIOTE for same
       * PIN doesn't work...
       */
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[idx] = DPPI_CH_SUB(RADIO_EVENTS_DISABLED);
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[idx] = DPPI_CH_SUB(RADIO_EVENTS_ADDRESS);
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[idx] = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_3);
  
      /* Publish RADIO->EVENTS_RXREADY */
      NRF_RADIO_NS->PUBLISH_RXREADY = DPPI_CH_PUB(RADIO_EVENTS_READY);
      NRF_DPPIC_NS->CHENSET = DPPI_CH_MASK(RADIO_EVENTS_RXREADY);
  
      /* Publish RADIO->EVENTS_DISABLED */
      NRF_RADIO_NS->PUBLISH_DISABLED = DPPI_CH_PUB(RADIO_EVENTS_DISABLED);
      NRF_DPPIC_NS->CHENSET = DPPI_CH_MASK(RADIO_EVENTS_DISABLED);
  #endif
  }
  
  int
  ble_phy_init(void)
  {
      int rc;
  
      g_ble_phy_gpiote_idx = 8;
  
      /* Default phy to use is 1M */
      g_ble_phy_data.phy_cur_phy_mode = BLE_PHY_MODE_1M;
      g_ble_phy_data.phy_tx_phy_mode = BLE_PHY_MODE_1M;
      g_ble_phy_data.phy_rx_phy_mode = BLE_PHY_MODE_1M;
  
      g_ble_phy_data.rx_pwr_compensation = 0;
  
      /* Set phy channel to an invalid channel so first set channel works */
      g_ble_phy_data.phy_chan = BLE_PHY_NUM_CHANS;
  
      /* Toggle peripheral power to reset (just in case) */
      NRF_RADIO_NS->POWER = 0;
      NRF_RADIO_NS->POWER = 1;
  
      /* Errata 16 - RADIO: POWER register is not functional
       * Workaround: Reset all RADIO registers in firmware.
       */
      NRF_RADIO_NS->SUBSCRIBE_TXEN = 0;
      NRF_RADIO_NS->SUBSCRIBE_RXEN = 0;
      NRF_RADIO_NS->SUBSCRIBE_DISABLE = 0;
  
      /* Disable all interrupts */
      NRF_RADIO_NS->INTENCLR = NRF_RADIO_IRQ_MASK_ALL;
  
      /* Set configuration registers */
      NRF_RADIO_NS->MODE = RADIO_MODE_MODE_Ble_1Mbit;
      NRF_RADIO_NS->PCNF0 = NRF_PCNF0;
  
      /* XXX: should maxlen be 251 for encryption? */
      NRF_RADIO_NS->PCNF1 = NRF_MAXLEN |
                            (RADIO_PCNF1_ENDIAN_Little <<  RADIO_PCNF1_ENDIAN_Pos) |
                            (NRF_BALEN << RADIO_PCNF1_BALEN_Pos) |
                            RADIO_PCNF1_WHITEEN_Msk;
  
      /* Enable radio fast ramp-up */
      NRF_RADIO_NS->MODECNF0 |= (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos) & RADIO_MODECNF0_RU_Msk;
  
      /* Set logical address 1 for TX and RX */
      NRF_RADIO_NS->TXADDRESS = 0;
      NRF_RADIO_NS->RXADDRESSES = (1 << 0);
  
      /* Configure the CRC registers */
      NRF_RADIO_NS->CRCCNF = (RADIO_CRCCNF_SKIPADDR_Skip << RADIO_CRCCNF_SKIPADDR_Pos) | RADIO_CRCCNF_LEN_Three;
  
      /* Configure BLE poly */
      NRF_RADIO_NS->CRCPOLY = 0x0000065B;
  
      /* Configure IFS */
      NRF_RADIO_NS->TIFS = BLE_LL_IFS;
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
      NRF_CCM_NS->INTENCLR = 0xffffffff;
      NRF_CCM_NS->SHORTS = CCM_SHORTS_ENDKSGEN_CRYPT_Msk;
      NRF_CCM_NS->EVENTS_ERROR = 0;
      memset(nrf_encrypt_scratchpad, 0, sizeof(nrf_encrypt_scratchpad));
  #endif
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
      g_ble_phy_data.phy_aar_scratch = 0;
      NRF_AAR_NS->IRKPTR = (uint32_t)&g_nrf_irk_list[0];
      NRF_AAR_NS->INTENCLR = 0xffffffff;
      NRF_AAR_NS->EVENTS_END = 0;
      NRF_AAR_NS->EVENTS_RESOLVED = 0;
      NRF_AAR_NS->EVENTS_NOTRESOLVED = 0;
      NRF_AAR_NS->NIRK = 0;
  #endif
  
      /* TIMER0 setup for PHY when using RTC */
      NRF_TIMER0_NS->TASKS_STOP = 1;
      NRF_TIMER0_NS->TASKS_SHUTDOWN = 1;
      NRF_TIMER0_NS->BITMODE = 3;    /* 32-bit timer */
      NRF_TIMER0_NS->MODE = 0;       /* Timer mode */
      NRF_TIMER0_NS->PRESCALER = 4;  /* gives us 1 MHz */
  
      /* Publish events */
      NRF_TIMER0_NS->PUBLISH_COMPARE[0] = DPPI_CH_PUB(TIMER0_EVENTS_COMPARE_0);
      NRF_TIMER0_NS->PUBLISH_COMPARE[3] = DPPI_CH_PUB(TIMER0_EVENTS_COMPARE_3);
      NRF_RADIO_NS->PUBLISH_END = DPPI_CH_PUB(RADIO_EVENTS_END);
      NRF_RADIO_NS->PUBLISH_BCMATCH = DPPI_CH_PUB(RADIO_EVENTS_BCMATCH);
      NRF_RADIO_NS->PUBLISH_ADDRESS = DPPI_CH_PUB(RADIO_EVENTS_ADDRESS);
      NRF_RTC0_NS->PUBLISH_COMPARE[0] = DPPI_CH_PUB(RTC0_EVENTS_COMPARE_0);
  
      /* Enable channels we publish on */
      NRF_DPPIC_NS->CHENSET = DPPI_CH_ENABLE_ALL;
  
      /* Captures tx/rx start in timer0 cc 1 and tx/rx end in timer0 cc 2 */
      NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[1] = DPPI_CH_SUB(RADIO_EVENTS_ADDRESS);
      NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[2] = DPPI_CH_SUB(RADIO_EVENTS_END);
  
  #if MYNEWT_VAL(BLE_LL_PA) || MYNEWT_VAL(BLE_LL_LNA)
      /* We keep both channels enabled and CLR task subscribed all the time. It's
       * enough to just (un)subscribe SET task when needed.
       * TODO: figure out if this affects power consumption
       */
  
      /* Publish RADIO->EVENTS_READY */
      NRF_RADIO_NS->PUBLISH_READY = DPPI_CH_PUB(RADIO_EVENTS_READY);
      NRF_DPPIC_NS->CHENSET = DPPI_CH_MASK(RADIO_EVENTS_READY);
      /* Publish RADIO->EVENTS_DISABLED */
      NRF_RADIO_NS->PUBLISH_DISABLED = DPPI_CH_PUB(RADIO_EVENTS_DISABLED);
      NRF_DPPIC_NS->CHENSET = DPPI_CH_MASK(RADIO_EVENTS_DISABLED);
  
  #if PLNA_SINGLE_GPIO
      plna_idx = ble_phy_gpiote_configure(MYNEWT_VAL(BLE_LL_PA_GPIO));
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[plna_idx] = DPPI_CH_SUB(RADIO_EVENTS_DISABLED);
      NRF_GPIOTE_NS->TASKS_CLR[plna_idx] = 1;
  #else
  #if MYNEWT_VAL(BLE_LL_PA)
      plna_pa_idx = ble_phy_gpiote_configure(MYNEWT_VAL(BLE_LL_PA_GPIO));
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_pa_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[plna_pa_idx] = DPPI_CH_SUB(RADIO_EVENTS_DISABLED);
      NRF_GPIOTE_NS->TASKS_CLR[plna_pa_idx] = 1;
  #endif
  #if MYNEWT_VAL(BLE_LL_LNA)
      plna_lna_idx = ble_phy_gpiote_configure(MYNEWT_VAL(BLE_LL_LNA_GPIO));
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_lna_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
      NRF_GPIOTE_NS->SUBSCRIBE_CLR[plna_lna_idx] = DPPI_CH_SUB(RADIO_EVENTS_DISABLED);
      NRF_GPIOTE_NS->TASKS_CLR[plna_lna_idx] = 1;
  #endif
  #endif
  
      NRF_DPPIC_NS->CHENSET = DPPI_CH_MASK_PLNA;
  #endif
  
      /* Set isr in vector table and enable interrupt */
  #ifndef RIOT_VERSION
      NVIC_SetPriority(RADIO_IRQn, 0);
  #endif
  #if MYNEWT
      NVIC_SetVector(RADIO_IRQn, (uint32_t)ble_phy_isr);
  #else
      ble_npl_hw_set_isr(RADIO_IRQn, ble_phy_isr);
  #endif
      NVIC_EnableIRQ(RADIO_IRQn);
  
      /* Register phy statistics */
      if (!g_ble_phy_data.phy_stats_initialized) {
          rc = stats_init_and_reg(STATS_HDR(ble_phy_stats),
                                  STATS_SIZE_INIT_PARMS(ble_phy_stats,
                                                        STATS_SIZE_32),
                                  STATS_NAME_INIT_PARMS(ble_phy_stats),
                                  "ble_phy");
          assert(rc == 0);
  
          g_ble_phy_data.phy_stats_initialized = 1;
      }
  
      ble_phy_dbg_time_setup();
  
      return 0;
  }
  
  int
  ble_phy_rx(void)
  {
      /*
       * Check radio state.
       *
       * In case radio is now disabling we'll wait for it to finish, but if for
       * any reason it's just in idle state we proceed with RX as usual since
       * nRF52 radio can ramp-up from idle state as well.
       *
       * Note that TX and RX states values are the same except for 3rd bit so we
       * can make a shortcut here when checking for idle state.
       */
      nrf_wait_disabled();
      if ((NRF_RADIO_NS->STATE != RADIO_STATE_STATE_Disabled) &&
          ((NRF_RADIO_NS->STATE & 0x07) != RADIO_STATE_STATE_RxIdle)) {
          ble_phy_disable();
          STATS_INC(ble_phy_stats, radio_state_errs);
          return BLE_PHY_ERR_RADIO_STATE;
      }
  
      /* Make sure all interrupts are disabled */
      NRF_RADIO_NS->INTENCLR = NRF_RADIO_IRQ_MASK_ALL;
  
      /* Clear events prior to enabling receive */
      NRF_RADIO_NS->EVENTS_END = 0;
      NRF_RADIO_NS->EVENTS_DISABLED = 0;
  
      /* Setup for rx */
      ble_phy_rx_xcvr_setup();
  
      /* task to start RX should be subscribed here  */
      assert(NRF_RADIO_NS->SUBSCRIBE_RXEN & DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_0));
  
      return 0;
  }
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
  void
  ble_phy_encrypt_enable(uint64_t pkt_counter, uint8_t *iv, uint8_t *key,
                         uint8_t is_central)
  {
      memcpy(nrf_ccm_data.key, key, 16);
      nrf_ccm_data.pkt_counter = pkt_counter;
      memcpy(nrf_ccm_data.iv, iv, 8);
      nrf_ccm_data.dir_bit = is_central;
      g_ble_phy_data.phy_encrypted = 1;
      /* Enable the module (AAR cannot be on while CCM on) */
      NRF_AAR_NS->ENABLE = AAR_ENABLE_ENABLE_Disabled;
      NRF_CCM_NS->ENABLE = CCM_ENABLE_ENABLE_Enabled;
  }
  
  void
  ble_phy_encrypt_set_pkt_cntr(uint64_t pkt_counter, int dir)
  {
      nrf_ccm_data.pkt_counter = pkt_counter;
      nrf_ccm_data.dir_bit = dir;
  }
  
  void
  ble_phy_encrypt_disable(void)
  {
      NRF_CCM_NS->SUBSCRIBE_CRYPT = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
      NRF_CCM_NS->TASKS_STOP = 1;
      NRF_CCM_NS->EVENTS_ERROR = 0;
      NRF_CCM_NS->ENABLE = CCM_ENABLE_ENABLE_Disabled;
  
      g_ble_phy_data.phy_encrypted = 0;
  }
  #endif
  
  void
  ble_phy_set_txend_cb(ble_phy_tx_end_func txend_cb, void *arg)
  {
      /* Set transmit end callback and arg */
      g_ble_phy_data.txend_cb = txend_cb;
      g_ble_phy_data.txend_arg = arg;
  }
  
  int
  ble_phy_tx_set_start_time(uint32_t cputime, uint8_t rem_usecs)
  {
      int rc;
  
      ble_phy_trace_u32x2(BLE_PHY_TRACE_ID_START_TX, cputime, rem_usecs);
  
  #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
      ble_phy_mode_apply(g_ble_phy_data.phy_tx_phy_mode);
  #endif
  
      /* XXX: This should not be necessary, but paranoia is good! */
      /* Clear timer0 compare to RXEN since we are transmitting */
      NRF_RADIO_NS->SUBSCRIBE_RXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
  
      if (ble_phy_set_start_time(cputime, rem_usecs, true) != 0) {
          STATS_INC(ble_phy_stats, tx_late);
          ble_phy_disable();
          rc = BLE_PHY_ERR_TX_LATE;
      } else {
          /* Enable PPI to automatically start TXEN */
          NRF_RADIO_NS->SUBSCRIBE_TXEN = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_0);
          rc = 0;
  
          ble_phy_plna_enable_pa();
      }
      return rc;
  }
  
  int
  ble_phy_rx_set_start_time(uint32_t cputime, uint8_t rem_usecs)
  {
      bool late = false;
      int rc = 0;
  
      ble_phy_trace_u32x2(BLE_PHY_TRACE_ID_START_RX, cputime, rem_usecs);
  
  #if (BLE_LL_BT5_PHY_SUPPORTED == 1)
      ble_phy_mode_apply(g_ble_phy_data.phy_rx_phy_mode);
  #endif
  
      /* XXX: This should not be necessary, but paranoia is good! */
      /* Clear timer0 compare to TXEN since we are transmitting */
      NRF_RADIO_NS->SUBSCRIBE_TXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
  
      if (ble_phy_set_start_time(cputime, rem_usecs, false) != 0) {
          STATS_INC(ble_phy_stats, rx_late);
  
          /* We're late so let's just try to start RX as soon as possible */
          ble_phy_set_start_now();
  
          late = true;
      }
  
      /* Enable PPI to automatically start RXEN */
      NRF_RADIO_NS->SUBSCRIBE_RXEN = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_0);
  
      ble_phy_plna_enable_lna();
  
      /* Start rx */
      rc = ble_phy_rx();
  
      /*
       * If we enabled receiver but were late, let's return proper error code so
       * caller can handle this.
       */
      if (!rc && late) {
          rc = BLE_PHY_ERR_RX_LATE;
      }
  
      return rc;
  }
  
  int
  ble_phy_tx(ble_phy_tx_pducb_t pducb, void *pducb_arg, uint8_t end_trans)
  {
      int rc;
      uint8_t *dptr;
      uint8_t *pktptr;
      uint8_t payload_len;
      uint8_t hdr_byte;
      uint32_t state;
      uint32_t shortcuts;
  
      if (g_ble_phy_data.phy_transition_late) {
          ble_phy_disable();
          STATS_INC(ble_phy_stats, tx_late);
          return BLE_PHY_ERR_TX_LATE;
      }
  
      /*
       * This check is to make sure that the radio is not in a state where
       * it is moving to disabled state. If so, let it get there.
       */
      nrf_wait_disabled();
  
      /*
       * XXX: Although we may not have to do this here, I clear all the PPI
       * that should not be used when transmitting. Some of them are only enabled
       * if encryption and/or privacy is on, but I dont care. Better to be
       * paranoid, and if you are going to clear one, might as well clear them
       * all.
       */
      NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[3] = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
      NRF_RADIO_NS->SUBSCRIBE_DISABLE = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_3);
      NRF_AAR_NS->SUBSCRIBE_START = DPPI_CH_UNSUB(RADIO_EVENTS_BCMATCH);
      NRF_CCM_NS->SUBSCRIBE_CRYPT = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
      if (g_ble_phy_data.phy_encrypted) {
          dptr = (uint8_t *)&g_ble_phy_enc_buf[0];
          pktptr = (uint8_t *)&g_ble_phy_tx_buf[0];
          NRF_CCM_NS->SHORTS = CCM_SHORTS_ENDKSGEN_CRYPT_Msk;
          NRF_CCM_NS->INPTR = (uint32_t)dptr;
          NRF_CCM_NS->OUTPTR = (uint32_t)pktptr;
          NRF_CCM_NS->SCRATCHPTR = (uint32_t)&nrf_encrypt_scratchpad[0];
          NRF_CCM_NS->EVENTS_ERROR = 0;
          NRF_CCM_NS->MODE = CCM_MODE_LENGTH_Msk | ble_phy_get_ccm_datarate();
          NRF_CCM_NS->CNFPTR = (uint32_t)&nrf_ccm_data;
      } else {
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
          NRF_AAR_NS->IRKPTR = (uint32_t)&g_nrf_irk_list[0];
  #endif
          dptr = (uint8_t *)&g_ble_phy_tx_buf[0];
          pktptr = dptr;
      }
  #else
      dptr = (uint8_t *)&g_ble_phy_tx_buf[0];
      pktptr = dptr;
  #endif
  
      /* Set PDU payload */
      payload_len = pducb(&dptr[3], pducb_arg, &hdr_byte);
  
      /* RAM representation has S0, LENGTH and S1 fields. (3 bytes) */
      dptr[0] = hdr_byte;
      dptr[1] = payload_len;
      dptr[2] = 0;
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
      /* Start key-stream generation and encryption (via short) */
      if (g_ble_phy_data.phy_encrypted) {
          NRF_CCM_NS->TASKS_KSGEN = 1;
      }
  #endif
  
      NRF_RADIO_NS->PACKETPTR = (uint32_t)pktptr;
  
      /* Clear the ready, end and disabled events */
      NRF_RADIO_NS->EVENTS_READY = 0;
      NRF_RADIO_NS->EVENTS_END = 0;
      NRF_RADIO_NS->EVENTS_DISABLED = 0;
  
      /* Enable shortcuts for transmit start/end. */
      shortcuts = RADIO_SHORTS_END_DISABLE_Msk | RADIO_SHORTS_READY_START_Msk;
      NRF_RADIO_NS->SHORTS = shortcuts;
      NRF_RADIO_NS->INTENSET = RADIO_INTENSET_DISABLED_Msk;
  
      /* Set the PHY transition */
      g_ble_phy_data.phy_transition = end_trans;
  
      /* Set transmitted payload length */
      g_ble_phy_data.phy_tx_pyld_len = payload_len;
  
      /* If we already started transmitting, abort it! */
      state = NRF_RADIO_NS->STATE;
      if (state != RADIO_STATE_STATE_Tx) {
          /* Set phy state to transmitting and count packet statistics */
          g_ble_phy_data.phy_state = BLE_PHY_STATE_TX;
          STATS_INC(ble_phy_stats, tx_good);
          STATS_INCN(ble_phy_stats, tx_bytes, payload_len + BLE_LL_PDU_HDR_LEN);
          rc = BLE_ERR_SUCCESS;
      } else {
          ble_phy_disable();
          STATS_INC(ble_phy_stats, tx_late);
          rc = BLE_PHY_ERR_RADIO_STATE;
      }
  
      return rc;
  }
  
  int
  ble_phy_txpwr_set(int dbm)
  {
      /* "Rail" power level if outside supported range */
      dbm = ble_phy_txpower_round(dbm);
  
      NRF_RADIO_NS->TXPOWER = dbm;
      g_ble_phy_data.phy_txpwr_dbm = dbm;
  
      return 0;
  }
  
  int
  ble_phy_txpower_round(int dbm)
  {
      /* "Rail" power level if outside supported range */
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_0dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_0dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg1dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg1dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg2dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg2dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg3dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg4dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg4dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg4dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg5dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg5dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg6dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg6dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg7dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg7dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg8dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg8dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg12dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg12dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg16dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg16dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg20dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg20dBm;
      }
  
      if (dbm >= (int8_t)RADIO_TXPOWER_TXPOWER_Neg40dBm) {
          return (int8_t)RADIO_TXPOWER_TXPOWER_Neg40dBm;
      }
  
      return (int8_t)RADIO_TXPOWER_TXPOWER_Neg40dBm;
  }
  
  static int
  ble_phy_set_access_addr(uint32_t access_addr)
  {
      NRF_RADIO_NS->BASE0 = (access_addr << 8);
      NRF_RADIO_NS->PREFIX0 = (NRF_RADIO_NS->PREFIX0 & 0xFFFFFF00) | (access_addr >> 24);
  
      g_ble_phy_data.phy_access_address = access_addr;
  
      return 0;
  }
  
  int
  ble_phy_txpwr_get(void)
  {
      return g_ble_phy_data.phy_txpwr_dbm;
  }
  
  void
  ble_phy_set_rx_pwr_compensation(int8_t compensation)
  {
      g_ble_phy_data.rx_pwr_compensation = compensation;
  }
  
  int
  ble_phy_setchan(uint8_t chan, uint32_t access_addr, uint32_t crcinit)
  {
      assert(chan < BLE_PHY_NUM_CHANS);
  
      /* Check for valid channel range */
      if (chan >= BLE_PHY_NUM_CHANS) {
          return BLE_PHY_ERR_INV_PARAM;
      }
  
      /* Set current access address */
      ble_phy_set_access_addr(access_addr);
  
      /* Configure crcinit */
      NRF_RADIO_NS->CRCINIT = crcinit;
  
      /* Set the frequency and the data whitening initial value */
      g_ble_phy_data.phy_chan = chan;
      NRF_RADIO_NS->FREQUENCY = g_ble_phy_chan_freq[chan];
      NRF_RADIO_NS->DATAWHITEIV = chan;
  
      return 0;
  }
  
  /**
   * Stop the timer used to count microseconds when using RTC for cputime
   */
  static void
  ble_phy_stop_usec_timer(void)
  {
      NRF_TIMER0_NS->TASKS_STOP = 1;
      NRF_TIMER0_NS->TASKS_SHUTDOWN = 1;
      NRF_RTC0_NS->EVTENCLR = RTC_EVTENSET_COMPARE0_Msk;
  }
  
  /**
   * ble phy disable irq and ppi
   *
   * This routine is to be called when reception was stopped due to either a
   * wait for response timeout or a packet being received and the phy is to be
   * restarted in receive mode. Generally, the disable routine is called to stop
   * the phy.
   */
  static void
  ble_phy_disable_irq_and_ppi(void)
  {
      NRF_RADIO_NS->INTENCLR = NRF_RADIO_IRQ_MASK_ALL;
      NRF_RADIO_NS->SHORTS = 0;
      NRF_RADIO_NS->TASKS_DISABLE = 1;
  
      NRF_TIMER0_NS->SUBSCRIBE_START = DPPI_CH_UNSUB(RTC0_EVENTS_COMPARE_0);
      NRF_TIMER0_NS->SUBSCRIBE_CAPTURE[3] = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
      NRF_RADIO_NS->SUBSCRIBE_DISABLE = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_3);
      NRF_RADIO_NS->SUBSCRIBE_TXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
      NRF_RADIO_NS->SUBSCRIBE_RXEN = DPPI_CH_UNSUB(TIMER0_EVENTS_COMPARE_0);
      NRF_AAR_NS->SUBSCRIBE_START = DPPI_CH_UNSUB(RADIO_EVENTS_BCMATCH);
      NRF_CCM_NS->SUBSCRIBE_CRYPT = DPPI_CH_UNSUB(RADIO_EVENTS_ADDRESS);
  
  #if PLNA_SINGLE_GPIO
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
  #else
  #if MYNEWT_VAL(BLE_LL_PA)
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_pa_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
  #endif
  #if MYNEWT_VAL(BLE_LL_LNA)
      NRF_GPIOTE_NS->SUBSCRIBE_SET[plna_lna_idx] = DPPI_CH_UNSUB(RADIO_EVENTS_READY);
  #endif
  #endif
  
      NVIC_ClearPendingIRQ(RADIO_IRQn);
      g_ble_phy_data.phy_state = BLE_PHY_STATE_IDLE;
  }
  
  void
  ble_phy_restart_rx(void)
  {
      ble_phy_stop_usec_timer();
      ble_phy_disable_irq_and_ppi();
  
      ble_phy_set_start_now();
      /* Enable PPI to automatically start RXEN */
      NRF_RADIO_NS->SUBSCRIBE_RXEN = DPPI_CH_SUB(TIMER0_EVENTS_COMPARE_0);
  
      ble_phy_rx();
  }
  
  static void
  ble_phy_dbg_clear_pins(void)
  {
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_TXRXEN_READY_PIN) >= 0
      NRF_GPIOTE_NS->TASKS_CLR[phy_dbg_txrxen_ready_idx] = 1;
  #endif
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_ADDRESS_END_PIN) >= 0
      NRF_GPIOTE_NS->TASKS_CLR[phy_dbg_address_end_idx] = 1;
  #endif
  #if MYNEWT_VAL(BLE_PHY_DBG_TIME_WFR_PIN) >= 0
      NRF_GPIOTE_NS->TASKS_CLR[phy_dbg_wfr_idx] = 1;
  #endif
  }
  
  void
  ble_phy_disable(void)
  {
      ble_phy_trace_void(BLE_PHY_TRACE_ID_DISABLE);
  
      ble_phy_stop_usec_timer();
      ble_phy_disable_irq_and_ppi();
  
      ble_phy_dbg_clear_pins();
  }
  
  uint32_t
  ble_phy_access_addr_get(void)
  {
      return g_ble_phy_data.phy_access_address;
  }
  
  int
  ble_phy_state_get(void)
  {
      return g_ble_phy_data.phy_state;
  }
  
  int
  ble_phy_rx_started(void)
  {
      return g_ble_phy_data.phy_rx_started;
  }
  
  uint8_t
  ble_phy_xcvr_state_get(void)
  {
      uint32_t state;
      state = NRF_RADIO_NS->STATE;
      return (uint8_t)state;
  }
  
  uint8_t
  ble_phy_max_data_pdu_pyld(void)
  {
      return BLE_LL_DATA_PDU_MAX_PYLD;
  }
  
  #if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
  void
  ble_phy_resolv_list_enable(void)
  {
      NRF_AAR_NS->NIRK = (uint32_t)g_nrf_num_irks;
      g_ble_phy_data.phy_privacy = 1;
  }
  
  void
  ble_phy_resolv_list_disable(void)
  {
      g_ble_phy_data.phy_privacy = 0;
  }
  #endif
  
  #if MYNEWT_VAL(BLE_LL_DTM)
  void
  ble_phy_enable_dtm(void)
  {
      /* When DTM is enabled we need to disable whitening as per
       * Bluetooth v5.0 Vol 6. Part F. 4.1.1
       */
      NRF_RADIO_NS->PCNF1 &= ~RADIO_PCNF1_WHITEEN_Msk;
  }
  
  void
  ble_phy_disable_dtm(void)
  {
      /* Enable whitening */
      NRF_RADIO_NS->PCNF1 |= RADIO_PCNF1_WHITEEN_Msk;
  }
  #endif
  
  void
  ble_phy_rfclk_enable(void)
  {
  #if MYNEWT
      nrf5340_net_clock_hfxo_request();
  #else
      NRF_CLOCK_NS->TASKS_HFCLKSTART = 1;
  #endif
  }
  
  void
  ble_phy_rfclk_disable(void)
  {
  #if MYNEWT
      nrf5340_net_clock_hfxo_release();
  #else
      NRF_CLOCK_NS->TASKS_HFCLKSTOP = 1;
  #endif
  }
  