/*
 * 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 "syscfg/syscfg.h"
#if !MYNEWT_VAL(BLE_PHY_DEBUG_DSER)
#define MCU_DIAG_SER_DISABLE
#endif

#include <assert.h>
#include <stdint.h>
#include "nimble/ble.h"
#include "mcu/mcu.h"
#include "mcu/cmac_timer.h"
#include <ipc_cmac/shm.h>
#include "controller/ble_phy.h"
#include "controller/ble_ll.h"
#include "stats/stats.h"
#include "CMAC.h"
#include "ble_hw_priv.h"
#include "ble_rf_priv.h"

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
#error LE Coded PHY cannot be enabled on DA1469x
#endif

#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif

/* 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_late_sched)
    STATS_SECT_ENTRY(tx_late_frame)
    STATS_SECT_ENTRY(tx_late_field)
    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_late_sched)
    STATS_NAME(ble_phy_stats, tx_late_frame)
    STATS_NAME(ble_phy_stats, tx_late_field)
    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_CODED_PHY)
#error LE Coded PHY is not supported
#endif

/* An easy way to get and set bit field value in CMAC registers */
#define CMAC_SETREGF(_reg, _field, _val)                                    \
    CMAC->_reg = (CMAC->_reg & ~(CMAC_ ## _reg ## _ ## _field ## _Msk)) |   \
                 ((_val) << (CMAC_ ## _reg ## _ ## _field ## _Pos));
#define CMAC_GETREGF(_reg, _field)                                          \
    (CMAC->_reg & (CMAC_ ## _reg ## _ ## _field ## _Msk)) >>                \
    (CMAC_ ## _reg ## _ ## _field ## _Pos)

/* Definitions for fields queue */
#define FIELD_DATA_REG_DMA_TX(_offset, _len) \
    ((uint32_t)&g_ble_phy_tx_buf[(_offset)] & 0x3ffff) | ((_len) << 20)
#define FIELD_DATA_REG_DMA_RX(_offset, _len) \
    ((uint32_t)&g_ble_phy_rx_buf[(_offset)] & 0x3ffff) | ((_len) << 20)

#if MYNEWT_VAL(BLE_LL_DTM)
#define PHY_WHITENING   (g_ble_phy_data.phy_whitening)
#else
#define PHY_WHITENING   (1)
#endif

#define FIELD_CTRL_REG_TX_PREAMBLE                                  \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |     \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_TX_DATA_SRC_Pos) |      \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos) |            \
    (g_ble_phy_data.phy_mode_pre_len <<                             \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_SIZE_M1_Pos)
#define FIELD_CTRL_REG_TX_ACCESS_ADDR                               \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EXC_ON_EXP_Pos) |       \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |     \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_TX_DATA_SRC_Pos) |      \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos) |            \
    (31 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_SIZE_M1_Pos)
#define FIELD_CTRL_REG_TX_PAYLOAD                                   \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (PHY_WHITENING <<                                               \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |          \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_DMA_MEM_Pos) |       \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos);
#define FIELD_CTRL_REG_TX_ENC_PAYLOAD                               \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |     \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_DMA_CRYPTO_Pos) |    \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos)
#define FIELD_CTRL_REG_TX_MIC                                       \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |     \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_DMA_CRYPTO_Pos) |    \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos)
#define FIELD_CTRL_REG_TX_CRC                                       \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (PHY_WHITENING <<                                               \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |          \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_TX_DATA_SRC_Pos) |      \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_LAST_Pos) |             \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_MSB_FIRST_Pos) |        \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos) |            \
    (23 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_SIZE_M1_Pos)
#define FIELD_CTRL_REG_RX_ACCESS_ADDR \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |     \
    (0 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_TX_DATA_SRC_Pos) |      \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CORR_Pos) |          \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos) |            \
    (31 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_SIZE_M1_Pos)
#define FIELD_CTRL_REG_RX_HEADER \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EXC_ON_EXP_Pos) |       \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (PHY_WHITENING <<                                               \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |          \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_DMA_MEM_Pos) |       \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos)
#define FIELD_CTRL_REG_RX_CRC                                       \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (PHY_WHITENING <<                                               \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |          \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_LAST_Pos) |             \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_MSB_FIRST_Pos) |        \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos) |            \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_DMA_MEM_Pos)
#define FIELD_CTRL_REG_RX_PAYLOAD                                   \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (PHY_WHITENING <<                                               \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |          \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_DMA_MEM_Pos) |       \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos)
#define FIELD_CTRL_REG_RX_PAYLOAD_WITH_EXC \
    FIELD_CTRL_REG_RX_PAYLOAD |                                     \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EXC_ON_EXP_Pos)
#define FIELD_CTRL_REG_RX_ENC_PAYLOAD                               \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_CRC_Pos) |           \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_WHITENING_Pos) |     \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_XL_DMA_CRYPTO_Pos) |    \
    (g_ble_phy_data.phy_mode_evpsym <<                              \
     CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_EVPSYMBOL_LUT_Pos) |         \
    (1 << CMAC_CM_FIELD_PUSH_CTRL_REG_FIELD_VALID_Pos)

/* RF power up/down delays */
#define PHY_DELAY_POWER_DN_RX   (23)
#define PHY_DELAY_POWER_DN_TX   (23)
#define PHY_DELAY_POWER_UP_RX   (90)
#define PHY_DELAY_POWER_UP_TX   (75)
#define PHY_DELAY_TX_RX         ((PHY_DELAY_POWER_DN_TX) + (PHY_DELAY_POWER_UP_RX))
#define PHY_DELAY_RX_TX         ((PHY_DELAY_POWER_DN_RX) + (PHY_DELAY_POWER_UP_TX))

/* RF TX/RX path delays */
static const uint8_t g_ble_phy_path_delay_tx[2] = {
    4, /* 1M = 3.8us */
    0, /* 2M = 0.2us */
};
static const uint8_t g_ble_phy_path_delay_rx[2] = {
    2, /* 1M = 2.2us */
    1, /* 2M = 0.8us */
};

/* Measured and pre-calculated offsets for transitions */
static const uint8_t g_ble_phy_frame_offset_txrx[4] = {
    ((BLE_LL_IFS) - (PHY_DELAY_TX_RX) + (4)), /* 2M/1M */
    ((BLE_LL_IFS) - (PHY_DELAY_TX_RX) + (5)), /* 1M/1M */
    ((BLE_LL_IFS) - (PHY_DELAY_TX_RX) + (4)), /* 2M/2M */
    ((BLE_LL_IFS) - (PHY_DELAY_TX_RX) + (5)), /* 1M/2M */
};
static const uint8_t g_ble_phy_frame_offset_rxtx[4] = {
    ((BLE_LL_IFS) - (PHY_DELAY_RX_TX) - (5)), /* 2M/1M */
    ((BLE_LL_IFS) - (PHY_DELAY_RX_TX) - (6)), /* 1M/1M */
    ((BLE_LL_IFS) - (PHY_DELAY_RX_TX) - (3)), /* 2M/2M */
    ((BLE_LL_IFS) - (PHY_DELAY_RX_TX) - (5)), /* 1M/2M */
};

/* packet start offsets (in usecs) */
static const uint16_t g_ble_phy_mode_pkt_start_off[BLE_PHY_NUM_MODE] = { 376, 40, 24, 376 };

struct ble_phy_data {
    uint8_t phy_state;          /* Current state */
    uint8_t channel;            /* Current PHY channel */
    uint8_t phy_mode_cur;       /* Current PHY mode */
    uint8_t phy_mode_tx;        /* TX PHY mode */
    uint8_t phy_mode_rx;        /* RX PHY mode */
    uint8_t phy_mode_pre_len;   /* Preamble length - 1 */
    uint8_t phy_mode_evpsym;    /* EVPSYMBOL_LUT value for fields */
    uint8_t end_transition;     /* Scheduled transition */
    uint8_t path_delay_tx;
    uint8_t path_delay_rx;
    uint8_t frame_offset_txrx;
    uint8_t frame_offset_rxtx;
    uint8_t phy_rx_started;
    uint8_t phy_encrypted;
    uint8_t phy_privacy;
#if MYNEWT_VAL(BLE_LL_DTM)
    uint8_t phy_whitening;      /* Whitening state (disabled for DTM) */
#endif
    uint32_t access_addr;       /* Current access address */
    uint32_t crc_init;
    uint64_t llt_rx_start;
    struct ble_mbuf_hdr rxhdr;
    ble_phy_tx_end_func txend_cb;
    void *txend_arg;
    uint8_t phy_tx_set;
};

static struct ble_phy_data g_ble_phy_data;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
/* Encryption related variables */
struct ble_phy_encrypt_obj {
    uint8_t key[16];
    uint8_t b0[16];
    uint8_t b1[16];
    uint8_t ai[16];
};

struct ble_phy_encrypt_obj g_ble_phy_encrypt_data;

static void ble_phy_tx_enc_start(void);
static void ble_phy_rx_enc_start(uint8_t len);
#endif

#define SW_MAC_EXC_NONE             (0)
#define SW_MAC_EXC_LL_RX_END        (1)
#define SW_MAC_EXC_TXEND_CB         (2)
#define SW_MAC_EXC_LL_RX_START      (3)
#define SW_MAC_EXC_WFR_TIMER_EXP    (4)

static volatile uint8_t g_sw_mac_exc;

/* Channel index to RF channel mapping */
static const uint8_t g_ble_phy_chan_to_rf[BLE_PHY_NUM_CHANS] = {
     1,  2,  3,  4,  5,  6,  7,  8,  9, 10, /* 0-9 */
    11, 13, 14, 15, 16, 17, 18, 19, 20, 21, /* 10-19 */
    22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 20-29 */
    32, 33, 34, 35, 36, 37, 38,  0, 12, 39, /* 30-39 */
};

__attribute__((aligned(4)))
static uint8_t g_ble_phy_tx_buf[BLE_PHY_MAX_PDU_LEN + 3];
__attribute__((aligned(4)))
static uint8_t g_ble_phy_rx_buf[BLE_PHY_MAX_PDU_LEN + 3];

static void ble_phy_irq_field_tx(void);
static void ble_phy_irq_field_rx(void);
static void ble_phy_irq_frame_tx(void);
static void ble_phy_irq_frame_rx(void);
static bool ble_phy_rx_start_isr(void);
static void ble_phy_rx_setup_fields(void);
static void ble_phy_rx_setup_xcvr(void);
static void ble_phy_mode_apply(uint8_t phy_mode);
static int ble_phy_get_cur_phy(void);

void
FIELD_IRQHandler(void)
{
    MCU_DIAG_SER('E');

    switch (g_ble_phy_data.phy_state) {
    case BLE_PHY_STATE_TX:
        ble_phy_irq_field_tx();
        break;
    case BLE_PHY_STATE_RX:
        ble_phy_irq_field_rx();
        break;
    default:
        STATS_INC(ble_phy_stats, radio_state_errs);
        CMAC->CM_EXC_STAT_REG = 0xfffffffe;
        break;
    }

    MCU_DIAG_SER('e');
}

void
CALLBACK_IRQHandler(void)
{
    MCU_DIAG_SER('C');

    /* XXX: clear these for now. */
    (void)CMAC->CM_BS_SMPL_D_REG;

    /* Clear IRQ*/
    CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV1C_CALLBACK_VALID_CLR_Msk;
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_FIELD_ON_THR_EXP_Msk;

    /*
     * Program next frame for transition to TX. CM_EV_LINKUP_REG register to
     * enable actual transition can be set later, we just need to make sure 2nd
     * frame is already set before current frame is finished - this guarantees
     * that frame for transition will be moved to 1st frame once current frame
     * is popped off the queue.
     */
    CMAC->CM_FRAME_2_REG = CMAC_CM_FRAME_2_REG_FRAME_VALID_Msk |
                           CMAC_CM_FRAME_2_REG_FRAME_TX_Msk |
                           CMAC_CM_FRAME_2_REG_FRAME_EXC_ON_BS_START_Msk |
                           ((g_ble_phy_data.frame_offset_rxtx) <<
                            CMAC_CM_FRAME_2_REG_FRAME_START_OFFSET_Pos);

    /*
     * We just got an access address match so do this as early as possible
     * to save time in the field rx isr.
     */
    ble_phy_rx_start_isr();

    MCU_DIAG_SER('c');
}

void
FRAME_IRQHandler(void)
{
    MCU_DIAG_SER('F');

    switch (g_ble_phy_data.phy_state) {
    case BLE_PHY_STATE_TX:
        ble_phy_irq_frame_tx();
        break;
    case BLE_PHY_STATE_RX:
        ble_phy_irq_frame_rx();
        break;
    default:
        STATS_INC(ble_phy_stats, radio_state_errs);
        CMAC->CM_EXC_STAT_REG = 0xfffffffe;
        break;
    }

    MCU_DIAG_SER('f');
}

void
SW_MAC_IRQHandler(void)
{
    uint8_t exc;
    int rc;

    MCU_DIAG_SER('S');

    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_SW_MAC_Msk;
    assert(g_sw_mac_exc);

    exc = g_sw_mac_exc;
    g_sw_mac_exc = 0;

    MCU_DIAG_SER('0' + exc);

    /* Next SW_MAC handover can now be queued */
    os_arch_cmac_bs_ctrl_irq_unblock();

    switch (exc) {
    case SW_MAC_EXC_TXEND_CB:
        assert(g_ble_phy_data.txend_cb);
        g_ble_phy_data.txend_cb(g_ble_phy_data.txend_arg);
        break;
    case SW_MAC_EXC_WFR_TIMER_EXP:
        ble_ll_wfr_timer_exp(NULL);
        break;
    case SW_MAC_EXC_LL_RX_START:
        /* Call Link Layer receive start function */
        rc = ble_ll_rx_start(&g_ble_phy_rx_buf[0], g_ble_phy_data.channel,
                             &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;

            /* No transition */
            CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_PHY_TO_IDLE_2_NONE_Msk |
                                     CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_NONE_Msk;
        } else if (rc > 0) {
            g_ble_phy_data.phy_rx_started = 1;

            /* Setup transition */
            CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_PHY_TO_IDLE_2_EXC_Msk |
                                     CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_PHY_TO_IDLE_Msk;
        } else {
            /* Disable PHY */
            ble_phy_disable();
            STATS_INC(ble_phy_stats, rx_aborts);
        }
        break;
    case SW_MAC_EXC_LL_RX_END:
        /* Call LL end processing */
        rc = ble_ll_rx_end(&g_ble_phy_rx_buf[0], &g_ble_phy_data.rxhdr);
        if (rc < 0) {
            ble_phy_disable();
        }
        break;
    default:
        assert(0);
        break;
    }

    MCU_DIAG_SER('s');
}

static inline void
ble_phy_sw_mac_handover(uint8_t exc)
{
    assert(!g_sw_mac_exc);

    g_sw_mac_exc = exc;

    CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV1C_SW_MAC_Msk;

    /*
     * We want SW_MAC to be fired just after BS_CTRL interrupt so we block
     * BS_CTRL temporarily and SW_MAC is next in order of interrupts priority.
     */
    os_arch_cmac_bs_ctrl_irq_block();
}

static void
ble_phy_rx_end_isr(void)
{
    struct ble_mbuf_hdr *ble_hdr;

    /* XXX just clear captured timer for now. Handle rx end time */
    (void)CMAC->CM_TS1_REG;

    /* Set RSSI and CRC status flag in header */
    ble_hdr = &g_ble_phy_data.rxhdr;

    /* Count PHY crc errors and valid packets */
    if (CMAC->CM_CRC_REG != 0) {
        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 (g_ble_phy_rx_buf[1] != 0) {
                if (CMAC->CM_CRYPTO_STAT_REG != 0) {
                    ble_hdr->rxinfo.flags |= BLE_MBUF_HDR_F_MIC_FAILURE;
                } else {
                    g_ble_phy_rx_buf[1] = g_ble_phy_rx_buf[1] - 4;
                }
            }
        }
#endif
    }

    ble_phy_sw_mac_handover(SW_MAC_EXC_LL_RX_END);
}

static bool
ble_phy_rx_start_isr(void)
{
    uint64_t llt;
    uint32_t timestamp;
    struct ble_mbuf_hdr *ble_hdr;

    /* 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.channel;
    ble_hdr->rxinfo.handle = 0;
    ble_hdr->rxinfo.phy = ble_phy_get_cur_phy();
    ble_hdr->rxinfo.phy_mode = g_ble_phy_data.phy_mode_rx;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
    ble_hdr->rxinfo.user_data = NULL;
#endif

    /* Read the latched RSSI value */
    ble_hdr->rxinfo.rssi = ble_rf_get_rssi();
#if MYNEWT_VAL(CMAC_DEBUG_DATA_ENABLE)
    g_cmac_shm_debugdata.last_rx_rssi = ble_hdr->rxinfo.rssi;
#endif

    /* Count rx starts */
    STATS_INC(ble_phy_stats, rx_starts);

    /*
     * Calculate packet start time. Note that we have only received the
     * access address at this point but we should have the 1st symbol and
     * thus the timestamp should be set (this is based on looking at the diag
     * signals). For now, lets make sure that the dirty bit is set. The
     * dirty bit means that the timestamp was set since the last time cleared.
     * Note that we need to read the timestamp first to guarantee it was set
     * before reading the LL timer.
     */
    timestamp = CMAC->CM_TS1_REG;
    assert((timestamp & CMAC_CM_TS1_REG_TS1_DIRTY_Msk) != 0);

    llt = cmac_timer_read37();

    /* Captured timestamp has only 11lsb, we use current LL timer value to
     * calculate 37-bit timestamp. To do this we will replace 11 lsb of current
     * LL timer value with captured value, but we need to check if msb of
     * captured timestamp (i.e. 11) is the same as in current LL timer value.
     * If not, it means 10 lsb of LL timer value wrapped around since timestamp
     * was captured and we need to adjust LL timer value. This assumes that
     * LL timer value was read no later than 1024 usecs from timestamp capture,
     * but that's fine since isr should be triggered immediately after capture.
     */
    if (((uint32_t)llt ^ timestamp) & (1 << 10)) {
        llt -= 1024;
    }
    llt &= ~((uint64_t)0x3ff);
    llt |= timestamp & 0x3ff;

    /* Timestamp is captured after access address so we need to adjust rx start
     * time accordingly.
     */
    llt -= g_ble_phy_mode_pkt_start_off[g_ble_phy_data.phy_mode_rx] +
           g_ble_phy_data.path_delay_rx;

    ble_hdr->beg_cputime = llt >> 5;
    ble_hdr->rem_usecs = llt & 0x1f;

    return true;
}

static void
ble_phy_irq_field_tx_exc_bs_start_4this(void)
{
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_BS_START_4THIS_Msk;

    if (g_ble_phy_data.end_transition == BLE_PHY_TRANSITION_TX_RX) {
        /*
         * Setup 2nd frame that will start after current one.
         * -2us offset to adjust for allowed active clock accuracy.
         */
        CMAC->CM_FRAME_2_REG = CMAC_CM_FRAME_2_REG_FRAME_VALID_Msk |
                               (((g_ble_phy_data.frame_offset_txrx) - 2) <<
                                CMAC_CM_FRAME_2_REG_FRAME_START_OFFSET_Pos);

        /* Next frame starts automatically on phy2idle */
        CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_PHY_TO_IDLE_2_EXC_Msk |
                                 CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_PHY_TO_IDLE_Msk;

        ble_phy_wfr_enable(BLE_PHY_WFR_ENABLE_TXRX, g_ble_phy_data.phy_mode_rx, 0);
    } else {
        CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_PHY_TO_IDLE_2_EXC_Msk |
                                 CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_NONE_Msk;
    }

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
    if (g_ble_phy_data.phy_encrypted && (g_ble_phy_tx_buf[1] != 0)) {
        ble_phy_tx_enc_start();
    }
#endif
}

static void
ble_phy_irq_field_tx_exc_field_on_thr_exp(void)
{
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_FIELD_ON_THR_EXP_Msk;
    (void)CMAC->CM_TS1_REG;

    /*  Set up remaining field (CRC) */
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_TX_CRC;
}

static void
ble_phy_irq_field_tx(void)
{
    uint32_t stat;

    stat = CMAC->CM_EXC_STAT_REG;

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_BS_START_4THIS_Msk) {
        MCU_DIAG_SER('6');
        ble_phy_irq_field_tx_exc_bs_start_4this();
    }

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_FIELD_ON_THR_EXP_Msk) {
        MCU_DIAG_SER('7');
        ble_phy_irq_field_tx_exc_field_on_thr_exp();
    }
}

static void
ble_phy_irq_frame_tx_exc_bs_stop(void)
{
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_BS_STOP_Msk;

    /* Clear latched timestamp so we do not have error on next frame */
    (void)CMAC->CM_TS1_REG;

    if (g_ble_phy_data.end_transition == BLE_PHY_TRANSITION_TX_RX) {
#if MYNEWT_VAL(BLE_LL_PHY)
        ble_phy_mode_apply(g_ble_phy_data.phy_mode_rx);
#endif
        ble_phy_rx_setup_fields();
    } else {
        CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_PHY_TO_IDLE_2_EXC_Msk |
                                 CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_NONE_Msk;
    }

    g_ble_phy_data.phy_tx_set = 0;

    if (g_ble_phy_data.txend_cb) {
        ble_phy_sw_mac_handover(SW_MAC_EXC_TXEND_CB);
        return;
    }
}

static void
ble_phy_irq_frame_tx_exc_phy_to_idle_4this(void)
{
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_PHY_TO_IDLE_4THIS_Msk;

    if (g_ble_phy_data.end_transition == BLE_PHY_TRANSITION_TX_RX) {
        ble_phy_rx_setup_xcvr();

        g_ble_phy_data.phy_state = BLE_PHY_STATE_RX;
    } else {
        /*
         * Disable explicitly in case RX-TX was done (we cannot setup for auto
         * disable in such case) */
        ble_rf_stop();

        g_ble_phy_data.phy_state = BLE_PHY_STATE_IDLE;
    }

    g_ble_phy_data.end_transition = BLE_PHY_TRANSITION_NONE;
}

static void
ble_phy_irq_frame_tx(void)
{
    uint32_t stat;

    stat = CMAC->CM_EXC_STAT_REG;

    /*
     * In case of phy2idle this should be first and only exception we handle
     * here. This is because in case of TX-RX transition frame_start will occur
     * at the same as phy2idle so we will have 2 exceptions here. To handle this
     * properly we first need to handle phy2idle in TX state and keep frame_start
     * pending so it will be called again in RX state.
     */
    if (stat & CMAC_CM_EXC_STAT_REG_EXC_PHY_TO_IDLE_4THIS_Msk) {
        MCU_DIAG_SER('6');
        ble_phy_irq_frame_tx_exc_phy_to_idle_4this();
        return;
    }

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_FRAME_START_Msk) {
        MCU_DIAG_SER('7');
        CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_FRAME_START_Msk;
    }

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_BS_STOP_Msk) {
        MCU_DIAG_SER('8');
        ble_phy_irq_frame_tx_exc_bs_stop();
    }
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
static void
ble_phy_field_rx_encrypted(uint32_t len)
{
    if (len) {
        /*
         * An encrypted frame should have a minimum length of 5
         * bytes (at least one for payload and 4 for MIC). If the
         * length is less than 5 this frame is bogus and will most
         * likely fail CRC. We still need to process this frame
         * though as we need to call the handover function with
         * the frame. If this happens we will not bother to
         * run the remaining bytes through the accelerator; just
         * process them like normal and generate (a hopefully
         * incorrect) CRC.
         */
        if (len >= 5) {
            /* Start the crypto accelerator */
            ble_phy_rx_enc_start(len);

            /*
             * We have already processed one byte; process remaining
             * payload and MIC. Note: length contains MIC.
             */
            len -= 2;
            CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4, len);
            CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_ENC_PAYLOAD;

            /* CRC */
            CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4 + len, 3);
            CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_CRC;
        } else {
            /* We have processed one byte so far. Send remaining
               payload bytes to normal rx payload processing */
            len -= 2;
            if (len) {
                CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4, len);
                CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_PAYLOAD;
            }

            CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4 + len, 3);
            CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_CRC;

            /* Clear crypto pre-buffer */
            CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_SW_REQ_PBUF_CLR_Msk;
        }
    } else {
        /* We programmed one byte, so get next two bytes for CRC */
        CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4, 1);
        CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_CRC;
    }
}
#endif

