|  | /* ieee802154_kw41z.c - NXP KW41Z driver */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2017 Linaro Limited | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define LOG_MODULE_NAME ieee802154_kw41z | 
|  | #define LOG_LEVEL CONFIG_IEEE802154_DRIVER_LOG_LEVEL | 
|  |  | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(LOG_MODULE_NAME); | 
|  |  | 
|  | #include <zephyr.h> | 
|  | #include <kernel.h> | 
|  | #include <device.h> | 
|  | #include <init.h> | 
|  | #include <irq.h> | 
|  | #include <net/ieee802154_radio.h> | 
|  | #include <net/net_if.h> | 
|  | #include <net/net_pkt.h> | 
|  | #include <sys/byteorder.h> | 
|  | #include <random/rand32.h> | 
|  |  | 
|  | #include "fsl_xcvr.h" | 
|  |  | 
|  | #if defined(CONFIG_NET_L2_OPENTHREAD) | 
|  | #include <net/openthread.h> | 
|  | #endif | 
|  |  | 
|  |  | 
|  | /* | 
|  | * For non-invasive tracing of IRQ events. Sometimes the print logs | 
|  | * will shift the timings around so this trace buffer can be used to | 
|  | * post inspect conditions to see what sequence of events occurred. | 
|  | */ | 
|  |  | 
|  | #define KW41_DBG_TRACE_WTRM	0 | 
|  | #define KW41_DBG_TRACE_RX	1 | 
|  | #define KW41_DBG_TRACE_TX	2 | 
|  | #define KW41_DBG_TRACE_CCA	3 | 
|  | #define KW41_DBG_TRACE_TMR3	0xFF | 
|  |  | 
|  | #if defined(CONFIG_KW41_DBG_TRACE) | 
|  |  | 
|  | #define KW41_DBG_TRACE_SIZE 30 | 
|  |  | 
|  | struct kw41_dbg_trace { | 
|  | uint8_t	type; | 
|  | uint32_t	time; | 
|  | uint32_t	irqsts; | 
|  | uint32_t	phy_ctrl; | 
|  | uint32_t	seq_state; | 
|  | }; | 
|  |  | 
|  | struct kw41_dbg_trace kw41_dbg[KW41_DBG_TRACE_SIZE]; | 
|  | int kw41_dbg_idx; | 
|  |  | 
|  | #define KW_DBG_TRACE(_type, _irqsts, _phy_ctrl, _seq_state) \ | 
|  | do { \ | 
|  | kw41_dbg[kw41_dbg_idx].type = (_type); \ | 
|  | kw41_dbg[kw41_dbg_idx].time = \ | 
|  | ZLL->EVENT_TMR >> ZLL_EVENT_TMR_EVENT_TMR_SHIFT; \ | 
|  | kw41_dbg[kw41_dbg_idx].irqsts = (_irqsts); \ | 
|  | kw41_dbg[kw41_dbg_idx].phy_ctrl = (_phy_ctrl); \ | 
|  | kw41_dbg[kw41_dbg_idx].seq_state = (_seq_state); \ | 
|  | if (++kw41_dbg_idx == KW41_DBG_TRACE_SIZE) { \ | 
|  | kw41_dbg_idx = 0; \ | 
|  | } \ | 
|  | } while (0) | 
|  |  | 
|  | #else | 
|  |  | 
|  | #define KW_DBG_TRACE(_type, _irqsts, _phy_ctrl, _seq_state) | 
|  |  | 
|  | #endif | 
|  |  | 
|  | #define KW41Z_DEFAULT_CHANNEL		26 | 
|  | #define KW41Z_CCA_TIME			8 | 
|  | #define KW41Z_SHR_PHY_TIME		12 | 
|  | #define KW41Z_PER_BYTE_TIME		2 | 
|  | #define KW41Z_ACK_WAIT_TIME		54 | 
|  | #define KW41Z_PRE_RX_WAIT_TIME		1 | 
|  | #define KW40Z_POST_SEQ_WAIT_TIME	1 | 
|  |  | 
|  | #define RADIO_0_IRQ_PRIO		0x0 | 
|  | #define KW41Z_FCS_LENGTH		2 | 
|  | #define KW41Z_PSDU_LENGTH		125 | 
|  | #define KW41Z_OUTPUT_POWER_MAX		4 | 
|  | #define KW41Z_OUTPUT_POWER_MIN		(-31) | 
|  |  | 
|  | #define IEEE802154_ACK_LENGTH		5 | 
|  |  | 
|  | #define BM_ZLL_IRQSTS_TMRxMSK (ZLL_IRQSTS_TMR1MSK_MASK | \ | 
|  | ZLL_IRQSTS_TMR2MSK_MASK | \ | 
|  | ZLL_IRQSTS_TMR3MSK_MASK | \ | 
|  | ZLL_IRQSTS_TMR4MSK_MASK) | 
|  |  | 
|  | /* | 
|  | * Clear channel assessment types. Note that there is an extra one when | 
|  | * bit 26 is included for "No CCA before transmit" if we are handling | 
|  | * ACK frames but we will let the hardware handle that automatically. | 
|  | */ | 
|  | enum { | 
|  | KW41Z_CCA_ED,       /* Energy detect */ | 
|  | KW41Z_CCA_MODE1,    /* Energy above threshold   */ | 
|  | KW41Z_CCA_MODE2,    /* Carrier sense only       */ | 
|  | KW41Z_CCA_MODE3     /* Mode 1 + Mode 2          */ | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * KW41Z has a sequencer that can run in any of the following states. | 
|  | */ | 
|  | enum { | 
|  | KW41Z_STATE_IDLE, | 
|  | KW41Z_STATE_RX, | 
|  | KW41Z_STATE_TX, | 
|  | KW41Z_STATE_CCA, | 
|  | KW41Z_STATE_TXRX, | 
|  | KW41Z_STATE_CCCA | 
|  | }; | 
|  |  | 
|  | /* Lookup table for PA_PWR register */ | 
|  | static const uint8_t pa_pwr_lt[] = { | 
|  | 1,                   /* -31.1 dBm: -31 */ | 
|  | 2, 2, 2, 2, 2, 2, 2, /* -25.0 dBm: -30, -29, -28, -27, -26, -25 */ | 
|  | 4, 4, 4, 4, 4,       /* -19.0 dBm: -24, -23, -22, -21, -20, -19 */ | 
|  | 6, 6, 6,             /* -15.6 dBm: -18, -17, -16 */ | 
|  | 8, 8,                /* -13.1 dBm: -15, -14 */ | 
|  | 10, 10,              /* -11.2 dBm: -13, -12 */ | 
|  | 12, 12,              /* - 9.6 dBm: -11, -10 */ | 
|  | 14,                  /* - 8.3 dBm: -9 */ | 
|  | 16,                  /* - 7.2 dBm: -8 */ | 
|  | 18,                  /* - 6.2 dBm: -7 */ | 
|  | 20,                  /* - 5.3 dBm: -6 */ | 
|  | 22,                  /* - 4.5 dBm: -5 */ | 
|  | 24,                  /* - 3.8 dBm: -4 */ | 
|  | 28,                  /* - 2.5 dBm: -3 */ | 
|  | 30,                  /* - 1.9 dBm: -2 */ | 
|  | 34,                  /* - 1.0 dBm: -1 */ | 
|  | 40,                  /* + 0.3 dBm:  0 */ | 
|  | 44,                  /* + 1.1 dBm: +1 */ | 
|  | 50,                  /* + 2.1 dBm: +2 */ | 
|  | 58,                  /* + 3.1 dBm: +3 */ | 
|  | 62                   /* + 3.5 dBm: +4 */ | 
|  | }; | 
|  |  | 
|  | struct kw41z_context { | 
|  | struct net_if *iface; | 
|  | uint8_t mac_addr[8]; | 
|  |  | 
|  | struct k_sem seq_sync; | 
|  | atomic_t seq_retval; | 
|  |  | 
|  | uint32_t rx_warmup_time; | 
|  | uint32_t tx_warmup_time; | 
|  |  | 
|  | bool frame_pending; /* FP bit state from the most recent ACK frame. */ | 
|  | }; | 
|  |  | 
|  | static struct kw41z_context kw41z_context_data; | 
|  |  | 
|  | static inline uint8_t kw41z_get_instant_state(void) | 
|  | { | 
|  | return (ZLL->SEQ_STATE & ZLL_SEQ_STATE_SEQ_STATE_MASK) >> | 
|  | ZLL_SEQ_STATE_SEQ_STATE_SHIFT; | 
|  | } | 
|  |  | 
|  | static inline uint8_t kw41z_get_seq_state(void) | 
|  | { | 
|  | return (ZLL->PHY_CTRL & ZLL_PHY_CTRL_XCVSEQ_MASK) >> | 
|  | ZLL_PHY_CTRL_XCVSEQ_SHIFT; | 
|  | } | 
|  |  | 
|  | static inline void kw41z_set_seq_state(uint8_t state) | 
|  | { | 
|  | #if CONFIG_SOC_MKW40Z4 | 
|  | /* | 
|  | * KW40Z seems to require a small delay when switching to IDLE state | 
|  | * after a programmed sequence is complete. | 
|  | */ | 
|  | if (state == KW41Z_STATE_IDLE) { | 
|  | k_busy_wait(KW40Z_POST_SEQ_WAIT_TIME); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | ZLL->PHY_CTRL = (ZLL->PHY_CTRL & ~ZLL_PHY_CTRL_XCVSEQ_MASK) | | 
|  | ZLL_PHY_CTRL_XCVSEQ(state); | 
|  | } | 
|  |  | 
|  | static inline void kw41z_wait_for_idle(void) | 
|  | { | 
|  | uint8_t state = kw41z_get_instant_state(); | 
|  |  | 
|  | while (state != KW41Z_STATE_IDLE) { | 
|  | state = kw41z_get_instant_state(); | 
|  | } | 
|  |  | 
|  | if (state != KW41Z_STATE_IDLE) { | 
|  | LOG_ERR("Error waiting for idle state"); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void kw41z_phy_abort(void) | 
|  | { | 
|  | int key; | 
|  |  | 
|  | key = irq_lock(); | 
|  |  | 
|  | /* Mask SEQ interrupt */ | 
|  | ZLL->PHY_CTRL |= ZLL_PHY_CTRL_SEQMSK_MASK; | 
|  | /* Disable timer trigger (for scheduled XCVSEQ) */ | 
|  | if (ZLL->PHY_CTRL & ZLL_PHY_CTRL_TMRTRIGEN_MASK) { | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_TMRTRIGEN_MASK; | 
|  | /* give the FSM enough time to start if it was triggered */ | 
|  | while ((XCVR_MISC->XCVR_CTRL & | 
|  | XCVR_CTRL_XCVR_STATUS_TSM_COUNT_MASK) == 0) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /* If XCVR is not idle, abort current SEQ */ | 
|  | if (ZLL->PHY_CTRL & ZLL_PHY_CTRL_XCVSEQ_MASK) { | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_XCVSEQ_MASK; | 
|  | /* wait for Sequence Idle (if not already) */ | 
|  |  | 
|  | while (ZLL->SEQ_STATE & ZLL_SEQ_STATE_SEQ_STATE_MASK) { | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Stop timers */ | 
|  | ZLL->PHY_CTRL &= ~(ZLL_PHY_CTRL_TMR1CMP_EN_MASK | | 
|  | ZLL_PHY_CTRL_TMR2CMP_EN_MASK | | 
|  | ZLL_PHY_CTRL_TMR3CMP_EN_MASK | | 
|  | ZLL_PHY_CTRL_TC3TMOUT_MASK); | 
|  |  | 
|  | /* | 
|  | * Clear all IRQ bits to avoid unexpected interrupts. | 
|  | * | 
|  | * For Coverity, this is a pointer to a register bank and the IRQSTS | 
|  | * register bits get cleared when a 1 is written to them so doing a | 
|  | * reg=reg may generate a warning but it is needed to clear the bits. | 
|  | */ | 
|  | ZLL->IRQSTS = ZLL->IRQSTS; | 
|  |  | 
|  | irq_unlock(key); | 
|  | } | 
|  |  | 
|  | static void kw41z_isr_timeout_cleanup(void) | 
|  | { | 
|  | uint32_t irqsts; | 
|  |  | 
|  | /* | 
|  | * Set the PHY sequencer back to IDLE and disable TMR3 comparator | 
|  | * and timeout | 
|  | */ | 
|  | ZLL->PHY_CTRL &= ~(ZLL_PHY_CTRL_TMR3CMP_EN_MASK | | 
|  | ZLL_PHY_CTRL_TC3TMOUT_MASK   | | 
|  | ZLL_PHY_CTRL_XCVSEQ_MASK); | 
|  |  | 
|  | /* Mask SEQ, RX, TX and CCA interrupts */ | 
|  | ZLL->PHY_CTRL |= ZLL_PHY_CTRL_CCAMSK_MASK | | 
|  | ZLL_PHY_CTRL_RXMSK_MASK  | | 
|  | ZLL_PHY_CTRL_TXMSK_MASK  | | 
|  | ZLL_PHY_CTRL_SEQMSK_MASK; | 
|  |  | 
|  | while (ZLL->SEQ_STATE & ZLL_SEQ_STATE_SEQ_STATE_MASK) { | 
|  | } | 
|  |  | 
|  | irqsts = ZLL->IRQSTS; | 
|  | /* Mask TMR3 interrupt */ | 
|  | irqsts |= ZLL_IRQSTS_TMR3MSK_MASK; | 
|  |  | 
|  | ZLL->IRQSTS = irqsts; | 
|  | } | 
|  |  | 
|  | static void kw41z_isr_seq_cleanup(void) | 
|  | { | 
|  | uint32_t irqsts; | 
|  |  | 
|  | /* Set the PHY sequencer back to IDLE */ | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_XCVSEQ_MASK; | 
|  | /* Mask SEQ, RX, TX and CCA interrupts */ | 
|  | ZLL->PHY_CTRL |= ZLL_PHY_CTRL_CCAMSK_MASK | | 
|  | ZLL_PHY_CTRL_RXMSK_MASK  | | 
|  | ZLL_PHY_CTRL_TXMSK_MASK  | | 
|  | ZLL_PHY_CTRL_SEQMSK_MASK; | 
|  |  | 
|  | while (ZLL->SEQ_STATE & ZLL_SEQ_STATE_SEQ_STATE_MASK) { | 
|  | } | 
|  |  | 
|  | irqsts = ZLL->IRQSTS; | 
|  | /* Mask TMR3 interrupt */ | 
|  | irqsts |= ZLL_IRQSTS_TMR3MSK_MASK; | 
|  |  | 
|  | /* Clear transceiver interrupts except TMRxIRQ */ | 
|  | irqsts &= ~(ZLL_IRQSTS_TMR1IRQ_MASK | | 
|  | ZLL_IRQSTS_TMR2IRQ_MASK | | 
|  | ZLL_IRQSTS_TMR3IRQ_MASK | | 
|  | ZLL_IRQSTS_TMR4IRQ_MASK); | 
|  | ZLL->IRQSTS = irqsts; | 
|  | } | 
|  |  | 
|  | static inline void kw41z_enable_seq_irq(void) | 
|  | { | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_SEQMSK_MASK; | 
|  | } | 
|  |  | 
|  | static inline void kw41z_disable_seq_irq(void) | 
|  | { | 
|  | ZLL->PHY_CTRL |= ZLL_PHY_CTRL_SEQMSK_MASK; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Set the T3CMP timer comparator. The 'timeout' value is an offset from | 
|  | * now. | 
|  | */ | 
|  | static void kw41z_tmr3_set_timeout(uint32_t timeout) | 
|  | { | 
|  | uint32_t irqsts; | 
|  |  | 
|  | /* Add in the current time so that we can get the comparator to | 
|  | * match appropriately to our offset time. | 
|  | */ | 
|  | timeout += ZLL->EVENT_TMR >> ZLL_EVENT_TMR_EVENT_TMR_SHIFT; | 
|  |  | 
|  | /* disable TMR3 compare */ | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_TMR3CMP_EN_MASK; | 
|  | ZLL->T3CMP = timeout & ZLL_T3CMP_T3CMP_MASK; | 
|  |  | 
|  | /* acknowledge TMR3 IRQ */ | 
|  | irqsts  = ZLL->IRQSTS & BM_ZLL_IRQSTS_TMRxMSK; | 
|  | irqsts |= ZLL_IRQSTS_TMR3IRQ_MASK; | 
|  | ZLL->IRQSTS = irqsts; | 
|  | /* enable TMR3 compare and autosequence stop by TC3 match */ | 
|  | ZLL->PHY_CTRL |= | 
|  | (ZLL_PHY_CTRL_TMR3CMP_EN_MASK | ZLL_PHY_CTRL_TC3TMOUT_MASK); | 
|  | } | 
|  |  | 
|  | static void kw41z_tmr3_disable(void) | 
|  | { | 
|  | uint32_t irqsts; | 
|  |  | 
|  | /* | 
|  | * disable TMR3 compare and disable autosequence stop by TC3 | 
|  | * match | 
|  | */ | 
|  | ZLL->PHY_CTRL &= ~(ZLL_PHY_CTRL_TMR3CMP_EN_MASK | | 
|  | ZLL_PHY_CTRL_TC3TMOUT_MASK); | 
|  | /* mask TMR3 interrupt (do not change other IRQ status) */ | 
|  | irqsts  = ZLL->IRQSTS & BM_ZLL_IRQSTS_TMRxMSK; | 
|  | irqsts |= ZLL_IRQSTS_TMR3MSK_MASK; | 
|  | /* acknowledge TMR3 IRQ */ | 
|  | irqsts |= ZLL_IRQSTS_TMR3IRQ_MASK; | 
|  |  | 
|  | ZLL->IRQSTS = irqsts; | 
|  | } | 
|  |  | 
|  | static enum ieee802154_hw_caps kw41z_get_capabilities(const struct device *dev) | 
|  | { | 
|  | return IEEE802154_HW_FCS | | 
|  | IEEE802154_HW_2_4_GHZ | | 
|  | IEEE802154_HW_FILTER | | 
|  | IEEE802154_HW_TX_RX_ACK; | 
|  | } | 
|  |  | 
|  | static int kw41z_cca(const struct device *dev) | 
|  | { | 
|  | struct kw41z_context *kw41z = dev->data; | 
|  |  | 
|  | kw41z_phy_abort(); | 
|  |  | 
|  | k_sem_init(&kw41z->seq_sync, 0, 1); | 
|  |  | 
|  | kw41z_enable_seq_irq(); | 
|  | ZLL->PHY_CTRL = (ZLL->PHY_CTRL & ~ZLL_PHY_CTRL_CCATYPE_MASK) | | 
|  | ZLL_PHY_CTRL_CCATYPE(KW41Z_CCA_MODE1); | 
|  |  | 
|  | kw41z_set_seq_state(KW41Z_STATE_CCA); | 
|  |  | 
|  | k_sem_take(&kw41z->seq_sync, K_FOREVER); | 
|  |  | 
|  | return kw41z->seq_retval; | 
|  | } | 
|  |  | 
|  | static int kw41z_set_channel(const struct device *dev, uint16_t channel) | 
|  | { | 
|  | if (channel < 11 || channel > 26) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | ZLL->CHANNEL_NUM0 = channel; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kw41z_set_pan_id(const struct device *dev, uint16_t pan_id) | 
|  | { | 
|  | ZLL->MACSHORTADDRS0 = (ZLL->MACSHORTADDRS0 & | 
|  | ~ZLL_MACSHORTADDRS0_MACPANID0_MASK) | | 
|  | ZLL_MACSHORTADDRS0_MACPANID0(pan_id); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kw41z_set_short_addr(const struct device *dev, uint16_t short_addr) | 
|  | { | 
|  | ZLL->MACSHORTADDRS0 = (ZLL->MACSHORTADDRS0 & | 
|  | ~ZLL_MACSHORTADDRS0_MACSHORTADDRS0_MASK) | | 
|  | ZLL_MACSHORTADDRS0_MACSHORTADDRS0(short_addr); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kw41z_set_ieee_addr(const struct device *dev, | 
|  | const uint8_t *ieee_addr) | 
|  | { | 
|  | uint32_t val; | 
|  |  | 
|  | memcpy(&val, ieee_addr, sizeof(val)); | 
|  | ZLL->MACLONGADDRS0_LSB = val; | 
|  |  | 
|  | memcpy(&val, ieee_addr + sizeof(val), sizeof(val)); | 
|  | ZLL->MACLONGADDRS0_MSB = val; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kw41z_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 kw41z_set_ieee_addr(dev, filter->ieee_addr); | 
|  | } else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) { | 
|  | return kw41z_set_short_addr(dev, filter->short_addr); | 
|  | } else if (type == IEEE802154_FILTER_TYPE_PAN_ID) { | 
|  | return kw41z_set_pan_id(dev, filter->pan_id); | 
|  | } | 
|  |  | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | static int kw41z_set_txpower(const struct device *dev, int16_t dbm) | 
|  | { | 
|  | if (dbm < KW41Z_OUTPUT_POWER_MIN) { | 
|  | LOG_INF("TX-power %d dBm below min of %d dBm, using %d dBm", | 
|  | dbm, | 
|  | KW41Z_OUTPUT_POWER_MIN, | 
|  | KW41Z_OUTPUT_POWER_MIN); | 
|  | dbm = KW41Z_OUTPUT_POWER_MIN; | 
|  | } else if (dbm > KW41Z_OUTPUT_POWER_MAX) { | 
|  | LOG_INF("TX-power %d dBm above max of %d dBm, using %d dBm", | 
|  | dbm, | 
|  | KW41Z_OUTPUT_POWER_MAX, | 
|  | KW41Z_OUTPUT_POWER_MAX); | 
|  | dbm = KW41Z_OUTPUT_POWER_MAX; | 
|  | } | 
|  |  | 
|  | ZLL->PA_PWR = pa_pwr_lt[dbm - KW41Z_OUTPUT_POWER_MIN]; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kw41z_start(const struct device *dev) | 
|  | { | 
|  | irq_enable(Radio_1_IRQn); | 
|  |  | 
|  | kw41z_set_seq_state(KW41Z_STATE_RX); | 
|  | kw41z_enable_seq_irq(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int kw41z_stop(const struct device *dev) | 
|  | { | 
|  | irq_disable(Radio_1_IRQn); | 
|  |  | 
|  | kw41z_disable_seq_irq(); | 
|  | kw41z_set_seq_state(KW41Z_STATE_IDLE); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static uint8_t kw41z_convert_lqi(uint8_t hw_lqi) | 
|  | { | 
|  | if (hw_lqi >= 220U) { | 
|  | return 255; | 
|  | } else { | 
|  | return (hw_lqi * 51U) / 44; | 
|  | } | 
|  | } | 
|  |  | 
|  | static inline void kw41z_rx(struct kw41z_context *kw41z, uint8_t len) | 
|  | { | 
|  | struct net_pkt *pkt = NULL; | 
|  | struct net_buf *buf = NULL; | 
|  | uint8_t pkt_len, hw_lqi; | 
|  | int rslt; | 
|  |  | 
|  | LOG_DBG("ENTRY: len: %d", len); | 
|  |  | 
|  | #if defined(CONFIG_NET_L2_OPENTHREAD) | 
|  | /* | 
|  | * OpenThread stack expects a receive frame to include the FCS | 
|  | */ | 
|  | pkt_len = len; | 
|  | #else | 
|  | pkt_len = len - KW41Z_FCS_LENGTH; | 
|  | #endif | 
|  |  | 
|  | pkt = net_pkt_alloc_with_buffer(kw41z->iface, pkt_len, | 
|  | AF_UNSPEC, 0, K_NO_WAIT); | 
|  | if (!pkt) { | 
|  | LOG_ERR("No buf available"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | buf = pkt->buffer; | 
|  |  | 
|  | #if CONFIG_SOC_MKW41Z4 | 
|  | /* PKT_BUFFER_RX needs to be accessed aligned to 16 bits */ | 
|  | for (uint16_t reg_val = 0, i = 0; i < pkt_len; i++) { | 
|  | if (i % 2 == 0U) { | 
|  | reg_val = ZLL->PKT_BUFFER_RX[i/2U]; | 
|  | buf->data[i] = reg_val & 0xFF; | 
|  | } else { | 
|  | buf->data[i] = reg_val >> 8; | 
|  | } | 
|  | } | 
|  | #else /* CONFIG_SOC_MKW40Z4 */ | 
|  | /* PKT_BUFFER needs to be accessed aligned to 32 bits */ | 
|  | for (uint32_t reg_val = 0, i = 0; i < pkt_len; i++) { | 
|  | switch (i % 4) { | 
|  | case 0: | 
|  | reg_val = ZLL->PKT_BUFFER[i/4U]; | 
|  | buf->data[i] = reg_val & 0xFF; | 
|  | break; | 
|  | case 1: | 
|  | buf->data[i] = (reg_val >> 8) & 0xFF; | 
|  | break; | 
|  | case 2: | 
|  | buf->data[i] = (reg_val >> 16) & 0xFF; | 
|  | break; | 
|  | default: | 
|  | buf->data[i] = reg_val >> 24; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | net_buf_add(buf, pkt_len); | 
|  |  | 
|  | hw_lqi = (ZLL->LQI_AND_RSSI & ZLL_LQI_AND_RSSI_LQI_VALUE_MASK) >> | 
|  | ZLL_LQI_AND_RSSI_LQI_VALUE_SHIFT; | 
|  | net_pkt_set_ieee802154_lqi(pkt, kw41z_convert_lqi(hw_lqi)); | 
|  | /* ToDo: get the rssi as well and use net_pkt_set_ieee802154_rssi() */ | 
|  |  | 
|  | rslt = net_recv_data(kw41z->iface, pkt); | 
|  | if (rslt < 0) { | 
|  | LOG_ERR("RCV Packet dropped by NET stack: %d", rslt); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | return; | 
|  | out: | 
|  | if (pkt) { | 
|  | net_pkt_unref(pkt); | 
|  | } | 
|  | } | 
|  |  | 
|  | #define ACK_FRAME_LEN 3 | 
|  | #define ACK_FRAME_TYPE (2 << 0) | 
|  | #define ACK_FRAME_PENDING_BIT (1 << 4) | 
|  |  | 
|  | static void handle_ack(struct kw41z_context *kw41z, uint8_t seq_number) | 
|  | { | 
|  | struct net_pkt *ack_pkt; | 
|  | uint8_t ack_psdu[ACK_FRAME_LEN]; | 
|  |  | 
|  | ack_pkt = net_pkt_alloc_with_buffer(kw41z->iface, ACK_FRAME_LEN, | 
|  | AF_UNSPEC, 0, K_NO_WAIT); | 
|  | if (!ack_pkt) { | 
|  | LOG_ERR("No free packet available."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* Re-create ACK frame. */ | 
|  | ack_psdu[0] = kw41z_context_data.frame_pending ? | 
|  | ACK_FRAME_TYPE | ACK_FRAME_PENDING_BIT : ACK_FRAME_TYPE; | 
|  | ack_psdu[1] = 0; | 
|  | ack_psdu[2] = seq_number; | 
|  |  | 
|  | if (net_pkt_write(ack_pkt, ack_psdu, sizeof(ack_psdu)) < 0) { | 
|  | LOG_ERR("Failed to write to a packet."); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | /* Use some fake values for LQI and RSSI. */ | 
|  | (void)net_pkt_set_ieee802154_lqi(ack_pkt, 80); | 
|  | (void)net_pkt_set_ieee802154_rssi(ack_pkt, -40); | 
|  |  | 
|  | net_pkt_cursor_init(ack_pkt); | 
|  |  | 
|  | if (ieee802154_radio_handle_ack(kw41z->iface, ack_pkt) != NET_OK) { | 
|  | LOG_INF("ACK packet not handled - releasing."); | 
|  | } | 
|  |  | 
|  | out: | 
|  | net_pkt_unref(ack_pkt); | 
|  | } | 
|  |  | 
|  | static int kw41z_tx(const struct device *dev, enum ieee802154_tx_mode mode, | 
|  | struct net_pkt *pkt, struct net_buf *frag) | 
|  | { | 
|  | struct kw41z_context *kw41z = dev->data; | 
|  | uint8_t payload_len = frag->len; | 
|  | uint32_t tx_timeout; | 
|  | uint8_t xcvseq; | 
|  | int key; | 
|  |  | 
|  | if (mode != IEEE802154_TX_MODE_DIRECT) { | 
|  | NET_ERR("TX mode %d not supported", mode); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * The transmit requests are preceded by the CCA request. On | 
|  | * completion of the CCA the sequencer should be in the IDLE | 
|  | * state. | 
|  | */ | 
|  | if (kw41z_get_seq_state() != KW41Z_STATE_IDLE) { | 
|  | LOG_WRN("Can't initiate new SEQ state"); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | if (payload_len > KW41Z_PSDU_LENGTH) { | 
|  | LOG_ERR("Payload too long"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | key = irq_lock(); | 
|  |  | 
|  | /* Disable the 802.15.4 radio IRQ */ | 
|  | ZLL->PHY_CTRL |= ZLL_PHY_CTRL_TRCV_MSK_MASK; | 
|  | kw41z_disable_seq_irq(); | 
|  |  | 
|  | #if CONFIG_SOC_MKW41Z4 | 
|  | ((uint8_t *)ZLL->PKT_BUFFER_TX)[0] = payload_len + KW41Z_FCS_LENGTH; | 
|  | memcpy(((uint8_t *)ZLL->PKT_BUFFER_TX) + 1, | 
|  | (void *)frag->data, payload_len); | 
|  | #else /* CONFIG_SOC_MKW40Z4 */ | 
|  | ((uint8_t *)ZLL->PKT_BUFFER)[0] = payload_len + KW41Z_FCS_LENGTH; | 
|  | memcpy(((uint8_t *)ZLL->PKT_BUFFER) + 1, | 
|  | (void *)frag->data, payload_len); | 
|  | #endif | 
|  |  | 
|  | /* Set CCA mode */ | 
|  | ZLL->PHY_CTRL = (ZLL->PHY_CTRL & ~ZLL_PHY_CTRL_CCATYPE_MASK) | | 
|  | ZLL_PHY_CTRL_CCATYPE(KW41Z_CCA_MODE1); | 
|  |  | 
|  | /* Clear all IRQ flags */ | 
|  | ZLL->IRQSTS = ZLL->IRQSTS; | 
|  |  | 
|  | /* | 
|  | * Current Zephyr 802.15.4 stack doesn't support ACK offload | 
|  | */ | 
|  |  | 
|  | /* Perform automatic reception of ACK frame, if required */ | 
|  | if (ieee802154_is_ar_flag_set(frag)) { | 
|  | tx_timeout = kw41z->tx_warmup_time + KW41Z_SHR_PHY_TIME + | 
|  | payload_len * KW41Z_PER_BYTE_TIME + 10 + | 
|  | KW41Z_ACK_WAIT_TIME; | 
|  |  | 
|  | LOG_DBG("AUTOACK ENABLED: len: %d, timeout: %d, seq: %d", | 
|  | payload_len, tx_timeout, frag->data[2]); | 
|  |  | 
|  | kw41z_tmr3_set_timeout(tx_timeout); | 
|  | ZLL->PHY_CTRL |= ZLL_PHY_CTRL_RXACKRQD_MASK; | 
|  | xcvseq = KW41Z_STATE_TXRX; | 
|  | } else { | 
|  | LOG_DBG("AUTOACK DISABLED: len: %d, seq: %d", | 
|  | payload_len, frag->data[2]); | 
|  |  | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_RXACKRQD_MASK; | 
|  | xcvseq = KW41Z_STATE_TX; | 
|  | } | 
|  |  | 
|  | kw41z_enable_seq_irq(); | 
|  | /* | 
|  | * PHY_CTRL is sensitive to multiple writes that can kick off | 
|  | * the sequencer engine causing TX with AR request to send the | 
|  | * TX frame multiple times. | 
|  | * | 
|  | * To minimize, ensure there is only one write to PHY_CTRL with | 
|  | * TXRX sequence enable and the 802.15.4 radio IRQ. | 
|  | */ | 
|  | ZLL->PHY_CTRL = (ZLL->PHY_CTRL & ~ZLL_PHY_CTRL_TRCV_MSK_MASK) | xcvseq; | 
|  | irq_unlock(key); | 
|  | k_sem_take(&kw41z->seq_sync, K_FOREVER); | 
|  |  | 
|  | if ((kw41z->seq_retval == 0) && ieee802154_is_ar_flag_set(frag)) { | 
|  | handle_ack(kw41z, frag->data[2]); | 
|  | } | 
|  |  | 
|  | LOG_DBG("seq_retval: %ld", kw41z->seq_retval); | 
|  | return kw41z->seq_retval; | 
|  | } | 
|  |  | 
|  | static void kw41z_isr(int unused) | 
|  | { | 
|  | uint32_t irqsts = ZLL->IRQSTS; | 
|  | uint8_t state = kw41z_get_seq_state(); | 
|  | uint8_t restart_rx = 1U; | 
|  | uint32_t rx_len; | 
|  |  | 
|  | /* | 
|  | * Variable is used in debug output to capture the state of the | 
|  | * sequencer at interrupt. | 
|  | */ | 
|  | uint32_t seq_state = ZLL->SEQ_STATE; | 
|  |  | 
|  | LOG_DBG("ENTRY: irqsts: 0x%08X, PHY_CTRL: 0x%08X, " | 
|  | "SEQ_STATE: 0x%08X, SEQ_CTRL: 0x%08X, TMR: %d, state: %d", | 
|  | irqsts, (unsigned int)ZLL->PHY_CTRL, | 
|  | (unsigned int)seq_state, | 
|  | (unsigned int)ZLL->SEQ_CTRL_STS, | 
|  | (unsigned int)(ZLL->EVENT_TMR >> ZLL_EVENT_TMR_EVENT_TMR_SHIFT), | 
|  | state); | 
|  |  | 
|  | /* Clear interrupts */ | 
|  | ZLL->IRQSTS = irqsts; | 
|  |  | 
|  | if (irqsts & ZLL_IRQSTS_FILTERFAIL_IRQ_MASK) { | 
|  | LOG_DBG("Incoming RX failed packet filtering rules: " | 
|  | "CODE: 0x%08X, irqsts: 0x%08X, PHY_CTRL: 0x%08X, " | 
|  | "SEQ_STATE: 0x%08X, state: %d", | 
|  | (unsigned int)ZLL->FILTERFAIL_CODE, | 
|  | irqsts, | 
|  | (unsigned int)ZLL->PHY_CTRL, | 
|  | (unsigned int)seq_state, state); | 
|  |  | 
|  | restart_rx = 0U; | 
|  |  | 
|  | } else if ((!(ZLL->PHY_CTRL & ZLL_PHY_CTRL_RX_WMRK_MSK_MASK)) && | 
|  | (irqsts & ZLL_IRQSTS_RXWTRMRKIRQ_MASK)) { | 
|  | /* | 
|  | * There is a bug in the KW41Z where in noisy environments | 
|  | * the RX sequence can get lost. The watermark mask IRQ can | 
|  | * start TMR3 to complete the rest of the read or to assert | 
|  | * IRQ if the sequencer gets lost so we can reset things. | 
|  | * Note that a TX from the upper layers will also reset | 
|  | * things so the problem is contained a bit in normal | 
|  | * operation. | 
|  | */ | 
|  | rx_len = (irqsts & ZLL_IRQSTS_RX_FRAME_LENGTH_MASK) | 
|  | >> ZLL_IRQSTS_RX_FRAME_LENGTH_SHIFT; | 
|  |  | 
|  | KW_DBG_TRACE(KW41_DBG_TRACE_WTRM, irqsts, | 
|  | (unsigned int)ZLL->PHY_CTRL, seq_state); | 
|  |  | 
|  | if (rx_len > IEEE802154_ACK_LENGTH) { | 
|  |  | 
|  | LOG_DBG("WMRK irq: seq_state: 0x%08x, rx_len: %d", | 
|  | seq_state, rx_len); | 
|  | /* | 
|  | * Assume the RX includes an auto-ACK so set the | 
|  | * timer to include the RX frame size, crc, IFS, | 
|  | * and ACK length and convert to symbols. | 
|  | * | 
|  | * IFS is 12 symbols | 
|  | * | 
|  | * ACK frame is 11 bytes: 4 preamble, 1 start of | 
|  | * frame, 1 frame length, 2 frame control, | 
|  | * 1 sequence, 2 FCS. Times two to convert to symbols. | 
|  | */ | 
|  | rx_len = rx_len * 2U + 12 + 22 + 2; | 
|  | kw41z_tmr3_set_timeout(rx_len); | 
|  | } | 
|  | restart_rx = 0U; | 
|  | } | 
|  |  | 
|  | /* Sequence done IRQ */ | 
|  | if ((state != KW41Z_STATE_IDLE) && (irqsts & ZLL_IRQSTS_SEQIRQ_MASK)) { | 
|  | /* | 
|  | * PLL unlock, the autosequence has been aborted due to | 
|  | * PLL unlock | 
|  | */ | 
|  | if (irqsts & ZLL_IRQSTS_PLL_UNLOCK_IRQ_MASK) { | 
|  | LOG_ERR("PLL unlock error"); | 
|  | kw41z_isr_seq_cleanup(); | 
|  | restart_rx = 1U; | 
|  | } | 
|  | /* | 
|  | * TMR3 timeout, the autosequence has been aborted due to | 
|  | * TMR3 timeout | 
|  | */ | 
|  | else if ((irqsts & ZLL_IRQSTS_TMR3IRQ_MASK) && | 
|  | (!(irqsts & ZLL_IRQSTS_RXIRQ_MASK)) && | 
|  | (state != KW41Z_STATE_TX)) { | 
|  |  | 
|  | LOG_DBG("a) TMR3 timeout: irqsts: 0x%08X, " | 
|  | "seq_state: 0x%08X, PHY_CTRL: 0x%08X, " | 
|  | "state: %d", | 
|  | irqsts, seq_state, | 
|  | (unsigned int)ZLL->PHY_CTRL, state); | 
|  |  | 
|  | KW_DBG_TRACE(KW41_DBG_TRACE_TMR3, irqsts, | 
|  | (unsigned int)ZLL->PHY_CTRL, seq_state); | 
|  |  | 
|  | kw41z_isr_timeout_cleanup(); | 
|  | restart_rx = 1U; | 
|  |  | 
|  | if (state == KW41Z_STATE_TXRX) { | 
|  | /* TODO: What is the right error for no ACK? */ | 
|  | atomic_set(&kw41z_context_data.seq_retval, | 
|  | -EBUSY); | 
|  | k_sem_give(&kw41z_context_data.seq_sync); | 
|  | } | 
|  | } else { | 
|  | kw41z_isr_seq_cleanup(); | 
|  |  | 
|  | switch (state) { | 
|  | case KW41Z_STATE_RX: | 
|  | LOG_DBG("RX seq done: SEQ_STATE: 0x%08X", | 
|  | (unsigned int)seq_state); | 
|  |  | 
|  | KW_DBG_TRACE(KW41_DBG_TRACE_RX, irqsts, | 
|  | (unsigned int)ZLL->PHY_CTRL, seq_state); | 
|  |  | 
|  | kw41z_tmr3_disable(); | 
|  |  | 
|  | rx_len = (ZLL->IRQSTS & | 
|  | ZLL_IRQSTS_RX_FRAME_LENGTH_MASK) >> | 
|  | ZLL_IRQSTS_RX_FRAME_LENGTH_SHIFT; | 
|  |  | 
|  | if (irqsts & ZLL_IRQSTS_RXIRQ_MASK) { | 
|  | if (rx_len != 0U) { | 
|  | kw41z_rx(&kw41z_context_data, | 
|  | rx_len); | 
|  | } | 
|  | } | 
|  | restart_rx = 1U; | 
|  | break; | 
|  | case KW41Z_STATE_TXRX: | 
|  | LOG_DBG("TXRX seq done"); | 
|  | kw41z_tmr3_disable(); | 
|  | /* Store the frame pending bit status. */ | 
|  | kw41z_context_data.frame_pending = | 
|  | irqsts & ZLL_IRQSTS_RX_FRM_PEND_MASK; | 
|  | case KW41Z_STATE_TX: | 
|  | LOG_DBG("TX seq done"); | 
|  | KW_DBG_TRACE(KW41_DBG_TRACE_TX, irqsts, | 
|  | (unsigned int)ZLL->PHY_CTRL, seq_state); | 
|  | if (irqsts & ZLL_IRQSTS_CCA_MASK) { | 
|  | atomic_set( | 
|  | &kw41z_context_data.seq_retval, | 
|  | -EBUSY); | 
|  | } else { | 
|  | atomic_set( | 
|  | &kw41z_context_data.seq_retval, | 
|  | 0); | 
|  | } | 
|  |  | 
|  | k_sem_give(&kw41z_context_data.seq_sync); | 
|  | restart_rx = 1U; | 
|  |  | 
|  | break; | 
|  | case KW41Z_STATE_CCA: | 
|  | LOG_DBG("CCA seq done"); | 
|  | KW_DBG_TRACE(KW41_DBG_TRACE_CCA, irqsts, | 
|  | (unsigned int)ZLL->PHY_CTRL, seq_state); | 
|  | if (irqsts & ZLL_IRQSTS_CCA_MASK) { | 
|  | atomic_set( | 
|  | &kw41z_context_data.seq_retval, | 
|  | -EBUSY); | 
|  | restart_rx = 1U; | 
|  | } else { | 
|  | atomic_set( | 
|  | &kw41z_context_data.seq_retval, | 
|  | 0); | 
|  | restart_rx = 0U; | 
|  | } | 
|  |  | 
|  | k_sem_give(&kw41z_context_data.seq_sync); | 
|  | break; | 
|  | default: | 
|  | LOG_DBG("Unhandled state: %d", state); | 
|  | restart_rx = 1U; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } else { | 
|  | /* Timer 3 Compare Match */ | 
|  | if ((irqsts & ZLL_IRQSTS_TMR3IRQ_MASK) && | 
|  | (!(irqsts & ZLL_IRQSTS_TMR3MSK_MASK))) { | 
|  |  | 
|  | LOG_DBG("b) TMR3 timeout: irqsts: 0x%08X, " | 
|  | "seq_state: 0x%08X, state: %d", | 
|  | irqsts, seq_state, state); | 
|  |  | 
|  | kw41z_tmr3_disable(); | 
|  | restart_rx = 0U; | 
|  | if (state != KW41Z_STATE_IDLE) { | 
|  | kw41z_isr_timeout_cleanup(); | 
|  | restart_rx = 1U; | 
|  | /* If we are not running an automated | 
|  | * sequence then handle event. TMR3 can expire | 
|  | * during Recv/Ack sequence where the transmit | 
|  | * of the ACK is not being interrupted. | 
|  | */ | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Restart RX */ | 
|  | if (restart_rx) { | 
|  | LOG_DBG("RESET RX"); | 
|  | kw41z_phy_abort(); | 
|  | kw41z_set_seq_state(KW41Z_STATE_RX); | 
|  | kw41z_enable_seq_irq(); | 
|  | } | 
|  | } | 
|  |  | 
|  | static inline uint8_t *get_mac(const struct device *dev) | 
|  | { | 
|  | struct kw41z_context *kw41z = dev->data; | 
|  |  | 
|  | /* | 
|  | * The KW40Z has two 32-bit registers for the MAC address where | 
|  | * 40 bits of the registers are factory programmed to be unique | 
|  | * and the rest are to be assigned as the "company-specific" value. | 
|  | * 802.15.4 defines a EUI-64 64-bit address with company specific | 
|  | * being 24 or 36 bits with the unique value being 24 or 40 bits. | 
|  | * | 
|  | * TODO: Grab from RSIM->MAC_LSB/MAC_MSB for the unique 40 bits | 
|  | *       and how to allow for a OUI portion? | 
|  | */ | 
|  |  | 
|  | uint32_t *ptr = (uint32_t *)(kw41z->mac_addr); | 
|  |  | 
|  | UNALIGNED_PUT(sys_rand32_get(), ptr); | 
|  | ptr = (uint32_t *)(kw41z->mac_addr + 4); | 
|  | UNALIGNED_PUT(sys_rand32_get(), ptr); | 
|  |  | 
|  | /* | 
|  | * Clear bit 0 to ensure it isn't a multicast address and set | 
|  | * bit 1 to indicate address is locally administered and may | 
|  | * not be globally unique. | 
|  | */ | 
|  | kw41z->mac_addr[0] = (kw41z->mac_addr[0] & ~0x01) | 0x02; | 
|  |  | 
|  | return kw41z->mac_addr; | 
|  | } | 
|  |  | 
|  | static int kw41z_init(const struct device *dev) | 
|  | { | 
|  | struct kw41z_context *kw41z = dev->data; | 
|  | xcvrStatus_t xcvrStatus; | 
|  |  | 
|  | xcvrStatus = XCVR_Init(ZIGBEE_MODE, DR_500KBPS); | 
|  | if (xcvrStatus != gXcvrSuccess_c) { | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | /* Disable all timers, enable AUTOACK, mask all interrupts */ | 
|  | ZLL->PHY_CTRL = ZLL_PHY_CTRL_CCATYPE(KW41Z_CCA_MODE1)	| | 
|  | ZLL_PHY_CTRL_CRC_MSK_MASK		| | 
|  | ZLL_PHY_CTRL_PLL_UNLOCK_MSK_MASK	| | 
|  | /*ZLL_PHY_CTRL_FILTERFAIL_MSK_MASK	|*/ | 
|  | ZLL_PHY_CTRL_RX_WMRK_MSK_MASK	| | 
|  | ZLL_PHY_CTRL_CCAMSK_MASK		| | 
|  | ZLL_PHY_CTRL_RXMSK_MASK			| | 
|  | ZLL_PHY_CTRL_TXMSK_MASK			| | 
|  | ZLL_PHY_CTRL_CCABFRTX_MASK		| | 
|  | ZLL_PHY_CTRL_SEQMSK_MASK; | 
|  |  | 
|  | #if CONFIG_SOC_MKW41Z4 | 
|  | ZLL->PHY_CTRL |= ZLL_IRQSTS_WAKE_IRQ_MASK; | 
|  | #endif | 
|  |  | 
|  | ZLL->PHY_CTRL |= ZLL_PHY_CTRL_AUTOACK_MASK; | 
|  |  | 
|  | /* | 
|  | * Clear all PP IRQ bits to avoid unexpected interrupts immediately | 
|  | * after init disable all timer interrupts | 
|  | */ | 
|  | ZLL->IRQSTS = ZLL->IRQSTS; | 
|  |  | 
|  | /* Clear HW indirect queue */ | 
|  | ZLL->SAM_TABLE |= ZLL_SAM_TABLE_INVALIDATE_ALL_MASK; | 
|  |  | 
|  | /* Accept FrameVersion 0 and 1 packets, reject all others */ | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_PROMISCUOUS_MASK; | 
|  | ZLL->RX_FRAME_FILTER &= ~ZLL_RX_FRAME_FILTER_FRM_VER_FILTER_MASK; | 
|  | ZLL->RX_FRAME_FILTER = ZLL_RX_FRAME_FILTER_FRM_VER_FILTER(3)	| | 
|  | ZLL_RX_FRAME_FILTER_CMD_FT_MASK		| | 
|  | ZLL_RX_FRAME_FILTER_DATA_FT_MASK		| | 
|  | ZLL_RX_FRAME_FILTER_ACK_FT_MASK		| | 
|  | ZLL_RX_FRAME_FILTER_BEACON_FT_MASK; | 
|  |  | 
|  | /* Set prescaler to obtain 1 symbol (16us) timebase */ | 
|  | ZLL->TMR_PRESCALE = 0x05; | 
|  |  | 
|  | kw41z_tmr3_disable(); | 
|  |  | 
|  | /* Compute warmup times (scaled to 16us) */ | 
|  | kw41z->rx_warmup_time = (XCVR_TSM->END_OF_SEQ & | 
|  | XCVR_TSM_END_OF_SEQ_END_OF_RX_WU_MASK) >> | 
|  | XCVR_TSM_END_OF_SEQ_END_OF_RX_WU_SHIFT; | 
|  | kw41z->tx_warmup_time = (XCVR_TSM->END_OF_SEQ & | 
|  | XCVR_TSM_END_OF_SEQ_END_OF_TX_WU_MASK) >> | 
|  | XCVR_TSM_END_OF_SEQ_END_OF_TX_WU_SHIFT; | 
|  |  | 
|  | if (kw41z->rx_warmup_time & 0x0F) { | 
|  | kw41z->rx_warmup_time = 1 + (kw41z->rx_warmup_time >> 4); | 
|  | } else { | 
|  | kw41z->rx_warmup_time = kw41z->rx_warmup_time >> 4; | 
|  | } | 
|  |  | 
|  | if (kw41z->tx_warmup_time & 0x0F) { | 
|  | kw41z->tx_warmup_time = 1 + (kw41z->tx_warmup_time >> 4); | 
|  | } else { | 
|  | kw41z->tx_warmup_time = kw41z->tx_warmup_time >> 4; | 
|  | } | 
|  |  | 
|  | /* Set CCA threshold to -75 dBm */ | 
|  | ZLL->CCA_LQI_CTRL &= ~ZLL_CCA_LQI_CTRL_CCA1_THRESH_MASK; | 
|  | ZLL->CCA_LQI_CTRL |= ZLL_CCA_LQI_CTRL_CCA1_THRESH(0xB5); | 
|  |  | 
|  | /* Set the default power level */ | 
|  | kw41z_set_txpower(dev, 0); | 
|  |  | 
|  | /* Adjust ACK delay to fulfill the 802.15.4 turnaround requirements */ | 
|  | ZLL->ACKDELAY &= ~ZLL_ACKDELAY_ACKDELAY_MASK; | 
|  | ZLL->ACKDELAY |= ZLL_ACKDELAY_ACKDELAY(-8); | 
|  |  | 
|  | /* Adjust LQI compensation */ | 
|  | ZLL->CCA_LQI_CTRL &= ~ZLL_CCA_LQI_CTRL_LQI_OFFSET_COMP_MASK; | 
|  | ZLL->CCA_LQI_CTRL |= ZLL_CCA_LQI_CTRL_LQI_OFFSET_COMP(96); | 
|  |  | 
|  | /* Enable the RxWatermark IRQ  */ | 
|  | ZLL->PHY_CTRL &= ~(ZLL_PHY_CTRL_RX_WMRK_MSK_MASK); | 
|  | /* Set Rx watermark level */ | 
|  | ZLL->RX_WTR_MARK = 0; | 
|  |  | 
|  |  | 
|  | /* Set default channel to 2405 MHZ */ | 
|  | kw41z_set_channel(dev, KW41Z_DEFAULT_CHANNEL); | 
|  |  | 
|  | /* Unmask Transceiver Global Interrupts */ | 
|  | ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_TRCV_MSK_MASK; | 
|  |  | 
|  | /* Configure Radio IRQ */ | 
|  | NVIC_ClearPendingIRQ(Radio_1_IRQn); | 
|  | IRQ_CONNECT(Radio_1_IRQn, RADIO_0_IRQ_PRIO, kw41z_isr, 0, 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void kw41z_iface_init(struct net_if *iface) | 
|  | { | 
|  | const struct device *dev = net_if_get_device(iface); | 
|  | struct kw41z_context *kw41z = dev->data; | 
|  | uint8_t *mac = get_mac(dev); | 
|  |  | 
|  | #if defined(CONFIG_KW41_DBG_TRACE) | 
|  | kw41_dbg_idx = 0; | 
|  | #endif | 
|  |  | 
|  | net_if_set_link_addr(iface, mac, 8, NET_LINK_IEEE802154); | 
|  | kw41z->iface = iface; | 
|  | ieee802154_init(iface); | 
|  | } | 
|  |  | 
|  | static int kw41z_configure(const struct device *dev, | 
|  | enum ieee802154_config_type type, | 
|  | const struct ieee802154_config *config) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct ieee802154_radio_api kw41z_radio_api = { | 
|  | .iface_api.init	= kw41z_iface_init, | 
|  |  | 
|  | .get_capabilities	= kw41z_get_capabilities, | 
|  | .cca			= kw41z_cca, | 
|  | .set_channel		= kw41z_set_channel, | 
|  | .filter			= kw41z_filter, | 
|  | .set_txpower		= kw41z_set_txpower, | 
|  | .start			= kw41z_start, | 
|  | .stop			= kw41z_stop, | 
|  | .tx			= kw41z_tx, | 
|  | .configure		= kw41z_configure, | 
|  | }; | 
|  |  | 
|  | #if defined(CONFIG_NET_L2_IEEE802154) | 
|  |  | 
|  | #define L2 IEEE802154_L2 | 
|  | #define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(IEEE802154_L2) | 
|  | #define MTU KW41Z_PSDU_LENGTH | 
|  |  | 
|  | #elif defined(CONFIG_NET_L2_OPENTHREAD) | 
|  |  | 
|  | #define L2 OPENTHREAD_L2 | 
|  | #define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(OPENTHREAD_L2) | 
|  | #define MTU 1280 | 
|  |  | 
|  | #endif | 
|  |  | 
|  | NET_DEVICE_INIT( | 
|  | kw41z,                              /* Device Name */ | 
|  | CONFIG_IEEE802154_KW41Z_DRV_NAME,   /* Driver Name */ | 
|  | kw41z_init,                         /* Initialization Function */ | 
|  | NULL,              /* No PM API support */ | 
|  | &kw41z_context_data,                /* Context data */ | 
|  | NULL,                               /* Configuration info */ | 
|  | CONFIG_IEEE802154_KW41Z_INIT_PRIO,  /* Initial priority */ | 
|  | &kw41z_radio_api,                   /* API interface functions */ | 
|  | L2,                                 /* L2 */ | 
|  | L2_CTX_TYPE,                        /* L2 context type */ | 
|  | MTU);                               /* MTU size */ |