/* ieee802154_mcxw.c - NXP MCXW 802.15.4 driver */ /* * Copyright 2025 NXP * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nxp_mcxw_ieee802154 #define LOG_MODULE_NAME ieee802154_mcxw #include LOG_MODULE_REGISTER(LOG_MODULE_NAME); #include #include #include #include #include #include #include #include #if defined(CONFIG_NET_L2_OPENTHREAD) #include #include #endif #include #include #include #include #include #include #include "EmbeddedTypes.h" #include "Phy.h" #include "fwk_platform_ot.h" #include "ieee802154_mcxw.h" #include "ieee802154_mcxw_utils.h" void PLATFORM_RemoteActiveReq(void); void PLATFORM_RemoteActiveRel(void); #if CONFIG_IEEE802154_CSL_ENDPOINT #define CMP_OVHD (4 * IEEE802154_SYMBOL_TIME_US) /* 2 LPTRM (32 kHz) ticks */ static bool_t csl_rx = FALSE; static void set_csl_sample_time(void); static void start_csl_receiver(void); static void stop_csl_receiver(void); static uint16_t rf_compute_csl_phase(uint32_t aTimeUs); #else /* CONFIG_IEEE802154_CSL_ENDPOINT */ #define start_csl_receiver() #define stop_csl_receiver() #endif /* CONFIG_IEEE802154_CSL_ENDPOINT */ static volatile uint32_t sun_rx_mode = RX_ON_IDLE_START; /* Private functions */ static void rf_abort(void); static void rf_set_channel(uint8_t channel); static void rf_set_tx_power(int8_t tx_power); static uint64_t rf_adjust_tstamp_from_phy(uint64_t ts); #if CONFIG_IEEE802154_CSL_ENDPOINT || CONFIG_NET_PKT_TXTIME static uint32_t rf_adjust_tstamp_from_app(uint32_t time); #endif /* CONFIG_IEEE802154_CSL_ENDPOINT || CONFIG_NET_PKT_TXTIME */ static void rf_rx_on_idle(uint32_t newValue); static uint8_t ot_phy_ctx = (uint8_t)(-1); static struct mcxw_context mcxw_ctx; /** * Stub function used for controlling low power mode */ WEAK void app_allow_device_to_slepp(void) { } /** * Stub function used for controlling low power mode */ WEAK void app_disallow_device_to_slepp(void) { } void mcxw_get_eui64(uint8_t *eui64) { __ASSERT_NO_MSG(eui64); /* PLATFORM_GetIeee802_15_4Addr(); */ sys_rand_get(eui64, sizeof(mcxw_ctx.mac)); eui64[0] = (eui64[0] & ~0x01) | 0x02; } static int mcxw_set_pan_id(const struct device *dev, uint16_t aPanId) { struct mcxw_context *mcxw_radio = dev->data; macToPlmeMessage_t msg; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibPanId_c; msg.msgData.setReq.PibAttributeValue = (uint64_t)aPanId; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); mcxw_radio->pan_id = aPanId; return 0; } static int mcxw_set_extended_address(const struct device *dev, const uint8_t *ieee_addr) { struct mcxw_context *mcxw_radio = dev->data; macToPlmeMessage_t msg; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibLongAddress_c; msg.msgData.setReq.PibAttributeValue = *(uint64_t *)ieee_addr; memcpy(mcxw_radio->mac, ieee_addr, 8); (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); return 0; } static int mcxw_set_short_address(const struct device *dev, uint16_t aShortAddress) { ARG_UNUSED(dev); macToPlmeMessage_t msg; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibShortAddress_c; msg.msgData.setReq.PibAttributeValue = (uint64_t)aShortAddress; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); return 0; } static int mcxw_filter(const struct device *dev, bool set, enum ieee802154_filter_type type, const struct ieee802154_filter *filter) { LOG_DBG("Applying filter %u", type); if (!set) { return -ENOTSUP; } if (type == IEEE802154_FILTER_TYPE_IEEE_ADDR) { return mcxw_set_extended_address(dev, filter->ieee_addr); } else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) { return mcxw_set_short_address(dev, filter->short_addr); } else if (type == IEEE802154_FILTER_TYPE_PAN_ID) { return mcxw_set_pan_id(dev, filter->pan_id); } return -ENOTSUP; } void mcxw_radio_receive(void) { macToPlmeMessage_t msg; phyStatus_t phy_status; app_disallow_device_to_slepp(); __ASSERT(mcxw_ctx.state != RADIO_STATE_DISABLED, "Radio RX invalid state"); mcxw_ctx.state = RADIO_STATE_RECEIVE; rf_abort(); rf_set_channel(mcxw_ctx.channel); if (sun_rx_mode) { start_csl_receiver(); /* restart Rx on idle only if it was enabled */ msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibRxOnWhenIdle; msg.msgData.setReq.PibAttributeValue = (uint64_t)1; phy_status = MAC_PLME_SapHandler(&msg, ot_phy_ctx); __ASSERT_NO_MSG(phy_status == gPhySuccess_c); } } static uint8_t mcxw_get_acc(const struct device *dev) { ARG_UNUSED(dev); return CONFIG_IEEE802154_MCXW_CSL_ACCURACY; } static int mcxw_start(const struct device *dev) { struct mcxw_context *mcxw_radio = dev->data; __ASSERT(mcxw_radio->state == RADIO_STATE_DISABLED, "%s", __func__); mcxw_radio->state = RADIO_STATE_SLEEP; rf_rx_on_idle(RX_ON_IDLE_START); mcxw_radio_receive(); return 0; } static int mcxw_stop(const struct device *dev) { struct mcxw_context *mcxw_radio = dev->data; __ASSERT(mcxw_radio->state != RADIO_STATE_DISABLED, "%s", __func__); stop_csl_receiver(); mcxw_radio->state = RADIO_STATE_DISABLED; return 0; } void mcxw_radio_sleep(void) { __ASSERT_NO_MSG(((mcxw_ctx.state != RADIO_STATE_TRANSMIT) && (mcxw_ctx.state != RADIO_STATE_DISABLED))); rf_abort(); stop_csl_receiver(); app_allow_device_to_slepp(); mcxw_ctx.state = RADIO_STATE_SLEEP; } static void mcxw_enable_src_match(bool enable) { macToPlmeMessage_t msg; msg.msgType = gPlmeSetSAMState_c; msg.msgData.SAMState = enable; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static int mcxw_src_match_entry(bool extended, uint8_t *address) { macToPlmeMessage_t msg; msg.msgType = gPlmeAddToSapTable_c; msg.msgData.deviceAddr.panId = mcxw_ctx.pan_id; if (extended) { msg.msgData.deviceAddr.mode = 3; memcpy(msg.msgData.deviceAddr.addr, address, 8); } else { msg.msgData.deviceAddr.mode = 2; memcpy(msg.msgData.deviceAddr.addr, address, 2); } if (gPhySuccess_c != MAC_PLME_SapHandler(&msg, ot_phy_ctx)) { /* the status is not returned from PHY over RPMSG */ return -ENOMEM; } return 0; } static int mcxw_src_clear_entry(bool extended, uint8_t *address) { macToPlmeMessage_t msg; msg.msgType = gPlmeRemoveFromSAMTable_c; msg.msgData.deviceAddr.panId = mcxw_ctx.pan_id; if (extended) { msg.msgData.deviceAddr.mode = 3; memcpy(msg.msgData.deviceAddr.addr, address, 8); } else { msg.msgData.deviceAddr.mode = 2; memcpy(msg.msgData.deviceAddr.addr, address, 2); } if (gPhySuccess_c != MAC_PLME_SapHandler(&msg, ot_phy_ctx)) { /* the status is not returned from PHY over RPMSG */ return -ENOENT; } return 0; } static int handle_ack(struct mcxw_context *mcxw_radio) { uint8_t len; struct net_pkt *pkt; int err = 0; len = mcxw_radio->rx_ack_frame.length; pkt = net_pkt_rx_alloc_with_buffer(mcxw_radio->iface, len, AF_UNSPEC, 0, K_NO_WAIT); if (!pkt) { LOG_ERR("No free packet available."); err = -ENOMEM; goto exit; } if (net_pkt_write(pkt, mcxw_radio->rx_ack_frame.psdu, len) < 0) { LOG_ERR("Failed to write to a packet."); err = -ENOMEM; goto free_ack; } net_pkt_set_ieee802154_lqi(pkt, mcxw_radio->rx_ack_frame.lqi); net_pkt_set_ieee802154_rssi_dbm(pkt, mcxw_radio->rx_ack_frame.rssi); net_pkt_set_timestamp_ns(pkt, mcxw_radio->rx_ack_frame.timestamp); net_pkt_cursor_init(pkt); if (ieee802154_handle_ack(mcxw_radio->iface, pkt) != NET_OK) { LOG_ERR("ACK packet not handled - releasing."); } free_ack: net_pkt_unref(pkt); exit: mcxw_radio->rx_ack_frame.length = 0; return err; } static int mcxw_tx(const struct device *dev, enum ieee802154_tx_mode mode, struct net_pkt *pkt, struct net_buf *frag) { struct mcxw_context *mcxw_radio = dev->data; /* tx_data buffer has reserved memory for both macToPdDataMessage_t and actual data frame * after */ macToPdDataMessage_t *msg = (macToPdDataMessage_t *)mcxw_radio->tx_data; phyStatus_t phy_status; uint8_t payload_len = frag->len; uint8_t *payload = frag->data; app_disallow_device_to_slepp(); __ASSERT(mcxw_radio->state != RADIO_STATE_DISABLED, "%s: radio disabled", __func__); if (payload_len > IEEE802154_MTU) { LOG_ERR("Payload too large: %d", payload_len); return -EMSGSIZE; } mcxw_radio->tx_frame.length = payload_len + IEEE802154_FCS_LENGTH; memcpy(mcxw_radio->tx_frame.psdu, payload, payload_len); mcxw_radio->tx_frame.sec_processed = net_pkt_ieee802154_frame_secured(pkt); mcxw_radio->tx_frame.hdr_updated = net_pkt_ieee802154_mac_hdr_rdy(pkt); rf_set_channel(mcxw_radio->channel); msg->msgType = gPdDataReq_c; msg->msgData.dataReq.psduLength = mcxw_radio->tx_frame.length; msg->msgData.dataReq.CCABeforeTx = gPhyNoCCABeforeTx_c; msg->msgData.dataReq.startTime = gPhySeqStartAsap_c; /* tx_frame.psdu will point to tx_frame.tx_data data buffer after macToPdDataMessage_t * structure */ msg->msgData.dataReq.pPsdu = mcxw_radio->tx_frame.psdu; if (ieee802154_is_ar_flag_set(frag)) { msg->msgData.dataReq.ackRequired = gPhyRxAckRqd_c; /* The 3 bytes are 1 byte frame length and 2 bytes FCS */ msg->msgData.dataReq.txDuration = IEEE802154_CCA_LEN_SYM + IEEE802154_PHY_SHR_LEN_SYM + (3 + mcxw_radio->tx_frame.length) * RADIO_SYMBOLS_PER_OCTET + IEEE802154_TURNAROUND_LEN_SYM; if (is_frame_version_2015(frag->data, frag->len)) { /* Because enhanced ack can be of variable length we need to set the timeout * value to account for the FCF and addressing fields only, and stop the * timeout timer after they are received and validated as a valid ACK */ msg->msgData.dataReq.txDuration += IEEE802154_ENH_ACK_WAIT_SYM; } else { msg->msgData.dataReq.txDuration += IEEE802154_IMM_ACK_WAIT_SYM; } } else { msg->msgData.dataReq.ackRequired = gPhyNoAckRqd_c; msg->msgData.dataReq.txDuration = 0xFFFFFFFFU; } switch (mode) { case IEEE802154_TX_MODE_DIRECT: msg->msgData.dataReq.CCABeforeTx = gPhyNoCCABeforeTx_c; break; case IEEE802154_TX_MODE_CCA: msg->msgData.dataReq.CCABeforeTx = gPhyCCAMode1_c; break; #if defined(CONFIG_NET_PKT_TXTIME) case IEEE802154_TX_MODE_TXTIME: case IEEE802154_TX_MODE_TXTIME_CCA: mcxw_radio->tx_frame.tx_delay = net_pkt_timestamp_ns(pkt); msg->msgData.dataReq.startTime = rf_adjust_tstamp_from_app(mcxw_radio->tx_frame.tx_delay); msg->msgData.dataReq.startTime /= IEEE802154_SYMBOL_TIME_US; break; #endif default: break; } msg->msgData.dataReq.flags = 0; #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 if (is_keyid_mode_1(mcxw_radio->tx_frame.psdu, mcxw_radio->tx_frame.length)) { if (!net_pkt_ieee802154_frame_secured(pkt)) { msg->msgData.dataReq.flags |= gPhyEncFrame; if (!net_pkt_ieee802154_mac_hdr_rdy(pkt)) { msg->msgData.dataReq.flags |= gPhyUpdHDr; #if CONFIG_IEEE802154_CSL_ENDPOINT /* Previously aFrame->mInfo.mTxInfo.mCslPresent was used to * determine if the radio code should update the IE header. This * field is no longer set by the OT stack. Until the issue is fixed * in OT stack check if CSL period is > 0 and always update CSL IE * in that case. */ if (mcxw_radio->csl_period) { uint32_t hdr_time_us; start_csl_receiver(); /* Add TX_ENCRYPT_DELAY_SYM symbols delay to allow * encryption to finish */ msg->msgData.dataReq.startTime = PhyTime_ReadClock() + TX_ENCRYPT_DELAY_SYM; hdr_time_us = (mcxw_get_time(NULL) / NSEC_PER_USEC) + (TX_ENCRYPT_DELAY_SYM + IEEE802154_PHY_SHR_LEN_SYM) * IEEE802154_SYMBOL_TIME_US; set_csl_ie(mcxw_radio->tx_frame.psdu, mcxw_radio->tx_frame.length, mcxw_radio->csl_period, rf_compute_csl_phase(hdr_time_us)); } #endif /* CONFIG_IEEE802154_CSL_ENDPOINT */ } } } #endif k_sem_reset(&mcxw_radio->tx_wait); phy_status = MAC_PD_SapHandler(msg, ot_phy_ctx); if (phy_status == gPhySuccess_c) { mcxw_radio->tx_status = 0; mcxw_radio->state = RADIO_STATE_TRANSMIT; } else { return -EIO; } k_sem_take(&mcxw_radio->tx_wait, K_FOREVER); /* PWR_AllowDeviceToSleep(); */ mcxw_radio_receive(); switch (mcxw_radio->tx_status) { case 0: if (mcxw_radio->rx_ack_frame.length) { return handle_ack(mcxw_radio); } return 0; default: return -(mcxw_radio->tx_status); } } void mcxw_rx_thread(void *arg1, void *arg2, void *arg3) { struct mcxw_context *mcxw_radio = (struct mcxw_context *)arg1; struct net_pkt *pkt; struct mcxw_rx_frame rx_frame; ARG_UNUSED(arg2); ARG_UNUSED(arg3); while (true) { pkt = NULL; LOG_DBG("Waiting for frame"); if (k_msgq_get(&mcxw_radio->rx_msgq, &rx_frame, K_FOREVER) < 0) { LOG_ERR("Failed to get RX data from message queue"); continue; } pkt = net_pkt_rx_alloc_with_buffer(mcxw_radio->iface, rx_frame.length, AF_UNSPEC, 0, K_FOREVER); if (net_pkt_write(pkt, rx_frame.psdu, rx_frame.length)) { goto drop; } net_pkt_set_ieee802154_lqi(pkt, rx_frame.lqi); net_pkt_set_ieee802154_rssi_dbm(pkt, rx_frame.rssi); net_pkt_set_ieee802154_ack_fpb(pkt, rx_frame.ack_fpb); #if defined(CONFIG_NET_PKT_TIMESTAMP) net_pkt_set_timestamp_ns(pkt, rx_frame.timestamp); #endif #if defined(CONFIG_NET_L2_OPENTHREAD) net_pkt_set_ieee802154_ack_seb(pkt, rx_frame.ack_seb); #endif if (net_recv_data(mcxw_radio->iface, pkt) < 0) { LOG_ERR("Packet dropped by NET stack"); goto drop; } k_free(rx_frame.phy_buffer); rx_frame.phy_buffer = NULL; /* restart rx on idle if enough space in message queue */ if (k_msgq_num_free_get(&mcxw_radio->rx_msgq) >= 2) { rf_rx_on_idle(RX_ON_IDLE_START); } continue; drop: /* PWR_AllowDeviceToSleep(); */ net_pkt_unref(pkt); } } int8_t mcxw_get_rssi(void) { macToPlmeMessage_t msg; msg.msgType = gPlmeGetReq_c; msg.msgData.getReq.PibAttribute = gPhyGetRSSILevel_c; msg.msgData.getReq.PibAttributeValue = 127; /* RSSI is invalid*/ (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); return (int8_t)msg.msgData.getReq.PibAttributeValue; } void mcxw_set_promiscuous(bool aEnable) { macToPlmeMessage_t msg; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibPromiscuousMode_c; msg.msgData.setReq.PibAttributeValue = (uint64_t)aEnable; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } void mcxw_set_pan_coord(bool aEnable) { macToPlmeMessage_t msg; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibPanCoordinator_c; msg.msgData.setReq.PibAttributeValue = (uint64_t)aEnable; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static int mcxw_energy_scan(const struct device *dev, uint16_t duration, energy_scan_done_cb_t done_cb) { int status = 0; phyStatus_t phy_status; macToPlmeMessage_t msg; app_disallow_device_to_slepp(); struct mcxw_context *mcxw_radio = dev->data; __ASSERT_NO_MSG(((mcxw_radio->state != RADIO_STATE_TRANSMIT) && (mcxw_radio->state != RADIO_STATE_DISABLED))); rf_abort(); rf_set_channel(mcxw_radio->channel); mcxw_radio->energy_scan_done = done_cb; msg.msgType = gPlmeEdReq_c; msg.msgData.edReq.startTime = gPhySeqStartAsap_c; msg.msgData.edReq.measureDurationSym = duration * 1000; phy_status = MAC_PLME_SapHandler(&msg, ot_phy_ctx); if (phy_status != gPhySuccess_c) { mcxw_radio->energy_scan_done = NULL; status = -EIO; } return status; } static int mcxw_set_txpower(const struct device *dev, int16_t dbm) { struct mcxw_context *mcxw_radio = dev->data; LOG_DBG("%d", dbm); if (dbm != mcxw_radio->tx_pwr_lvl) { /* Set Power level for TX */ rf_set_tx_power(dbm); mcxw_radio->tx_pwr_lvl = dbm; } return 0; } static void mcxw_configure_enh_ack_probing(const struct ieee802154_config *config) { uint32_t ie_param = 0; macToPlmeMessage_t msg; uint8_t *header_ie_buf = (uint8_t *)(config->ack_ie.header_ie); ie_param = (header_ie_buf[6] == 0x03 ? IeData_Lqi_c : 0) | (header_ie_buf[7] == 0x02 ? IeData_LinkMargin_c : 0) | (header_ie_buf[8] == 0x01 ? IeData_Rssi_c : 0); msg.msgType = gPlmeConfigureAckIeData_c; msg.msgData.AckIeData.param = (ie_param > 0 ? IeData_MSB_VALID_DATA : 0); msg.msgData.AckIeData.param |= ie_param; msg.msgData.AckIeData.shortAddr = config->ack_ie.short_addr; memcpy(msg.msgData.AckIeData.extAddr, config->ack_ie.ext_addr, 8); memcpy(msg.msgData.AckIeData.data, config->ack_ie.header_ie, config->ack_ie.header_ie->length); (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static void mcxw_set_mac_key(struct ieee802154_key *mac_keys) { macToPlmeMessage_t msg; __ASSERT_NO_MSG(mac_keys); __ASSERT_NO_MSG(mac_keys[0].key_id && mac_keys[0].key_value); __ASSERT_NO_MSG(mac_keys[1].key_id && mac_keys[1].key_value); __ASSERT_NO_MSG(mac_keys[2].key_id && mac_keys[2].key_value); msg.msgType = gPlmeSetMacKey_c; msg.msgData.MacKeyData.keyId = *(mac_keys[1].key_id); memcpy(msg.msgData.MacKeyData.prevKey, mac_keys[0].key_value, 16); memcpy(msg.msgData.MacKeyData.currKey, mac_keys[1].key_value, 16); memcpy(msg.msgData.MacKeyData.nextKey, mac_keys[2].key_value, 16); (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } void mcxw_set_mac_frame_counter(uint32_t frame_counter) { macToPlmeMessage_t msg; msg.msgType = gPlmeSetMacFrameCounter_c; msg.msgData.MacFrameCounter = frame_counter; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } void mcxw_set_mac_frame_counter_if_larger(uint32_t frame_counter) { macToPlmeMessage_t msg; msg.msgType = gPlmeSetMacFrameCounterIfLarger_c; msg.msgData.MacFrameCounter = frame_counter; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } #if CONFIG_IEEE802154_CSL_ENDPOINT static void mcxw_receive_at(uint8_t channel, uint32_t start, uint32_t duration) { macToPlmeMessage_t msg; __ASSERT_NO_MSG(mcxw_ctx.state == RADIO_STATE_SLEEP); mcxw_ctx.state = RADIO_STATE_RECEIVE; /* checks internally if the channel needs to be changed */ rf_set_channel(mcxw_ctx.channel); start = rf_adjust_tstamp_from_app(start); msg.msgType = gPlmeSetTRxStateReq_c; msg.msgData.setTRxStateReq.slottedMode = gPhyUnslottedMode_c; msg.msgData.setTRxStateReq.state = gPhySetRxOn_c; msg.msgData.setTRxStateReq.rxDuration = duration / IEEE802154_SYMBOL_TIME_US; msg.msgData.setTRxStateReq.startTime = start / IEEE802154_SYMBOL_TIME_US; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static void mcxw_enable_csl(uint16_t period) { mcxw_ctx.csl_period = period; macToPlmeMessage_t msg; msg.msgType = gPlmeCslEnable_c; msg.msgData.cslPeriod = period; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static void set_csl_sample_time(void) { if (!mcxw_ctx.csl_period) { return; } macToPlmeMessage_t msg; uint32_t csl_period = mcxw_ctx.csl_period * 10 * IEEE802154_SYMBOL_TIME_US; uint32_t dt = mcxw_ctx.csl_sample_time - (uint32_t)(mcxw_get_time(NULL) / NSEC_PER_USEC); /* next channel sample should be in the future */ while ((dt <= CMP_OVHD) || (dt > (CMP_OVHD + 2 * csl_period))) { mcxw_ctx.csl_sample_time += csl_period; dt = mcxw_ctx.csl_sample_time - (uint32_t)(mcxw_get_time(NULL) / NSEC_PER_USEC); } /* The CSL sample time is in microseconds and PHY function expects also microseconds */ msg.msgType = gPlmeCslSetSampleTime_c; msg.msgData.cslSampleTime = rf_adjust_tstamp_from_app(mcxw_ctx.csl_sample_time); (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static void start_csl_receiver(void) { if (!mcxw_ctx.csl_period) { return; } /* NBU has to be awake during CSL receiver trx so that conversion from * PHY timebase (NBU) to TMR timebase (host) is valid */ if (!csl_rx) { PLATFORM_RemoteActiveReq(); csl_rx = TRUE; } /* sample time is converted to PHY time */ set_csl_sample_time(); } static void stop_csl_receiver(void) { if (csl_rx) { PLATFORM_RemoteActiveRel(); csl_rx = FALSE; } } /* * Compute the CSL Phase for the time_us - i.e. the time from the time_us to * mcxw_ctx.csl_sample_time. The assumption is that mcxw_ctx.csl_sample_time > time_us. Since the * time is kept with a limited timer in reality it means that sometimes mcxw_ctx.csl_sample_time < * time_us, when the timer overflows. Therefore the formula should be: * * if (time_us <= mcxw_ctx.csl_sample_time) * csl_phase_us = mcxw_ctx.csl_sample_time - time_us; * else * csl_phase_us = MAX_TIMER_VALUE - time_us + mcxw_ctx.csl_sample_time; * * For simplicity the formula below has been used. */ static uint16_t rf_compute_csl_phase(uint32_t time_us) { /* convert CSL Period in microseconds - it was given in 10 symbols */ uint32_t csl_period_us = mcxw_ctx.csl_period * 10 * IEEE802154_SYMBOL_TIME_US; uint32_t csl_phase_us = (csl_period_us - (time_us % csl_period_us) + (mcxw_ctx.csl_sample_time % csl_period_us)) % csl_period_us; return (uint16_t)(csl_phase_us / (10 * IEEE802154_SYMBOL_TIME_US) + 1); } #endif /* CONFIG_IEEE802154_CSL_ENDPOINT */ /*************************************************************************************************/ static void rf_abort(void) { macToPlmeMessage_t msg; sun_rx_mode = RX_ON_IDLE_START; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibRxOnWhenIdle; msg.msgData.setReq.PibAttributeValue = (uint64_t)0; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); msg.msgType = gPlmeSetTRxStateReq_c; msg.msgData.setTRxStateReq.state = gPhyForceTRxOff_c; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static void rf_set_channel(uint8_t channel) { macToPlmeMessage_t msg; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibCurrentChannel_c; msg.msgData.setReq.PibAttributeValue = (uint64_t)channel; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); } static int mcxw_cca(const struct device *dev) { macToPlmeMessage_t msg; phyStatus_t phy_status; struct mcxw_context *mcxw_radio = dev->data; msg.msgType = gPlmeCcaReq_c; msg.msgData.ccaReq.ccaType = gPhyCCAMode1_c; msg.msgData.ccaReq.contCcaMode = gPhyContCcaDisabled; phy_status = MAC_PLME_SapHandler(&msg, ot_phy_ctx); __ASSERT_NO_MSG(phy_status == gPhySuccess_c); k_sem_take(&mcxw_radio->cca_wait, K_FOREVER); return (mcxw_radio->tx_status > 0) ? -EBUSY : 0; } static int mcxw_set_channel(const struct device *dev, uint16_t channel) { struct mcxw_context *mcxw_radio = dev->data; LOG_DBG("%u", channel); if (channel != mcxw_radio->channel) { if (channel < 11 || channel > 26) { return channel < 11 ? -ENOTSUP : -EINVAL; } mcxw_radio->channel = channel; } return 0; } static net_time_t mcxw_get_time(const struct device *dev) { static uint64_t sw_timestamp; static uint64_t hw_timestamp; ARG_UNUSED(dev); /* Get new 32bit HW timestamp */ uint32_t ticks; uint64_t hw_timestamp_new; uint64_t wrapped_val = 0; uint64_t increment; unsigned int key; key = irq_lock(); if (counter_get_value(mcxw_ctx.counter, &ticks)) { irq_unlock(key); return -1; } hw_timestamp_new = counter_ticks_to_us(mcxw_ctx.counter, ticks); /* Check if the timestamp has wrapped around */ if (hw_timestamp > hw_timestamp_new) { wrapped_val = COUNT_TO_USEC(((uint64_t)1 << 32), counter_get_frequency(mcxw_ctx.counter)); } increment = (hw_timestamp_new + wrapped_val) - hw_timestamp; sw_timestamp += increment; /* Store new HW timestamp for next iteration */ hw_timestamp = hw_timestamp_new; irq_unlock(key); return (net_time_t)sw_timestamp * NSEC_PER_USEC; } static void rf_set_tx_power(int8_t tx_power) { macToPlmeMessage_t msg; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibTransmitPower_c; msg.msgData.setReq.PibAttributeValue = (uint64_t)tx_power; MAC_PLME_SapHandler(&msg, ot_phy_ctx); } /* Used to convert from phy clock timestamp (in symbols) to platform time (in us) * the reception timestamp which must use a true 64bit timestamp source */ static uint64_t rf_adjust_tstamp_from_phy(uint64_t ts) { uint64_t now = PhyTime_ReadClock(); uint64_t delta; delta = (now >= ts) ? (now - ts) : ((PHY_TMR_MAX_VALUE + now) - ts); delta *= IEEE802154_SYMBOL_TIME_US; return (mcxw_get_time(NULL) / NSEC_PER_USEC) - delta; } #if CONFIG_IEEE802154_CSL_ENDPOINT || CONFIG_NET_PKT_TXTIME static uint32_t rf_adjust_tstamp_from_app(uint32_t time) { /* The phy timestamp is in symbols so we need to convert it to microseconds */ uint64_t ts = PhyTime_ReadClock() * IEEE802154_SYMBOL_TIME_US; uint32_t delta = time - (uint32_t)(mcxw_get_time(NULL) / NSEC_PER_USEC); return (uint32_t)(ts + delta); } #endif /* CONFIG_IEEE802154_CSL_ENDPOINT || CONFIG_NET_PKT_TXTIME */ /* Phy Data Service Access Point handler * Called by Phy to notify when Tx has been done or Rx data is available */ phyStatus_t pd_mac_sap_handler(void *msg, instanceId_t instance) { pdDataToMacMessage_t *data_msg = (pdDataToMacMessage_t *)msg; __ASSERT_NO_MSG(msg != NULL); /* PWR_DisallowDeviceToSleep(); */ switch (data_msg->msgType) { case gPdDataCnf_c: /* TX is done */ #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 if (is_keyid_mode_1(mcxw_ctx.tx_frame.psdu, mcxw_ctx.tx_frame.length) && !mcxw_ctx.tx_frame.sec_processed && !mcxw_ctx.tx_frame.hdr_updated) { set_frame_counter(mcxw_ctx.tx_frame.psdu, mcxw_ctx.tx_frame.length, data_msg->fc); mcxw_ctx.tx_frame.hdr_updated = true; } #endif mcxw_ctx.tx_frame.length = 0; mcxw_ctx.tx_status = 0; mcxw_ctx.state = RADIO_STATE_RECEIVE; mcxw_ctx.rx_ack_frame.channel = mcxw_ctx.channel; mcxw_ctx.rx_ack_frame.length = data_msg->msgData.dataCnf.ackLength; mcxw_ctx.rx_ack_frame.lqi = data_msg->msgData.dataCnf.ppduLinkQuality; mcxw_ctx.rx_ack_frame.rssi = data_msg->msgData.dataCnf.ppduRssi; mcxw_ctx.rx_ack_frame.timestamp = data_msg->msgData.dataCnf.timeStamp; memcpy(mcxw_ctx.rx_ack_frame.psdu, data_msg->msgData.dataCnf.ackData, mcxw_ctx.rx_ack_frame.length); k_sem_give(&mcxw_ctx.tx_wait); k_free(msg); break; case gPdDataInd_c: /* RX is done */ struct mcxw_rx_frame rx_frame; /* retrieve frame information and data */ rx_frame.lqi = data_msg->msgData.dataInd.ppduLinkQuality; rx_frame.rssi = data_msg->msgData.dataInd.ppduRssi; rx_frame.timestamp = rf_adjust_tstamp_from_phy(data_msg->msgData.dataInd.timeStamp); rx_frame.ack_fpb = data_msg->msgData.dataInd.rxAckFp; rx_frame.length = data_msg->msgData.dataInd.psduLength; rx_frame.psdu = data_msg->msgData.dataInd.pPsdu; rx_frame.ack_seb = data_msg->msgData.dataInd.ackedWithSecEnhAck; rx_frame.phy_buffer = (void *)msg; /* stop rx on idle if message queue is almost full */ if (k_msgq_num_free_get(&mcxw_ctx.rx_msgq) == 1) { rf_rx_on_idle(RX_ON_IDLE_STOP); } /* add the rx message in queue */ if (k_msgq_put(&mcxw_ctx.rx_msgq, &rx_frame, K_NO_WAIT) < 0) { LOG_ERR("Failed to push RX data to message queue"); } break; default: /* PWR_AllowDeviceToSleep(); */ break; } stop_csl_receiver(); return gPhySuccess_c; } /* Phy Layer Management Entities Service Access Point handler * Called by Phy to notify PLME event */ phyStatus_t plme_mac_sap_handler(void *msg, instanceId_t instance) { plmeToMacMessage_t *plme_msg = (plmeToMacMessage_t *)msg; __ASSERT_NO_MSG(msg != NULL); /* PWR_DisallowDeviceToSleep(); */ switch (plme_msg->msgType) { case gPlmeCcaCnf_c: if (plme_msg->msgData.ccaCnf.status == gPhyChannelBusy_c) { /* Channel is busy */ mcxw_ctx.tx_status = EBUSY; } else { mcxw_ctx.tx_status = 0; } mcxw_ctx.state = RADIO_STATE_RECEIVE; k_sem_give(&mcxw_ctx.cca_wait); break; case gPlmeEdCnf_c: /* Scan done */ if (mcxw_ctx.energy_scan_done != NULL) { energy_scan_done_cb_t callback = mcxw_ctx.energy_scan_done; mcxw_ctx.max_ed = plme_msg->msgData.edCnf.maxEnergyLeveldB; mcxw_ctx.energy_scan_done = NULL; callback(net_if_get_device(mcxw_ctx.iface), mcxw_ctx.max_ed); } break; case gPlmeTimeoutInd_c: if (RADIO_STATE_TRANSMIT == mcxw_ctx.state) { /* Ack timeout */ #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 if (is_keyid_mode_1(mcxw_ctx.tx_frame.psdu, mcxw_ctx.tx_frame.length) && !mcxw_ctx.tx_frame.sec_processed && !mcxw_ctx.tx_frame.hdr_updated) { set_frame_counter(mcxw_ctx.tx_frame.psdu, mcxw_ctx.tx_frame.length, plme_msg->fc); mcxw_ctx.tx_frame.hdr_updated = true; } #endif mcxw_ctx.state = RADIO_STATE_RECEIVE; /* No ack */ mcxw_ctx.tx_status = ENOMSG; k_sem_give(&mcxw_ctx.tx_wait); } else if (RADIO_STATE_RECEIVE == mcxw_ctx.state) { /* CSL Receive AT state has ended with timeout and we are returning to SLEEP * state */ mcxw_ctx.state = RADIO_STATE_SLEEP; /* PWR_AllowDeviceToSleep(); */ } break; case gPlmeAbortInd_c: /* TX Packet was loaded into TX Packet RAM but the TX/TR seq did not ended ok */ #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 if (is_keyid_mode_1(mcxw_ctx.tx_frame.psdu, mcxw_ctx.tx_frame.length) && !mcxw_ctx.tx_frame.sec_processed && !mcxw_ctx.tx_frame.hdr_updated) { set_frame_counter(mcxw_ctx.tx_frame.psdu, mcxw_ctx.tx_frame.length, plme_msg->fc); mcxw_ctx.tx_frame.hdr_updated = true; } #endif mcxw_ctx.state = RADIO_STATE_RECEIVE; mcxw_ctx.tx_status = EIO; k_sem_give(&mcxw_ctx.tx_wait); break; default: /* PWR_AllowDeviceToSleep(); */ break; } /* The message has been allocated by the Phy, we have to free it */ k_free(msg); stop_csl_receiver(); return gPhySuccess_c; } static int mcxw_configure(const struct device *dev, enum ieee802154_config_type type, const struct ieee802154_config *config) { ARG_UNUSED(dev); switch (type) { case IEEE802154_CONFIG_AUTO_ACK_FPB: if (config->auto_ack_fpb.mode == IEEE802154_FPB_ADDR_MATCH_THREAD) { mcxw_enable_src_match(config->auto_ack_fpb.enabled); } /* TODO IEEE802154_FPB_ADDR_MATCH_ZIGBEE */ break; case IEEE802154_CONFIG_ACK_FPB: if (config->ack_fpb.enabled) { return mcxw_src_match_entry(config->ack_fpb.extended, config->ack_fpb.addr); } else { return mcxw_src_clear_entry(config->ack_fpb.extended, config->ack_fpb.addr); } /* TODO otPlatRadioClearSrcMatchShortEntries */ /* TODO otPlatRadioClearSrcMatchExtEntries */ break; case IEEE802154_CONFIG_PAN_COORDINATOR: mcxw_set_pan_coord(config->pan_coordinator); break; case IEEE802154_CONFIG_PROMISCUOUS: mcxw_set_promiscuous(config->promiscuous); break; case IEEE802154_CONFIG_MAC_KEYS: mcxw_set_mac_key(config->mac_keys); break; case IEEE802154_CONFIG_FRAME_COUNTER: mcxw_set_mac_frame_counter(config->frame_counter); break; case IEEE802154_CONFIG_FRAME_COUNTER_IF_LARGER: mcxw_set_mac_frame_counter_if_larger(config->frame_counter); break; case IEEE802154_CONFIG_ENH_ACK_HEADER_IE: mcxw_configure_enh_ack_probing(config); break; #if defined(CONFIG_IEEE802154_CSL_ENDPOINT) case IEEE802154_CONFIG_EXPECTED_RX_TIME: mcxw_ctx.csl_sample_time = config->expected_rx_time; break; case IEEE802154_CONFIG_RX_SLOT: mcxw_receive_at(config->rx_slot.channel, config->rx_slot.start / NSEC_PER_USEC, config->rx_slot.duration / NSEC_PER_USEC); break; case IEEE802154_CONFIG_CSL_PERIOD: mcxw_enable_csl(config->csl_period); break; #endif /* CONFIG_IEEE802154_CSL_ENDPOINT */ case IEEE802154_CONFIG_RX_ON_WHEN_IDLE: if (config->rx_on_when_idle) { rf_rx_on_idle(RX_ON_IDLE_START); } else { rf_rx_on_idle(RX_ON_IDLE_STOP); } break; case IEEE802154_CONFIG_EVENT_HANDLER: break; default: return -EINVAL; } return 0; } IEEE802154_DEFINE_PHY_SUPPORTED_CHANNELS(drv_attr, 11, 26); static int mcxw_attr_get(const struct device *dev, enum ieee802154_attr attr, struct ieee802154_attr_value *value) { ARG_UNUSED(dev); if (ieee802154_attr_get_channel_page_and_range( attr, IEEE802154_ATTR_PHY_CHANNEL_PAGE_ZERO_OQPSK_2450_BPSK_868_915, &drv_attr.phy_supported_channels, value) == 0) { return 0; } return -EIO; } static enum ieee802154_hw_caps mcxw_get_capabilities(const struct device *dev) { enum ieee802154_hw_caps caps; caps = IEEE802154_HW_FCS | IEEE802154_HW_PROMISC | IEEE802154_HW_FILTER | IEEE802154_HW_TX_RX_ACK | IEEE802154_HW_RX_TX_ACK | IEEE802154_HW_ENERGY_SCAN | IEEE802154_HW_TXTIME | IEEE802154_HW_RXTIME | IEEE802154_HW_SLEEP_TO_TX | IEEE802154_RX_ON_WHEN_IDLE | IEEE802154_HW_TX_SEC | IEEE802154_HW_SELECTIVE_TXCHANNEL; return caps; } static int mcxw_init(const struct device *dev) { struct mcxw_context *mcxw_radio = dev->data; macToPlmeMessage_t msg; if (PLATFORM_InitOT() < 0) { return -EIO; } Phy_Init(); ot_phy_ctx = PHY_get_ctx(); /* Register Phy Data Service Access Point and Phy Layer Management Entities Service Access * Point handlers */ Phy_RegisterSapHandlers((PD_MAC_SapHandler_t)pd_mac_sap_handler, (PLME_MAC_SapHandler_t)plme_mac_sap_handler, ot_phy_ctx); msg.msgType = gPlmeEnableEncryption_c; (void)MAC_PLME_SapHandler(&msg, ot_phy_ctx); mcxw_radio->state = RADIO_STATE_DISABLED; mcxw_radio->energy_scan_done = NULL; mcxw_radio->channel = DEFAULT_CHANNEL; rf_set_channel(mcxw_radio->channel); mcxw_radio->tx_frame.length = 0; /* Make the psdu point to the space after macToPdDataMessage_t in the data buffer */ mcxw_radio->tx_frame.psdu = mcxw_radio->tx_data + sizeof(macToPdDataMessage_t); /* Get and start LPTRM counter */ mcxw_radio->counter = DEVICE_DT_GET(DT_NODELABEL(lptmr0)); if (counter_start(mcxw_radio->counter)) { return -EIO; } /* Init TX semaphore */ k_sem_init(&mcxw_radio->tx_wait, 0, 1); /* Init CCA semaphore */ k_sem_init(&mcxw_radio->cca_wait, 0, 1); /* Init RX message queue */ k_msgq_init(&mcxw_radio->rx_msgq, mcxw_radio->rx_msgq_buffer, sizeof(mcxw_rx_frame), NMAX_RXRING_BUFFERS); memset(&(mcxw_radio->rx_ack_frame), 0, sizeof(mcxw_radio->rx_ack_frame)); mcxw_radio->rx_ack_frame.psdu = mcxw_radio->rx_ack_data; k_thread_create(&mcxw_radio->rx_thread, mcxw_radio->rx_stack, CONFIG_IEEE802154_MCXW_RX_STACK_SIZE, mcxw_rx_thread, mcxw_radio, NULL, NULL, K_PRIO_COOP(2), 0, K_NO_WAIT); k_thread_name_set(&mcxw_radio->rx_thread, "mcxw_rx"); return 0; } static void mcxw_iface_init(struct net_if *iface) { const struct device *dev = net_if_get_device(iface); struct mcxw_context *mcxw_radio = dev->data; mcxw_get_eui64(mcxw_radio->mac); net_if_set_link_addr(iface, mcxw_radio->mac, sizeof(mcxw_radio->mac), NET_LINK_IEEE802154); mcxw_radio->iface = iface; ieee802154_init(iface); } static void rf_rx_on_idle(uint32_t new_val) { macToPlmeMessage_t msg; phyStatus_t phy_status; new_val %= 2; if (sun_rx_mode != new_val) { sun_rx_mode = new_val; msg.msgType = gPlmeSetReq_c; msg.msgData.setReq.PibAttribute = gPhyPibRxOnWhenIdle; msg.msgData.setReq.PibAttributeValue = (uint64_t)sun_rx_mode; phy_status = MAC_PLME_SapHandler(&msg, ot_phy_ctx); __ASSERT_NO_MSG(phy_status == gPhySuccess_c); } } static const struct ieee802154_radio_api mcxw71_radio_api = { .iface_api.init = mcxw_iface_init, .get_capabilities = mcxw_get_capabilities, .cca = mcxw_cca, .set_channel = mcxw_set_channel, .filter = mcxw_filter, .set_txpower = mcxw_set_txpower, .start = mcxw_start, .stop = mcxw_stop, .configure = mcxw_configure, .tx = mcxw_tx, .ed_scan = mcxw_energy_scan, .get_time = mcxw_get_time, .get_sch_acc = mcxw_get_acc, .attr_get = mcxw_attr_get, }; #if defined(CONFIG_NET_L2_IEEE802154) #define L2 IEEE802154_L2 #define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(IEEE802154_L2) #define MTU IEEE802154_MTU #elif defined(CONFIG_NET_L2_OPENTHREAD) #define L2 OPENTHREAD_L2 #define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(OPENTHREAD_L2) #define MTU 1280 #elif defined(CONFIG_NET_L2_CUSTOM_IEEE802154) #define L2 CUSTOM_IEEE802154_L2 #define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(CUSTOM_IEEE802154_L2) #define MTU CONFIG_NET_L2_CUSTOM_IEEE802154_MTU #endif #if defined(CONFIG_NET_L2_PHY_IEEE802154) NET_DEVICE_DT_INST_DEFINE(0, mcxw_init, NULL, &mcxw_ctx, NULL, CONFIG_IEEE802154_MCXW_INIT_PRIO, &mcxw71_radio_api, L2, L2_CTX_TYPE, MTU); #else DEVICE_DT_INST_DEFINE(0, mcxw_init, NULL, &mcxw_ctx, NULL, POST_KERNEL, CONFIG_IEEE802154_MCXW_INIT_PRIO, &mcxw71_radio_api); #endif