static void
ble_phy_field_rx_unencrypted(uint32_t len)
{
    uint8_t pduhdr;
    uint8_t adva_thr;

    if (len) {
        pduhdr = g_ble_phy_rx_buf[0];
        adva_thr = 0;

        /*
         * Setup interrupt after AdvA to start address resolving if
         * privacy is enabled and TxAdd bit is set.
         */
        if (MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) &&
            g_ble_phy_data.phy_privacy && (pduhdr & 0x40)) {

            /*
             * For legacy advertising AdvA ends at 6th byte.
             * For extended advertising AdvA ends at 8th byte.
             * We already programmed 2 bytes of payload so need
             * to adjust threshold accordingly or just reset it
             * in case there is not enough bytes in PDU to fit AdvA.
             */
            adva_thr = (pduhdr & 0x0f) == 0x07 ? 6 : 4;
            if (len >= adva_thr + 2) {
                CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4, adva_thr);
                CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_PAYLOAD_WITH_EXC;
            } else {
                adva_thr = 0;
            }
        }

        CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4 + adva_thr,
                                                             len - adva_thr);
        CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_PAYLOAD;
    }

    CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(4 + len, 1);
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_CRC;
}

static void
ble_phy_irq_field_rx_exc_field_on_thr_exp(void)
{
    uint32_t len;
    uint32_t smpl;

    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_FIELD_ON_THR_EXP_Msk;

    smpl = CMAC->CM_BS_SMPL_ST_REG;

    if ((smpl & CMAC_CM_BS_SMPL_ST_REG_FIELD_CNT_LATCHED_Msk) == 1) {
        assert(g_ble_phy_data.phy_rx_started == 0);

        /* Clear this */
        (void)CMAC->CM_TS1_REG;

        /* Read length of frame */
        len = CMAC->CM_BS_SMPL_D_REG;
        len = len & 0xFF;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
        if (g_ble_phy_data.phy_encrypted) {
            ble_phy_field_rx_encrypted(len);
        } else {
            ble_phy_field_rx_unencrypted(len);
        }
#else
        ble_phy_field_rx_unencrypted(len);
#endif

        ble_phy_sw_mac_handover(SW_MAC_EXC_LL_RX_START);
    } else if ((smpl & CMAC_CM_BS_SMPL_ST_REG_FIELD_CNT_LATCHED_Msk) == 3) {
        (void)CMAC->CM_BS_SMPL_D_REG;

        assert(g_ble_phy_data.phy_privacy);

        /*
         * Resolve only if RPA is received. AdvA is at different offset
         * in ExtAdv PDU. TxAdd was already checked before programming
         * field threshold.
         */
        if ((g_ble_phy_rx_buf[0] & 0x0f) == 0x07) {
            if ((g_ble_phy_rx_buf[9] & 0xc0) == 0x40) {
                ble_hw_resolv_proc_start(&g_ble_phy_rx_buf[4]);
            }
        } else {
            if ((g_ble_phy_rx_buf[7] & 0xc0) == 0x40) {
                ble_hw_resolv_proc_start(&g_ble_phy_rx_buf[2]);
            }
        }
    } else {
        assert(0);
    }
}

static void
ble_phy_irq_field_rx_exc_stat_reg_exc_corr_timeout(void)
{
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_CORR_TIMEOUT_Msk;
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_BS_STOP_Msk;
    CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_NONE_Msk;

    ble_phy_sw_mac_handover(SW_MAC_EXC_WFR_TIMER_EXP);
}

static void
ble_phy_irq_field_rx(void)
{
    uint32_t stat;

    stat = CMAC->CM_EXC_STAT_REG;

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_FIELD_ON_THR_EXP_Msk) {
        MCU_DIAG_SER('1');
        ble_phy_irq_field_rx_exc_field_on_thr_exp();
    }

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_CORR_TIMEOUT_Msk) {
        MCU_DIAG_SER('2');
        ble_phy_irq_field_rx_exc_stat_reg_exc_corr_timeout();
    }
}

static void
ble_phy_irq_frame_rx_exc_phy_to_idle_4this(void)
{
    uint8_t rf_chan;

    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_PHY_TO_IDLE_4THIS_Msk;

    /* We are here only on transition, so switch to TX */
#if MYNEWT_VAL(BLE_LL_PHY)
    ble_phy_mode_apply(g_ble_phy_data.phy_mode_tx);
#endif
    rf_chan = g_ble_phy_chan_to_rf[g_ble_phy_data.channel];
    ble_rf_setup_tx(rf_chan, g_ble_phy_data.phy_mode_tx);

    g_ble_phy_data.phy_state = BLE_PHY_STATE_TX;

    /* We do not want FIELD/FRAME interrupts until ble_phy_tx() has pushed all
     * fields.
     */
    if (!g_ble_phy_data.phy_tx_set) {
        NVIC_DisableIRQ(FRAME_IRQn);
        NVIC_DisableIRQ(FIELD_IRQn);
    }
}

static void
ble_phy_irq_frame_rx_exc_frame_start(void)
{
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_FRAME_START_Msk;
}

static void
ble_phy_irq_frame_rx_exc_bs_stop(void)
{
    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_BS_STOP_Msk;
    ble_phy_rx_end_isr();
}

static void
ble_phy_irq_frame_rx(void)
{
    uint32_t stat;

    stat = CMAC->CM_EXC_STAT_REG;

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_PHY_TO_IDLE_4THIS_Msk) {
        MCU_DIAG_SER('3');
        ble_phy_irq_frame_rx_exc_phy_to_idle_4this();
        return;
    }

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_FRAME_START_Msk) {
        MCU_DIAG_SER('1');
        ble_phy_irq_frame_rx_exc_frame_start();
    }

    if (stat & CMAC_CM_EXC_STAT_REG_EXC_BS_STOP_Msk) {
        MCU_DIAG_SER('2');
        ble_phy_irq_frame_rx_exc_bs_stop();
    }
}

static void
ble_phy_mode_apply(uint8_t phy_mode)
{
    if (phy_mode == g_ble_phy_data.phy_mode_cur) {
        return;
    }

    switch (phy_mode) {
    case BLE_PHY_MODE_1M:
#if MYNEWT_VAL(BLE_PHY_RF_HP_MODE)
        CMAC_SETREGF(CM_PHY_CTRL2_REG, CORR_CLK_MODE, 3);
#endif
        CMAC_SETREGF(CM_PHY_CTRL2_REG, PHY_MODE, 1);
        g_ble_phy_data.phy_mode_evpsym = 1; /* 1000 ns per symbol */
        g_ble_phy_data.phy_mode_pre_len = 7;
        break;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY)
    case BLE_PHY_MODE_2M:
#if MYNEWT_VAL(BLE_PHY_RF_HP_MODE)
        CMAC_SETREGF(CM_PHY_CTRL2_REG, CORR_CLK_MODE, 2);
#endif
        CMAC_SETREGF(CM_PHY_CTRL2_REG, PHY_MODE, 0);
        g_ble_phy_data.phy_mode_evpsym = 0; /* 500 ns per symbol */
        g_ble_phy_data.phy_mode_pre_len = 15;
        break;
#endif
    default:
        assert(0);
        return;
    }

    g_ble_phy_data.phy_mode_cur = phy_mode;
}

void
ble_phy_mode_set(uint8_t tx_phy_mode, uint8_t rx_phy_mode)
{
    uint8_t txrx;
    uint8_t rxtx;

    g_ble_phy_data.phy_mode_tx = tx_phy_mode;
    g_ble_phy_data.phy_mode_rx = rx_phy_mode;

    g_ble_phy_data.path_delay_tx = g_ble_phy_path_delay_tx[tx_phy_mode - 1];
    g_ble_phy_data.path_delay_rx = g_ble_phy_path_delay_rx[rx_phy_mode - 1];

    /*
     * Calculate index of transition in frame offset array without tons of
     * branches. Note that transitions have to be in specific order in array.
     *
     * phy_mode 1M = 01b
     * phy_mode 2M = 10b
     *
     * 1M/1M = 01b | 00b = 01b
     * 1M/2M = 01b | 10b = 11b
     * 2M/1M = 00b | 00b = 00b
     * 2M/2M = 00b | 10b = 10b
     */
    txrx = (tx_phy_mode & 0x01) | (rx_phy_mode & 0x02);
    rxtx = (rx_phy_mode & 0x01) | (tx_phy_mode & 0x02);
    g_ble_phy_data.frame_offset_txrx = g_ble_phy_frame_offset_txrx[txrx];
    g_ble_phy_data.frame_offset_rxtx = g_ble_phy_frame_offset_rxtx[rxtx];
}

static int
ble_phy_get_cur_phy(void)
{
#if MYNEWT_VAL(BLE_LL_PHY)
    switch (g_ble_phy_data.phy_mode_cur) {
    case BLE_PHY_MODE_1M:
        return BLE_PHY_1M;
    case BLE_PHY_MODE_2M:
        return BLE_PHY_2M;
    default:
        assert(0);
        return -1;
    }
#else
    return BLE_PHY_1M;
#endif
}

/**
 * Copies the data from the phy receive buffer into a mbuf chain.
 *
 * @param dptr Pointer to receive buffer
 * @param rxpdu Pointer to already allocated mbuf chain
 *
 * NOTE: the packet header already has the total mbuf length in it. The
 * lengths of the individual mbufs are not set prior to calling.
 *
 */
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 (om) {
        /*
         * 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] "+l" (dst), [src] "+l" (src),
                            [len] "+l" (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] "+l" (rem_len)
                      : [dst] "l" (dst), [src] "l" (src)
                      : "r3", "memory");

    /* Copy header */
    memcpy(BLE_MBUF_HDR_PTR(rxpdu), &g_ble_phy_data.rxhdr,
           sizeof(struct ble_mbuf_hdr));
}

void
ble_phy_wfr_enable(int txrx, uint8_t tx_phy_mode, uint32_t wfr_usecs)
{
    uint64_t llt;
    uint32_t corr_window;
    uint32_t llt_z_ticks;
    uint32_t aa_time;

    /*
     * RX is started 2us earlier due to allowed clock accuracy and it should end
     * 2us later for the same reason. Preamble is always 8us (8 symbols on 1M,
     * 16 symbols on 2M) and Access Address is 32us on 1M and 16us on 2M. Add
     * 1us just in case...
     */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY)
    aa_time = 2 + (g_ble_phy_data.phy_mode_rx == BLE_PHY_MODE_1M ? 40 : 24) + 2 + 1;
#else
    aa_time = 45;
#endif

    if (txrx == BLE_PHY_WFR_ENABLE_TXRX) {
        CMAC_SETREGF(CM_PHY_CTRL2_REG, CORR_WINDOW, aa_time);
        CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_CORR_TMR_LD_2_CORR_START_Msk;
    } else if (wfr_usecs < 16384) {
        CMAC_SETREGF(CM_PHY_CTRL2_REG, CORR_WINDOW, wfr_usecs + aa_time);
        CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_CORR_TMR_LD_2_CORR_START_Msk;
    } else {
        wfr_usecs += aa_time;
        llt = g_ble_phy_data.llt_rx_start;

        /*
         * wfr is outside range of CORR_WINDOW so we need to use LLT to start
         * correlator timeout with some delay. Let's use ~10ms as new CORR_WINDOW
         * value (does not really matter, just had to pick something) so need to
         * calculate how many hi-Z ticks of delay we need.
         */
        llt_z_ticks = (wfr_usecs - 10000) / 1024;

        /* New CORR_WINDOW is wfr adjusted by hi-Z ticks and remainder of 1st tick. */
        corr_window = wfr_usecs;
        corr_window -= llt_z_ticks * 1024;
        corr_window -= 1024 - (llt & 0x3ff);

        CMAC->CM_LL_TIMER1_36_10_EQ_Z_REG = (llt >> 10) + llt_z_ticks;
        CMAC_SETREGF(CM_PHY_CTRL2_REG, CORR_WINDOW, corr_window);
        CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_CORR_TMR_LD_2_TMR1_36_10_EQ_Z_Msk;
    }
}

int
ble_phy_init(void)
{
    g_ble_phy_data.phy_state = BLE_PHY_STATE_IDLE;
#if MYNEWT_VAL(BLE_LL_DTM)
    g_ble_phy_data.phy_whitening = 1;
#endif

    ble_rf_init();

    /*
     * 9_0_EQ_X can be linked to start RX/TX so we'll use this one for
     * scheduling TX/RX start - make sure it's not linked to LL_TIMER2LLC
     */
    CMAC->CM_LL_INT_SEL_REG &= ~CMAC_CM_LL_INT_SEL_REG_LL_TIMER1_9_0_EQ_X_SEL_Msk;

    CMAC->CM_PHY_CTRL_REG = ((PHY_DELAY_POWER_DN_RX - 1) << 24) |
                            ((PHY_DELAY_POWER_DN_TX - 1) << 16) |
                            ((PHY_DELAY_POWER_UP_RX - 1) << 8) |
                            ((PHY_DELAY_POWER_UP_TX - 1));

#if MYNEWT_VAL(BLE_PHY_RF_HP_MODE)
    CMAC->CM_PHY_CTRL2_REG = CMAC_CM_PHY_CTRL2_REG_PHY_MODE_Msk |
                             (3 << CMAC_CM_PHY_CTRL2_REG_CORR_CLK_MODE_Pos);
#else
    CMAC->CM_PHY_CTRL2_REG = CMAC_CM_PHY_CTRL2_REG_PHY_MODE_Msk |
                             (2 << CMAC_CM_PHY_CTRL2_REG_CORR_CLK_MODE_Pos);
#endif

    CMAC_SETREGF(CM_CTRL2_REG, WHITENING_MODE, 0);
    CMAC_SETREGF(CM_CTRL2_REG, CRC_MODE, 0);

    /* Setup for 1M by default */
    ble_phy_mode_set(BLE_PHY_MODE_1M, BLE_PHY_MODE_1M);
    ble_phy_mode_apply(BLE_PHY_MODE_1M);

    NVIC_SetPriority(FIELD_IRQn, 0);
    NVIC_SetPriority(CALLBACK_IRQn, 0);
    NVIC_SetPriority(FRAME_IRQn, 0);
    NVIC_SetPriority(CRYPTO_IRQn, 1);
    NVIC_SetPriority(SW_MAC_IRQn, 1);
    NVIC_EnableIRQ(FIELD_IRQn);
    NVIC_EnableIRQ(CALLBACK_IRQn);
    NVIC_EnableIRQ(FRAME_IRQn);
    NVIC_EnableIRQ(SW_MAC_IRQn);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
    /* Initialize non-zero fixed values in CCM blocks */
    g_ble_phy_encrypt_data.b0[0] = 0x49;
    g_ble_phy_encrypt_data.b1[1] = 0x01;
    g_ble_phy_encrypt_data.ai[0] = 0x01;
#endif

    /*
     * Disable FIELD1, FIELD2 and FRAME errors since they can happen
     * sometimes if we are too late on scheduling and trigger CMAC
     * error. We can detect if tx_late happened and recover properly.
     */
    CMAC->CM_ERROR_DIS_REG |= CMAC_CM_ERROR_DIS_REG_CM_FIELD1_ERR_Msk |
                              CMAC_CM_ERROR_DIS_REG_CM_FIELD2_ERR_Msk |
                              CMAC_CM_ERROR_DIS_REG_CM_FRAME_ERR_Msk;

    return 0;
}

void
ble_phy_disable(void)
{
    MCU_DIAG_SER('D');

    __disable_irq();

    CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV1C_BS_CLEAR_Msk;

    __NOP();
    __NOP();
    __NOP();
    __NOP();
    __NOP();

    CMAC->CM_EXC_STAT_REG = CMAC_CM_EXC_STAT_REG_EXC_FIELD_ON_THR_EXP_Msk |
                            CMAC_CM_EXC_STAT_REG_EXC_BS_START_4THIS_Msk |
                            CMAC_CM_EXC_STAT_REG_EXC_CORR_TIMEOUT_Msk |
                            CMAC_CM_EXC_STAT_REG_EXC_BS_STOP_Msk |
                            CMAC_CM_EXC_STAT_REG_EXC_FRAME_START_Msk |
                            CMAC_CM_EXC_STAT_REG_EXC_PHY_TO_IDLE_4THIS_Msk |
                            CMAC_CM_EXC_STAT_REG_EXC_SW_MAC_Msk;

    NVIC->ICPR[0] = (1 << FIELD_IRQn) | (1 << CALLBACK_IRQn) |
                    (1 << FRAME_IRQn) | (1 << SW_MAC_IRQn);

    os_arch_cmac_bs_ctrl_irq_unblock();
    g_sw_mac_exc = 0;

    ble_rf_stop();

    /*
     * If ble_phy_disable is called precisely when access address was matched,
     * ts1_dirty may not be cleared properly. This is because bs_clear will
     * cause bitstream controller to be stopped and we won't get callback_irq,
     * but seems like demodulator is still active for a while and will trigger
     * ev1c_ts1_trigger on 1st symbol which will set ts1_dirty. We do not expect
     * ts1_dirty to be set after bs_clear so we won't clear it. To workaround
     * his, we can just clear it explicitly here after everything is already
     * disabled.
     */
    (void)CMAC->CM_TS1_REG;

    __enable_irq();

    NVIC_EnableIRQ(FRAME_IRQn);
    NVIC_EnableIRQ(FIELD_IRQn);

    g_ble_phy_data.phy_state = BLE_PHY_STATE_IDLE;
    g_ble_phy_data.phy_tx_set = 0;
}

static void
ble_phy_rx_setup_fields(void)
{
    /* Make sure CRC LFSR initial value is set */
    CMAC_SETREGF(CM_CRC_REG, CRC_INIT_VAL, g_ble_phy_data.crc_init);

    CMAC->CM_FIELD_PUSH_DATA_REG = g_ble_phy_data.access_addr;
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_ACCESS_ADDR;
    CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(0, 2);
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_HEADER;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
    if (g_ble_phy_data.phy_encrypted) {
        /* Only program one byte for encrypted payloads */
        CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(2, 2);
        CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_ENC_PAYLOAD;
    } else {
        CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(2, 2);
        CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_PAYLOAD;
    }
#else
    CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_RX(2, 2);
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_RX_PAYLOAD;
#endif
}

static void
ble_phy_rx_setup_xcvr(void)
{
    uint8_t rf_chan = g_ble_phy_chan_to_rf[g_ble_phy_data.channel];

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
    ble_hw_resolv_proc_reset();
#endif

    CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV1C_CALLBACK_VALID_SET_Msk;

    ble_rf_setup_rx(rf_chan, g_ble_phy_data.phy_mode_rx);

    g_ble_phy_data.phy_rx_started = 0;
}

static int
ble_phy_rx(void)
{
    MCU_DIAG_SER('R');

    ble_rf_configure();
    ble_phy_rx_setup_xcvr();

    CMAC->CM_FRAME_1_REG = CMAC_CM_FRAME_1_REG_FRAME_VALID_Msk;

    CMAC_SETREGF(CM_PHY_CTRL2_REG, CORR_WINDOW, 0);

    ble_phy_rx_setup_fields();

    g_ble_phy_data.phy_state = BLE_PHY_STATE_RX;

    return 0;
}

int
ble_phy_rx_set_start_time(uint32_t cputime, uint8_t rem_usecs)
{
    uint64_t llt_rx_start;
    uint32_t llt32;
    int32_t time_till_start;
    int rc = 0;

    MCU_DIAG_SER('r');

#if MYNEWT_VAL(BLE_LL_PHY)
    ble_phy_mode_apply(g_ble_phy_data.phy_mode_rx);
#endif

    ble_phy_rx();

    /* Store LLT value at RX start, we'll need this to set wfr */
    llt_rx_start = (uint64_t)cputime * 32 + rem_usecs;
    g_ble_phy_data.llt_rx_start = llt_rx_start;

    /* Adjust start time for rx delays.
     * Note: we can use 32lsb for remaining calculations.
     */
    llt32 = llt_rx_start;
    llt32 -= PHY_DELAY_POWER_UP_RX - g_ble_phy_data.path_delay_rx;

    __disable_irq();
    CMAC->CM_LL_TIMER1_9_0_EQ_X_REG = llt32;
    CMAC->CM_EV_LINKUP_REG =
        CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_TMR1_9_0_EQ_X_Msk;

    time_till_start = (int32_t)(llt32 - cmac_timer_read32());
    if (time_till_start <= 0) {
        /*
         * Possible we missed the frame start! If we have, we need to start
         * ASAP.
         */
        if ((CMAC->CM_EXC_STAT_REG & CMAC_CM_EXC_STAT_REG_EXC_FRAME_START_Msk) == 0) {
            /* We missed start. Start now */
            CMAC->CM_EV_LINKUP_REG =
                CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_ASAP_Msk;
            rc = BLE_PHY_ERR_RX_LATE;
        }
    }
    __enable_irq();

    return rc;
}

int
ble_phy_tx(ble_phy_tx_pducb_t pducb, void *pducb_arg, uint8_t end_trans)
{
    uint8_t *txbuf = g_ble_phy_tx_buf;
    int rc;

    MCU_DIAG_SER('T');

    assert(CMAC->CM_FRAME_1_REG & CMAC_CM_FRAME_1_REG_FRAME_TX_Msk);

    g_ble_phy_data.end_transition = end_trans;

    /*
     * Program required fields now so in worst case TX can continue while we
     * are still preparing header and payload.
     */
    CMAC->CM_FIELD_PUSH_DATA_REG = g_ble_phy_data.access_addr & 1 ? 0x5555 : 0xaaaa;
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_TX_PREAMBLE;
    CMAC->CM_FIELD_PUSH_DATA_REG = g_ble_phy_data.access_addr;
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_TX_ACCESS_ADDR;

    /* Make sure CRC LFSR initial value is set */
    CMAC_SETREGF(CM_CRC_REG, CRC_INIT_VAL, g_ble_phy_data.crc_init);

    /* txbuf[0] is hdr_byte, txbuf[1] is pkt_len */
    txbuf[1] = pducb(&txbuf[2], pducb_arg, &txbuf[0]);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
    if (g_ble_phy_data.phy_encrypted && (txbuf[1] != 0)) {
        /* We have to add the MIC to the length */
        txbuf[1] += BLE_LL_DATA_MIC_LEN;

        /* Program header field */
        CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_TX(0, 2);
        CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_TX_PAYLOAD;

        /* Program payload (and MIC) */
        CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_TX(2, txbuf[1]);
        CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_TX_ENC_PAYLOAD;
    } else {
        /* Program header and payload fields */
        CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_TX(0, txbuf[1] + 2);
        CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_TX_PAYLOAD;
    }
#else
    /* Program header and payload fields */
    CMAC->CM_FIELD_PUSH_DATA_REG = FIELD_DATA_REG_DMA_TX(0, txbuf[1] + 2);
    CMAC->CM_FIELD_PUSH_CTRL_REG = FIELD_CTRL_REG_TX_PAYLOAD;
#endif

    /*
     * If there was FIELD_ON_THR exception it means access address was already
     * sent and we are likely too late here - abort.
     */
    if (CMAC->CM_EXC_STAT_REG & CMAC_CM_EXC_STAT_REG_EXC_FIELD_ON_THR_EXP_Msk) {
        ble_phy_disable();
        g_ble_phy_data.end_transition = BLE_PHY_TRANSITION_NONE;
        STATS_INC(ble_phy_stats, tx_late_field);
        STATS_INC(ble_phy_stats, tx_late);
        rc = BLE_PHY_ERR_RADIO_STATE;
    } else {
        if (g_ble_phy_data.phy_state == BLE_PHY_STATE_IDLE) {
            g_ble_phy_data.phy_state = BLE_PHY_STATE_TX;
        }
        STATS_INC(ble_phy_stats, tx_good);
        STATS_INCN(ble_phy_stats, tx_bytes, txbuf[1] + 2);
        rc = BLE_ERR_SUCCESS;
    }

    g_ble_phy_data.phy_tx_set = 1;

    /* Now we can handle BS_CTRL */
    NVIC_EnableIRQ(FRAME_IRQn);
    NVIC_EnableIRQ(FIELD_IRQn);

    return rc;
}

int
ble_phy_tx_set_start_time(uint32_t cputime, uint8_t rem_usecs)
{
    uint8_t rf_chan = g_ble_phy_chan_to_rf[g_ble_phy_data.channel];
    uint32_t ll_val32;
    int rc;

    MCU_DIAG_SER('t');

#if MYNEWT_VAL(BLE_LL_PHY)
    ble_phy_mode_apply(g_ble_phy_data.phy_mode_tx);
#endif

    assert(ble_rf_is_enabled());

    ble_rf_configure();
    ble_rf_setup_tx(rf_chan, g_ble_phy_data.phy_mode_tx);

    ll_val32 = cputime * 32 + rem_usecs;
    ll_val32 -= PHY_DELAY_POWER_UP_TX + g_ble_phy_data.path_delay_tx;
    /* we can schedule TX only up to 1023us in advance */
    assert((int32_t)(ll_val32 - cmac_timer_read32()) < 1024);

    /*
     * We do not want FIELD/FRAME interrupts until ble_phy_tx() has
     * pushed all fields.
     */
    NVIC_DisableIRQ(FRAME_IRQn);
    NVIC_DisableIRQ(FIELD_IRQn);

    CMAC->CM_LL_TIMER1_9_0_EQ_X_REG = ll_val32;
    CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_TMR1_9_0_EQ_X_Msk;

    if ((int32_t)(ll_val32 - cmac_timer_read32()) < 0) {
        STATS_INC(ble_phy_stats, tx_late_sched);
        goto tx_late;
    }

    /*
     * Program frame now since it needs to be ready for FRAME_START, we can
     * push fields later
     */
    CMAC->CM_FRAME_1_REG = CMAC_CM_FRAME_1_REG_FRAME_VALID_Msk |
                           CMAC_CM_FRAME_1_REG_FRAME_TX_Msk |
                           CMAC_CM_FRAME_1_REG_FRAME_EXC_ON_BS_START_Msk;

    /*
     * There should be no EXC_FRAME_START here so if it already happened we
     * need to assume tx_late and abort.
     */
    if (CMAC->CM_EXC_STAT_REG & CMAC_CM_EXC_STAT_REG_EXC_FRAME_START_Msk) {
        STATS_INC(ble_phy_stats, tx_late_frame);
        goto tx_late;
    }

    rc = 0;

    goto done;

tx_late:
    STATS_INC(ble_phy_stats, tx_late);
    ble_phy_disable();
    rc = BLE_PHY_ERR_TX_LATE;

done:
    return rc;
}

void
ble_phy_set_txend_cb(ble_phy_tx_end_func txend_cb, void *arg)
{
    g_ble_phy_data.txend_cb = txend_cb;
    g_ble_phy_data.txend_arg = arg;
}

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
void
ble_phy_tx_enc_start(void)
{
    struct ble_phy_encrypt_obj *enc;

    enc = &g_ble_phy_encrypt_data;
    enc->b0[15] = g_ble_phy_tx_buf[1] - 4;
    enc->b1[2] = g_ble_phy_tx_buf[0] & BLE_LL_DATA_HDR_LLID_MASK;

    /* XXX: should we check for busy? */
    /* XXX: might not be needed, but for now terminate any crypto operations. */
    CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_SW_REQ_ABORT_Msk;

    CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_IN_SEL_Msk |
                               CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_CTR_MAC_EN_Msk |
                               CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_CTR_PLD_EN_Msk |
                               CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_AUTH_EN_Msk |
                               CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_ENC_DECN_Msk;

    /* Start crypto */
    CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV_CRYPTO_START_Msk;
}

void
ble_phy_rx_enc_start(uint8_t len)
{
    struct ble_phy_encrypt_obj *enc;

    enc = &g_ble_phy_encrypt_data;
    enc->b0[15] = len - 4; /* length without MIC as length includes MIC */
    enc->b1[2] = g_ble_phy_rx_buf[0] & BLE_LL_DATA_HDR_LLID_MASK;

    /* XXX: should we check for busy? */
    /* XXX: might not be needed, but for now terminate any crypto operations. */
    //CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_SW_REQ_ABORT_Msk;

    CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_OUT_SEL_Msk |
        CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_CTR_MAC_EN_Msk |
        CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_CTR_PLD_EN_Msk |
        CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_AUTH_EN_Msk;

    /* Start crypto */
    CMAC->CM_EV_SET_REG = CMAC_CM_EV_SET_REG_EV_CRYPTO_START_Msk;
}

void
ble_phy_encrypt_enable(const uint8_t *key)
{
    struct ble_phy_encrypt_obj *enc;

    enc = &g_ble_phy_encrypt_data;
    memcpy(enc->key, key, 16);

    g_ble_phy_data.phy_encrypted = 1;

    /* Program key registers */
    CMAC->CM_CRYPTO_KEY_31_0_REG = get_le32(&enc->key[0]);
    CMAC->CM_CRYPTO_KEY_63_32_REG = get_le32(&enc->key[4]);
    CMAC->CM_CRYPTO_KEY_95_64_REG = get_le32(&enc->key[8]);
    CMAC->CM_CRYPTO_KEY_127_96_REG = get_le32(&enc->key[12]);

    /* Program ADRx registers */
    CMAC->CM_CRYPTO_IN_ADR0_REG = (uint32_t)enc->b1;
    CMAC->CM_CRYPTO_IN_ADR1_REG = (uint32_t)enc->b0;
    CMAC->CM_CRYPTO_IN_ADR2_REG = (uint32_t)&g_ble_phy_tx_buf[2];
    CMAC->CM_CRYPTO_IN_ADR3_REG = (uint32_t)enc->ai;
    CMAC->CM_CRYPTO_OUT_ADR_REG = (uint32_t)&g_ble_phy_rx_buf[2];

    CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_SW_REQ_PBUF_CLR_Msk;
}

void
ble_phy_encrypt_iv_set(const uint8_t *iv)
{
    struct ble_phy_encrypt_obj *enc;

    enc = &g_ble_phy_encrypt_data;
    memcpy(&enc->b0[6], iv, 8);
    memcpy(&enc->ai[6], iv, 8);
}


void
ble_phy_encrypt_counter_set(uint64_t counter, uint8_t dir_bit)
{
    struct ble_phy_encrypt_obj *enc;

    enc = &g_ble_phy_encrypt_data;
    put_le32(&enc->b0[1], counter);
    enc->b0[5] = dir_bit ? 0x80 : 0;
    put_le32(&enc->ai[1], counter);
    enc->ai[5] = enc->b0[5];

    CMAC->CM_CRYPTO_CTRL_REG = CMAC_CM_CRYPTO_CTRL_REG_CM_CRYPTO_SW_REQ_PBUF_CLR_Msk;
}

void
ble_phy_encrypt_disable(void)
{
    g_ble_phy_data.phy_encrypted = 0;
}
#endif

static int8_t
phy_txpower_round(int8_t dbm)
{
    static const int8_t supported_pwr_levels[] = {-18, -12, -8, -6, -3, -2,
                                                  -1, 0, 1, 2, 3, 4, 5, 6 };
    int i;

    for (i = 0; i < ARRAY_SIZE(supported_pwr_levels); i++) {
        if (dbm <= supported_pwr_levels[i]) {
            dbm = supported_pwr_levels[i];
            break;
        }
    }

    if (i == ARRAY_SIZE(supported_pwr_levels)) {
        dbm = supported_pwr_levels[ARRAY_SIZE(supported_pwr_levels) - 1];
    }

    return dbm;
}

int
ble_phy_tx_power_set(int dbm)
{
#if MYNEWT_VAL(CMAC_DEBUG_DATA_ENABLE)
    if (g_cmac_shm_debugdata.tx_power_ovr_enable) {
        dbm = g_cmac_shm_debugdata.tx_power_ovr;
    }
#endif

    dbm = phy_txpower_round(dbm);
    ble_rf_set_tx_power(dbm);

    return 0;
}

int
ble_phy_tx_power_round(int dbm)
{
    return phy_txpower_round(dbm);
}

int
ble_phy_setchan(uint8_t chan, uint32_t access_addr, uint32_t crc_init)
{
    uint8_t rf_chan = g_ble_phy_chan_to_rf[chan];

    assert(chan < BLE_PHY_NUM_CHANS);

    if (chan >= BLE_PHY_NUM_CHANS) {
        return BLE_PHY_ERR_INV_PARAM;
    }

    g_ble_phy_data.channel = chan;
    g_ble_phy_data.access_addr = access_addr;
    g_ble_phy_data.crc_init = crc_init;

    CMAC_SETREGF(CM_PHY_CTRL2_REG, PHY_RF_CHANNEL, rf_chan);

    return 0;
}

uint8_t
ble_phy_chan_get(void)
{
    return g_ble_phy_data.channel;
}

void
ble_phy_restart_rx(void)
{
    /* XXX: for now, we will disable the phy using ble_phy_disable and then
       re-enable it
     */
    ble_phy_disable();

    /* Apply mode before starting RX */
#if MYNEWT_VAL(BLE_LL_PHY)
    ble_phy_mode_apply(g_ble_phy_data.phy_mode_rx);
#endif

    /* Setup phy to rx again */
    ble_phy_rx();

    /* Start reception now */
    CMAC->CM_EV_LINKUP_REG = CMAC_CM_EV_LINKUP_REG_LU_FRAME_START_2_ASAP_Msk;
}

uint32_t
ble_phy_access_addr_get(void)
{
    return g_ble_phy_data.access_addr;
}

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)
{
    return ble_rf_is_enabled();
}

uint8_t
ble_phy_max_data_pdu_pyld(void)
{
    return BLE_LL_DATA_PDU_MAX_PYLD;
}

void
ble_phy_resolv_list_enable(void)
{
    g_ble_phy_data.phy_privacy = 1;

    ble_hw_resolv_proc_enable();
}

void
ble_phy_resolv_list_disable(void)
{
    ble_hw_resolv_proc_disable();

    g_ble_phy_data.phy_privacy = 0;
}

void
ble_phy_rfclk_enable(void)
{
    ble_rf_enable();
}

void
ble_phy_rfclk_disable(void)
{
    /* XXX We can't disable RF while PHY_BUSY is asserted so let's wait a bit */
    while (CMAC->CM_DIAG_WORD2_REG & CMAC_CM_DIAG_WORD2_REG_DIAG2_PHY_BUSY_BUF_Msk);

    ble_rf_disable();
}

#if MYNEWT_VAL(BLE_LL_DTM)
void
ble_phy_enable_dtm(void)
{
    g_ble_phy_data.phy_whitening = 0;
}

void
ble_phy_disable_dtm(void)
{
    g_ble_phy_data.phy_whitening = 1;
}

#if MYNEWT_VAL(BLE_LL_DTM_EXTENSIONS)
int
ble_phy_dtm_carrier(uint8_t rf_channel)
{
    return 1;
}
#endif
#endif
