| /* ieee802154_mcr20a.c - NXP MCR20A driver */ |
| |
| /* |
| * Copyright (c) 2017 PHYTEC Messtechnik GmbH |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define SYS_LOG_LEVEL CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL |
| #define SYS_LOG_DOMAIN "dev/mcr20a" |
| #include <logging/sys_log.h> |
| |
| #include <errno.h> |
| |
| #include <kernel.h> |
| #include <arch/cpu.h> |
| |
| #include <board.h> |
| #include <device.h> |
| #include <init.h> |
| #include <net/net_if.h> |
| #include <net/net_pkt.h> |
| |
| #include <misc/byteorder.h> |
| #include <string.h> |
| #include <rand32.h> |
| |
| #include <gpio.h> |
| |
| #include <net/ieee802154_radio.h> |
| |
| #include "ieee802154_mcr20a.h" |
| #include "MCR20Overwrites.h" |
| |
| /* |
| * max. TX duraton = (PR + SFD + FLI + PDU + FCS) |
| * + RX_warmup + cca + TX_warmup |
| * TODO: Calculate the value from frame length. |
| * Invalid for the SLOTTED mode. |
| */ |
| #define _MAX_PKT_TX_DURATION (133 + 9 + 8 + 9) |
| |
| #if (SYS_LOG_LEVEL == 4) |
| /* Prevent timer overflow during SYS_LOG_* output */ |
| #define _MACACKWAITDURATION (864 / 16 + 11625) |
| #define MCR20A_SEQ_SYNC_TIMEOUT (200) |
| #else |
| #define MCR20A_SEQ_SYNC_TIMEOUT (20) |
| #define _MACACKWAITDURATION (864 / 16) /* 864us * 62500Hz */ |
| #endif |
| |
| /* AUTOACK should be enabled by default, disable it only for testing */ |
| #define MCR20A_AUTOACK_ENABLED (true) |
| |
| #define MCR20A_FCS_LENGTH (2) |
| #define MCR20A_PSDU_LENGTH (125) |
| #define MCR20A_GET_SEQ_STATE_RETRIES (3) |
| |
| /* Values for the clock output (CLK_OUT) configuration */ |
| #ifdef CONFIG_MCR20A_CLK_OUT_DISABLED |
| #define MCR20A_CLK_OUT_CONFIG (MCR20A_CLK_OUT_HIZ) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_32MHZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(0) | MCR20A_CLK_OUT_DS |\ |
| MCR20A_CLK_OUT_EN) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_16MHZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(1) | MCR20A_CLK_OUT_DS |\ |
| MCR20A_CLK_OUT_EN) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_8MHZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(2) | MCR20A_CLK_OUT_EN) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_4MHZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(3) | MCR20A_CLK_OUT_EN) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_1MHZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(4) | MCR20A_CLK_OUT_EN) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_250KHZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(5) | MCR20A_CLK_OUT_EN) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_62500HZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(6) | MCR20A_CLK_OUT_EN) |
| |
| #elif CONFIG_MCR20A_CLK_OUT_32768HZ |
| #define MCR20A_CLK_OUT_CONFIG (set_bits_clk_out_div(7) | MCR20A_CLK_OUT_EN) |
| |
| #endif |
| |
| #ifdef CONFIG_MCR20A_IS_PART_OF_KW2XD_SIP |
| #define PART_OF_KW2XD_SIP 1 |
| #else |
| #define PART_OF_KW2XD_SIP 0 |
| #endif |
| |
| /* Values for the power mode (PM) configuration */ |
| #define MCR20A_PM_HIBERNATE 0 |
| #define MCR20A_PM_DOZE MCR20A_PWR_MODES_XTALEN |
| #define MCR20A_PM_IDLE (MCR20A_PWR_MODES_XTALEN |\ |
| MCR20A_PWR_MODES_PMC_MODE) |
| #define MCR20A_PM_AUTODOZE (MCR20A_PWR_MODES_XTALEN |\ |
| MCR20A_PWR_MODES_AUTODOZE) |
| |
| /* Default settings for the device initialization */ |
| #define MCR20A_DEFAULT_TX_POWER (0) |
| #define MCR20A_DEFAULT_CHANNEL (26) |
| |
| /* RF TX power max/min values (dBm) */ |
| #define MCR20A_OUTPUT_POWER_MAX (8) |
| #define MCR20A_OUTPUT_POWER_MIN (-35) |
| |
| /* Lookup table for the Power Control register */ |
| static const u8_t pow_lt[44] = { |
| 3, 4, 5, 6, |
| 6, 7, 7, 8, |
| 8, 9, 9, 10, |
| 11, 11, 12, 13, |
| 13, 14, 14, 15, |
| 16, 16, 17, 18, |
| 18, 19, 20, 20, |
| 21, 21, 22, 23, |
| 23, 24, 25, 25, |
| 26, 27, 27, 28, |
| 28, 29, 30, 31 |
| }; |
| |
| /* PLL integer and fractional lookup tables |
| * |
| * Fc = 2405 + 5(k - 11) , k = 11,12,...,26 |
| * |
| * Equation for PLL frequency, MKW2xD Reference Manual, p.255 : |
| * F = ((PLL_INT0 + 64) + (PLL_FRAC0/65536))32MHz |
| * |
| */ |
| static const u8_t pll_int_lt[16] = { |
| 11, 11, 11, 11, |
| 11, 11, 12, 12, |
| 12, 12, 12, 12, |
| 13, 13, 13, 13 |
| }; |
| |
| static const u16_t pll_frac_lt[16] = { |
| 10240, 20480, 30720, 40960, |
| 51200, 61440, 6144, 16384, |
| 26624, 36864, 47104, 57344, |
| 2048, 12288, 22528, 32768 |
| }; |
| |
| #define _usleep(usec) k_busy_wait(usec) |
| |
| /* Read direct (dreg is true) or indirect register (dreg is false) */ |
| u8_t _mcr20a_read_reg(struct mcr20a_spi *spi, bool dreg, u8_t addr) |
| { |
| u8_t len = dreg ? 2 : 3; |
| |
| k_sem_take(&spi->spi_sem, K_FOREVER); |
| |
| spi->cmd_buf[0] = dreg ? (MCR20A_REG_READ | addr) : |
| (MCR20A_IAR_INDEX | MCR20A_REG_WRITE); |
| spi->cmd_buf[1] = dreg ? 0 : (addr | MCR20A_REG_READ); |
| spi->cmd_buf[2] = 0; |
| |
| spi_slave_select(spi->dev, spi->slave); |
| |
| if (spi_transceive(spi->dev, spi->cmd_buf, len, |
| spi->cmd_buf, len) == 0) { |
| k_sem_give(&spi->spi_sem); |
| return spi->cmd_buf[len - 1]; |
| } |
| |
| k_sem_give(&spi->spi_sem); |
| |
| return 0; |
| } |
| |
| /* Write direct (dreg is true) or indirect register (dreg is false) */ |
| bool _mcr20a_write_reg(struct mcr20a_spi *spi, bool dreg, u8_t addr, |
| u8_t value) |
| { |
| u8_t len = dreg ? 2 : 3; |
| bool retval; |
| |
| k_sem_take(&spi->spi_sem, K_FOREVER); |
| |
| spi->cmd_buf[0] = dreg ? (MCR20A_REG_WRITE | addr) : |
| (MCR20A_IAR_INDEX | MCR20A_REG_WRITE); |
| spi->cmd_buf[1] = dreg ? value : (addr | MCR20A_REG_WRITE); |
| spi->cmd_buf[2] = dreg ? 0 : value; |
| |
| spi_slave_select(spi->dev, spi->slave); |
| retval = (spi_write(spi->dev, spi->cmd_buf, len) == 0); |
| |
| k_sem_give(&spi->spi_sem); |
| |
| return retval; |
| } |
| |
| /* Write multiple bytes to direct or indirect register */ |
| bool _mcr20a_write_burst(struct mcr20a_spi *spi, bool dreg, u16_t addr, |
| u8_t *data_buf, u8_t len) |
| { |
| bool retval; |
| |
| k_sem_take(&spi->spi_sem, K_FOREVER); |
| |
| if ((len + 2) > sizeof(spi->cmd_buf)) { |
| SYS_LOG_ERR("Buffer length too large"); |
| } |
| |
| if (dreg) { |
| spi->cmd_buf[0] = MCR20A_REG_WRITE | addr; |
| memcpy(&spi->cmd_buf[1], data_buf, len); |
| len += 1; |
| } else { |
| spi->cmd_buf[0] = MCR20A_IAR_INDEX | MCR20A_REG_WRITE; |
| spi->cmd_buf[1] = addr | MCR20A_REG_WRITE; |
| memcpy(&spi->cmd_buf[2], data_buf, len); |
| len += 2; |
| } |
| |
| spi_slave_select(spi->dev, spi->slave); |
| retval = (spi_write(spi->dev, spi->cmd_buf, len) == 0); |
| |
| k_sem_give(&spi->spi_sem); |
| |
| return retval; |
| } |
| |
| /* Read multiple bytes from direct or indirect register */ |
| bool _mcr20a_read_burst(struct mcr20a_spi *spi, bool dreg, u16_t addr, |
| u8_t *data_buf, u8_t len) |
| { |
| k_sem_take(&spi->spi_sem, K_FOREVER); |
| |
| if ((len + 2) > sizeof(spi->cmd_buf)) { |
| SYS_LOG_ERR("Buffer length too large"); |
| } |
| |
| if (dreg) { |
| spi->cmd_buf[0] = MCR20A_REG_READ | addr; |
| len += 1; |
| } else { |
| spi->cmd_buf[0] = MCR20A_IAR_INDEX | MCR20A_REG_WRITE; |
| spi->cmd_buf[1] = addr | MCR20A_REG_READ; |
| len += 2; |
| } |
| |
| spi_slave_select(spi->dev, spi->slave); |
| |
| if (spi_transceive(spi->dev, spi->cmd_buf, len, |
| spi->cmd_buf, len) != 0) { |
| k_sem_give(&spi->spi_sem); |
| return 0; |
| } |
| |
| if (dreg) { |
| memcpy(data_buf, &spi->cmd_buf[1], len - 1); |
| } else { |
| memcpy(data_buf, &spi->cmd_buf[2], len - 2); |
| } |
| |
| k_sem_give(&spi->spi_sem); |
| |
| return 1; |
| } |
| |
| /* Mask (msk is true) or unmask all interrupts from asserting IRQ_B */ |
| static bool mcr20a_mask_irqb(struct mcr20a_context *dev, bool msk) |
| { |
| u8_t ctrl4 = read_reg_phy_ctrl4(&dev->spi); |
| |
| if (msk) { |
| ctrl4 |= MCR20A_PHY_CTRL4_TRCV_MSK; |
| } else { |
| ctrl4 &= ~MCR20A_PHY_CTRL4_TRCV_MSK; |
| } |
| |
| return write_reg_phy_ctrl4(&dev->spi, ctrl4); |
| } |
| |
| /** Set an timeout value for the given compare register */ |
| static int mcr20a_timer_set(struct mcr20a_context *mcr20a, |
| u8_t cmp_reg, |
| u32_t timeout) |
| { |
| u32_t now = 0; |
| u32_t next; |
| bool retval; |
| |
| if (!read_burst_event_timer(&mcr20a->spi, (u8_t *)&now)) { |
| goto error; |
| } |
| |
| now = sys_le32_to_cpu(now); |
| next = now + timeout; |
| SYS_LOG_DBG("now: 0x%x set 0x%x", now, next); |
| next = sys_cpu_to_le32(next); |
| |
| switch (cmp_reg) { |
| case 1: |
| retval = write_burst_t1cmp(&mcr20a->spi, (u8_t *)&next); |
| break; |
| case 2: |
| retval = write_burst_t2cmp(&mcr20a->spi, (u8_t *)&next); |
| break; |
| case 3: |
| retval = write_burst_t3cmp(&mcr20a->spi, (u8_t *)&next); |
| break; |
| case 4: |
| retval = write_burst_t4cmp(&mcr20a->spi, (u8_t *)&next); |
| break; |
| default: |
| goto error; |
| } |
| |
| if (!retval) { |
| goto error; |
| } |
| |
| return 0; |
| |
| error: |
| SYS_LOG_ERR("Failed"); |
| return -EIO; |
| } |
| |
| static int mcr20a_timer_init(struct device *dev, u8_t tb) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t buf[3] = {0, 0, 0}; |
| u8_t ctrl4; |
| |
| if (!write_reg_tmr_prescale(&mcr20a->spi, |
| set_bits_tmr_prescale(tb))) { |
| goto error; |
| } |
| |
| if (!write_burst_t1cmp(&mcr20a->spi, buf)) { |
| goto error; |
| } |
| |
| ctrl4 = read_reg_phy_ctrl4(&mcr20a->spi); |
| ctrl4 |= MCR20A_PHY_CTRL4_TMRLOAD; |
| if (!write_reg_phy_ctrl4(&mcr20a->spi, ctrl4)) { |
| goto error; |
| } |
| |
| SYS_LOG_DBG("done, timebase %d", tb); |
| return 0; |
| |
| error: |
| SYS_LOG_ERR("Failed"); |
| return -EIO; |
| } |
| |
| /* Set Timer Comparator 4 */ |
| static int mcr20a_t4cmp_set(struct mcr20a_context *mcr20a, |
| u32_t timeout) |
| { |
| u8_t irqsts3; |
| u8_t ctrl3; |
| |
| if (mcr20a_timer_set(mcr20a, 4, timeout)) { |
| goto error; |
| } |
| |
| /* enable and clear irq for the timer 4 */ |
| irqsts3 = read_reg_irqsts3(&mcr20a->spi); |
| irqsts3 &= ~MCR20A_IRQSTS3_TMR4MSK; |
| irqsts3 |= MCR20A_IRQSTS3_TMR4IRQ; |
| if (!write_reg_irqsts3(&mcr20a->spi, irqsts3)) { |
| goto error; |
| } |
| |
| ctrl3 = read_reg_phy_ctrl3(&mcr20a->spi); |
| ctrl3 |= MCR20A_PHY_CTRL3_TMR4CMP_EN; |
| if (!write_reg_phy_ctrl3(&mcr20a->spi, ctrl3)) { |
| goto error; |
| } |
| |
| return 0; |
| |
| error: |
| SYS_LOG_DBG("Failed"); |
| return -EIO; |
| } |
| |
| /* Clear Timer Comparator 4 */ |
| static int mcr20a_t4cmp_clear(struct mcr20a_context *mcr20a) |
| { |
| u8_t irqsts3; |
| u8_t ctrl3; |
| |
| ctrl3 = read_reg_phy_ctrl3(&mcr20a->spi); |
| ctrl3 &= ~MCR20A_PHY_CTRL3_TMR4CMP_EN; |
| if (!write_reg_phy_ctrl3(&mcr20a->spi, ctrl3)) { |
| goto error; |
| } |
| |
| irqsts3 = read_reg_irqsts3(&mcr20a->spi); |
| irqsts3 |= MCR20A_IRQSTS3_TMR4IRQ; |
| if (!write_reg_irqsts3(&mcr20a->spi, irqsts3)) { |
| goto error; |
| } |
| |
| return 0; |
| |
| error: |
| SYS_LOG_DBG("Failed"); |
| return -EIO; |
| } |
| |
| static inline void _xcvseq_wait_until_idle(struct mcr20a_context *mcr20a) |
| { |
| u8_t state; |
| u8_t retries = MCR20A_GET_SEQ_STATE_RETRIES; |
| |
| do { |
| state = read_reg_seq_state(&mcr20a->spi); |
| retries--; |
| } while ((state & MCR20A_SEQ_STATE_MASK) && retries); |
| |
| if (state & MCR20A_SEQ_STATE_MASK) { |
| SYS_LOG_ERR("Timeout"); |
| } |
| } |
| |
| static inline int mcr20a_abort_sequence(struct mcr20a_context *mcr20a, |
| bool force) |
| { |
| u8_t ctrl1; |
| |
| ctrl1 = read_reg_phy_ctrl1(&mcr20a->spi); |
| SYS_LOG_DBG("CTRL1 0x%02x", ctrl1); |
| |
| if (((ctrl1 & MCR20A_PHY_CTRL1_XCVSEQ_MASK) == MCR20A_XCVSEQ_TX) || |
| ((ctrl1 & MCR20A_PHY_CTRL1_XCVSEQ_MASK) == MCR20A_XCVSEQ_TX_RX)) { |
| if (!force) { |
| return -1; |
| } |
| } |
| |
| /* Abort ongoing sequence */ |
| ctrl1 &= ~MCR20A_PHY_CTRL1_XCVSEQ_MASK; |
| if (!write_reg_phy_ctrl1(&mcr20a->spi, ctrl1)) { |
| return -1; |
| } |
| |
| _xcvseq_wait_until_idle(mcr20a); |
| |
| /* Clear relevant interrupt flags */ |
| if (!write_reg_irqsts1(&mcr20a->spi, MCR20A_IRQSTS1_IRQ_MASK)) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /* Initiate a (new) Transceiver Sequence */ |
| static inline int mcr20a_set_sequence(struct mcr20a_context *mcr20a, |
| u8_t seq) |
| { |
| u8_t ctrl1 = 0; |
| |
| seq = set_bits_phy_ctrl1_xcvseq(seq); |
| ctrl1 = read_reg_phy_ctrl1(&mcr20a->spi); |
| ctrl1 &= ~MCR20A_PHY_CTRL1_XCVSEQ_MASK; |
| |
| if ((seq == MCR20A_XCVSEQ_TX_RX) && |
| (ctrl1 & MCR20A_PHY_CTRL1_RXACKRQD)) { |
| /* RXACKRQD enabled, timer should be set. */ |
| mcr20a_t4cmp_set(mcr20a, _MACACKWAITDURATION + |
| _MAX_PKT_TX_DURATION); |
| } |
| |
| ctrl1 |= seq; |
| if (!write_reg_phy_ctrl1(&mcr20a->spi, ctrl1)) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static inline u32_t mcr20a_get_rssi(u32_t lqi) |
| { |
| /* Get rssi (Received Signal Strength Indicator, unit is dBm) |
| * from lqi (Link Quality Indicator) value. |
| * There are two different equations for RSSI: |
| * RF = (LQI – 286.6) / 2.69333 (MKW2xD Reference Manual) |
| * RF = (LQI – 295.4) / 2.84 (MCR20A Reference Manual) |
| * The last appears more to match the graphic (Figure 3-10). |
| * Since RSSI value is always positive and we want to |
| * avoid the floating point computation: |
| * -RF * 65536 = (LQI / 2.84 - 295.4 / 2.84) * 65536 |
| * RF * 65536 = (295.4 * 65536 / 2.84) - (LQI * 65536 / 2.84) |
| */ |
| u32_t a = (u32_t)(295.4 * 65536 / 2.84); |
| u32_t b = (u32_t)(65536 / 2.84); |
| |
| return (a - (b * lqi)) >> 16; |
| } |
| |
| static inline u8_t *get_mac(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u32_t *ptr = (u32_t *)(mcr20a->mac_addr); |
| |
| UNALIGNED_PUT(sys_rand32_get(), ptr); |
| ptr = (u32_t *)(mcr20a->mac_addr + 4); |
| UNALIGNED_PUT(sys_rand32_get(), ptr); |
| |
| mcr20a->mac_addr[0] = (mcr20a->mac_addr[0] & ~0x01) | 0x02; |
| |
| return mcr20a->mac_addr; |
| } |
| |
| static inline bool read_rxfifo_content(struct mcr20a_spi *spi, |
| struct net_buf *buf, u8_t len) |
| { |
| u8_t data[1 + MCR20A_PSDU_LENGTH]; |
| |
| if (len > MCR20A_PSDU_LENGTH) { |
| SYS_LOG_ERR("Packet length too large"); |
| return false; |
| } |
| |
| k_sem_take(&spi->spi_sem, K_FOREVER); |
| |
| data[0] = MCR20A_BUF_READ; |
| spi_slave_select(spi->dev, spi->slave); |
| |
| if (spi_transceive(spi->dev, data, len+1, data, len+1) != 0) { |
| k_sem_give(&spi->spi_sem); |
| return false; |
| } |
| |
| memcpy(buf->data, &data[1], len); |
| net_buf_add(buf, len); |
| |
| k_sem_give(&spi->spi_sem); |
| |
| return true; |
| } |
| |
| static inline void mcr20a_rx(struct mcr20a_context *mcr20a, u8_t len) |
| { |
| struct net_pkt *pkt = NULL; |
| struct net_buf *frag; |
| u8_t pkt_len; |
| |
| pkt_len = len - MCR20A_FCS_LENGTH; |
| |
| pkt = net_pkt_get_reserve_rx(0, K_NO_WAIT); |
| if (!pkt) { |
| SYS_LOG_ERR("No buf available"); |
| goto out; |
| } |
| |
| #if defined(CONFIG_IEEE802154_MCR20A_RAW) |
| /* TODO: Test raw mode */ |
| /** |
| * Reserve 1 byte for length |
| */ |
| net_pkt_set_ll_reserve(pkt, 1); |
| #endif |
| frag = net_pkt_get_frag(pkt, K_NO_WAIT); |
| if (!frag) { |
| SYS_LOG_ERR("No frag available"); |
| goto out; |
| } |
| |
| net_pkt_frag_insert(pkt, frag); |
| |
| if (!read_rxfifo_content(&mcr20a->spi, frag, pkt_len)) { |
| SYS_LOG_ERR("No content read"); |
| goto out; |
| } |
| |
| if (ieee802154_radio_handle_ack(mcr20a->iface, pkt) == NET_OK) { |
| SYS_LOG_DBG("ACK packet handled"); |
| goto out; |
| } |
| |
| net_pkt_set_ieee802154_lqi(pkt, read_reg_lqi_value(&mcr20a->spi)); |
| net_pkt_set_ieee802154_rssi(pkt, mcr20a_get_rssi( |
| net_pkt_ieee802154_lqi(pkt))); |
| |
| SYS_LOG_DBG("Caught a packet (%u) (LQI: %u, RSSI: %u)", |
| pkt_len, net_pkt_ieee802154_lqi(pkt), |
| net_pkt_ieee802154_rssi(pkt)); |
| |
| #if defined(CONFIG_IEEE802154_MCR20A_RAW) |
| net_buf_add_u8(frag, mcr20a->lqi); |
| #endif |
| |
| if (net_recv_data(mcr20a->iface, pkt) < 0) { |
| SYS_LOG_DBG("Packet dropped by NET stack"); |
| goto out; |
| } |
| |
| net_analyze_stack("MCR20A Rx Fiber stack", |
| K_THREAD_STACK_BUFFER(mcr20a->mcr20a_rx_stack), |
| K_THREAD_STACK_SIZEOF(mcr20a->mcr20a_rx_stack)); |
| return; |
| out: |
| if (pkt) { |
| net_pkt_unref(pkt); |
| } |
| } |
| |
| /* |
| * The function checks how the XCV sequence has been completed |
| * and sets the variable seq_retval accordingly. It returns true |
| * if a new sequence is to be set. This function is only to be called |
| * when a sequence has been completed. |
| */ |
| static inline bool _irqsts1_event(struct mcr20a_context *mcr20a, |
| u8_t *dregs) |
| { |
| u8_t seq = dregs[MCR20A_PHY_CTRL1] & MCR20A_PHY_CTRL1_XCVSEQ_MASK; |
| u8_t new_seq = MCR20A_XCVSEQ_RECEIVE; |
| bool retval = false; |
| |
| switch (seq) { |
| case MCR20A_XCVSEQ_RECEIVE: |
| if ((dregs[MCR20A_IRQSTS1] & MCR20A_IRQSTS1_RXIRQ)) { |
| if ((dregs[MCR20A_IRQSTS1] & MCR20A_IRQSTS1_TXIRQ)) { |
| SYS_LOG_DBG("Finished RxSeq + TxAck"); |
| } else { |
| SYS_LOG_DBG("Finished RxSeq"); |
| } |
| |
| mcr20a_rx(mcr20a, dregs[MCR20A_RX_FRM_LEN]); |
| retval = true; |
| } |
| break; |
| case MCR20A_XCVSEQ_TX: |
| case MCR20A_XCVSEQ_TX_RX: |
| if (dregs[MCR20A_IRQSTS1] & MCR20A_IRQSTS1_CCAIRQ) { |
| if (dregs[MCR20A_IRQSTS2] & MCR20A_IRQSTS2_CCA) { |
| SYS_LOG_DBG("Finished CCA, CH busy"); |
| atomic_set(&mcr20a->seq_retval, -EBUSY); |
| retval = true; |
| break; |
| } |
| } |
| |
| if (dregs[MCR20A_IRQSTS1] & MCR20A_IRQSTS1_TXIRQ) { |
| atomic_set(&mcr20a->seq_retval, 0); |
| |
| if ((dregs[MCR20A_IRQSTS1] & MCR20A_IRQSTS1_RXIRQ)) { |
| SYS_LOG_DBG("Finished TxSeq + RxAck"); |
| /* Got Ack, timer should be disabled. */ |
| mcr20a_t4cmp_clear(mcr20a); |
| } else { |
| SYS_LOG_DBG("Finished TxSeq"); |
| } |
| |
| retval = true; |
| } |
| break; |
| case MCR20A_XCVSEQ_CONTINUOUS_CCA: |
| case MCR20A_XCVSEQ_CCA: |
| if ((dregs[MCR20A_IRQSTS1] & MCR20A_IRQSTS1_CCAIRQ)) { |
| |
| /* If CCCA, then timer should be disabled. */ |
| /* mcr20a_t4cmp_clear(mcr20a); */ |
| |
| if (dregs[MCR20A_IRQSTS2] & MCR20A_IRQSTS2_CCA) { |
| SYS_LOG_DBG("Finished CCA, CH busy"); |
| atomic_set(&mcr20a->seq_retval, -EBUSY); |
| } else { |
| /** |
| * Assume that after the CCA, |
| * a transmit sequence follows and |
| * set here the sequence manager to Idle. |
| */ |
| SYS_LOG_DBG("Finished CCA, CH idle"); |
| new_seq = MCR20A_XCVSEQ_IDLE; |
| atomic_set(&mcr20a->seq_retval, 0); |
| } |
| |
| retval = true; |
| } |
| break; |
| case MCR20A_XCVSEQ_IDLE: |
| default: |
| SYS_LOG_ERR("SEQ triggered, but XCVSEQ is in the Idle state"); |
| SYS_LOG_ERR("IRQSTS: 0x%02x", dregs[MCR20A_IRQSTS1]); |
| break; |
| } |
| |
| dregs[MCR20A_PHY_CTRL1] &= ~MCR20A_PHY_CTRL1_XCVSEQ_MASK; |
| dregs[MCR20A_PHY_CTRL1] |= new_seq; |
| |
| return retval; |
| } |
| |
| /* |
| * Check the Timer Comparator IRQ register IRQSTS3. |
| * Currently we use only T4CMP to cancel the running sequence, |
| * usually the TR. |
| */ |
| static inline bool _irqsts3_event(struct mcr20a_context *mcr20a, |
| u8_t *dregs) |
| { |
| bool retval = false; |
| |
| if (dregs[MCR20A_IRQSTS3] & MCR20A_IRQSTS3_TMR4IRQ) { |
| SYS_LOG_DBG("Sequence timeout, IRQSTSs 0x%02x 0x%02x 0x%02x", |
| dregs[MCR20A_IRQSTS1], |
| dregs[MCR20A_IRQSTS2], |
| dregs[MCR20A_IRQSTS3]); |
| |
| atomic_set(&mcr20a->seq_retval, -EBUSY); |
| mcr20a_t4cmp_clear(mcr20a); |
| dregs[MCR20A_PHY_CTRL1] &= ~MCR20A_PHY_CTRL1_XCVSEQ_MASK; |
| dregs[MCR20A_PHY_CTRL1] |= MCR20A_XCVSEQ_RECEIVE; |
| |
| /* Clear all interrupts */ |
| dregs[MCR20A_IRQSTS1] = MCR20A_IRQSTS1_IRQ_MASK; |
| retval = true; |
| } else { |
| SYS_LOG_ERR("IRQSTS3 contains untreated IRQs: 0x%02x", |
| dregs[MCR20A_IRQSTS3]); |
| } |
| |
| return retval; |
| } |
| |
| static void mcr20a_thread_main(void *arg) |
| { |
| struct device *dev = (struct device *)arg; |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t dregs[MCR20A_PHY_CTRL4 + 1]; |
| bool set_new_seq; |
| u8_t ctrl1 = 0; |
| |
| while (true) { |
| k_sem_take(&mcr20a->isr_sem, K_FOREVER); |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| set_new_seq = false; |
| |
| if (!mcr20a_mask_irqb(mcr20a, true)) { |
| SYS_LOG_ERR("Failed to mask IRQ_B"); |
| goto unmask_irqb; |
| } |
| |
| /* Read the register from IRQSTS1 until CTRL4 */ |
| if (!read_burst_irqsts1_ctrl4(&mcr20a->spi, dregs)) { |
| SYS_LOG_ERR("Failed to read register"); |
| goto unmask_irqb; |
| } |
| /* make backup from PHY_CTRL1 register */ |
| ctrl1 = dregs[MCR20A_PHY_CTRL1]; |
| |
| if (dregs[MCR20A_IRQSTS3] & MCR20A_IRQSTS3_IRQ_MASK) { |
| set_new_seq = _irqsts3_event(mcr20a, dregs); |
| } else if (dregs[MCR20A_IRQSTS1] & MCR20A_IRQSTS1_SEQIRQ) { |
| set_new_seq = _irqsts1_event(mcr20a, dregs); |
| } |
| |
| if (dregs[MCR20A_IRQSTS2] & MCR20A_IRQSTS2_IRQ_MASK) { |
| SYS_LOG_ERR("IRQSTS2 contains untreated IRQs: 0x%02x", |
| dregs[MCR20A_IRQSTS2]); |
| } |
| |
| SYS_LOG_DBG("WB: 0x%02x | 0x%02x | 0x%02x", |
| dregs[MCR20A_IRQSTS1], |
| dregs[MCR20A_IRQSTS2], |
| dregs[MCR20A_IRQSTS3]); |
| |
| /* Write back register, clear IRQs and set new sequence */ |
| if (set_new_seq) { |
| /* Reset sequence manager */ |
| ctrl1 &= ~MCR20A_PHY_CTRL1_XCVSEQ_MASK; |
| if (!write_reg_phy_ctrl1(&mcr20a->spi, ctrl1)) { |
| SYS_LOG_ERR("Failed to reset SEQ manager"); |
| } |
| |
| _xcvseq_wait_until_idle(mcr20a); |
| |
| if (!write_burst_irqsts1_ctrl1(&mcr20a->spi, dregs)) { |
| SYS_LOG_ERR("Failed to write CTRL1"); |
| } |
| } else { |
| if (!write_burst_irqsts1_irqsts3(&mcr20a->spi, dregs)) { |
| SYS_LOG_ERR("Failed to write IRQSTS3"); |
| } |
| } |
| |
| unmask_irqb: |
| if (!mcr20a_mask_irqb(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to unmask IRQ_B"); |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| |
| if (set_new_seq) { |
| k_sem_give(&mcr20a->seq_sync); |
| } |
| } |
| } |
| |
| static inline void irqb_int_handler(struct device *port, |
| struct gpio_callback *cb, u32_t pins) |
| { |
| struct mcr20a_context *mcr20a = CONTAINER_OF(cb, |
| struct mcr20a_context, |
| irqb_cb); |
| k_sem_give(&mcr20a->isr_sem); |
| } |
| |
| static inline void set_reset(struct device *dev, u32_t value) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| |
| gpio_pin_write(mcr20a->reset_gpio, |
| CONFIG_MCR20A_GPIO_RESET_PIN, value); |
| } |
| |
| static void enable_irqb_interrupt(struct mcr20a_context *mcr20a, |
| bool enable) |
| { |
| if (enable) { |
| gpio_pin_enable_callback(mcr20a->irq_gpio, |
| CONFIG_MCR20A_GPIO_IRQ_B_PIN); |
| } else { |
| gpio_pin_disable_callback(mcr20a->irq_gpio, |
| CONFIG_MCR20A_GPIO_IRQ_B_PIN); |
| } |
| } |
| |
| static inline void setup_gpio_callbacks(struct mcr20a_context *mcr20a) |
| { |
| gpio_init_callback(&mcr20a->irqb_cb, |
| irqb_int_handler, |
| BIT(CONFIG_MCR20A_GPIO_IRQ_B_PIN)); |
| gpio_add_callback(mcr20a->irq_gpio, &mcr20a->irqb_cb); |
| } |
| |
| static int mcr20a_set_cca_mode(struct device *dev, u8_t mode) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t ctrl4; |
| |
| ctrl4 = read_reg_phy_ctrl4(&mcr20a->spi); |
| ctrl4 &= ~MCR20A_PHY_CTRL4_CCATYPE_MASK; |
| ctrl4 |= set_bits_phy_ctrl4_ccatype(mode); |
| |
| if (!write_reg_phy_ctrl4(&mcr20a->spi, ctrl4)) { |
| SYS_LOG_ERR("Failed"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static enum ieee802154_hw_caps mcr20a_get_capabilities(struct device *dev) |
| { |
| return IEEE802154_HW_FCS | |
| IEEE802154_HW_2_4_GHZ | |
| IEEE802154_HW_FILTER; |
| } |
| |
| /* Note: CCA before TX is enabled by default */ |
| static int mcr20a_cca(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| int retval; |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| |
| if (!mcr20a_mask_irqb(mcr20a, true)) { |
| SYS_LOG_ERR("Failed to mask IRQ_B"); |
| goto error; |
| } |
| |
| k_sem_init(&mcr20a->seq_sync, 0, 1); |
| |
| if (mcr20a_abort_sequence(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to reset XCV sequence"); |
| goto error; |
| } |
| |
| SYS_LOG_DBG("start CCA sequence"); |
| |
| if (mcr20a_set_sequence(mcr20a, MCR20A_XCVSEQ_CCA)) { |
| SYS_LOG_ERR("Failed to reset XCV sequence"); |
| goto error; |
| } |
| |
| if (!mcr20a_mask_irqb(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to unmask IRQ_B"); |
| goto error; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| retval = k_sem_take(&mcr20a->seq_sync, MCR20A_SEQ_SYNC_TIMEOUT); |
| if (retval) { |
| SYS_LOG_ERR("Timeout occurred, %d", retval); |
| return retval; |
| } |
| |
| SYS_LOG_DBG("done"); |
| |
| return mcr20a->seq_retval; |
| |
| error: |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| return -EIO; |
| } |
| |
| static int mcr20a_set_channel(struct device *dev, u16_t channel) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t buf[3]; |
| u8_t ctrl1; |
| int retval = -EIO; |
| |
| if (channel < 11 || channel > 26) { |
| SYS_LOG_ERR("Unsupported channel %u", channel); |
| return -EINVAL; |
| } |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| |
| if (!mcr20a_mask_irqb(mcr20a, true)) { |
| SYS_LOG_ERR("Failed to mask IRQ_B"); |
| goto out; |
| } |
| |
| ctrl1 = read_reg_phy_ctrl1(&mcr20a->spi); |
| |
| if (mcr20a_abort_sequence(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to reset XCV sequence"); |
| goto out; |
| } |
| |
| SYS_LOG_DBG("%u", channel); |
| channel -= 11; |
| buf[0] = set_bits_pll_int0_val(pll_int_lt[channel]); |
| buf[1] = (u8_t)pll_frac_lt[channel]; |
| buf[2] = (u8_t)(pll_frac_lt[channel] >> 8); |
| |
| if (!write_burst_pll_int0(&mcr20a->spi, buf)) { |
| SYS_LOG_ERR("Failed to set PLL"); |
| goto out; |
| } |
| |
| if (mcr20a_set_sequence(mcr20a, ctrl1)) { |
| SYS_LOG_ERR("Failed to restore XCV sequence"); |
| goto out; |
| } |
| |
| retval = 0; |
| |
| out: |
| if (!mcr20a_mask_irqb(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to unmask IRQ_B"); |
| retval = -EIO; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| |
| return retval; |
| } |
| |
| static int mcr20a_set_pan_id(struct device *dev, u16_t pan_id) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| |
| pan_id = sys_le16_to_cpu(pan_id); |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| |
| if (!write_burst_pan_id(&mcr20a->spi, (u8_t *) &pan_id)) { |
| SYS_LOG_ERR("FAILED"); |
| return -EIO; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| SYS_LOG_DBG("0x%x", pan_id); |
| |
| return 0; |
| } |
| |
| static int mcr20a_set_short_addr(struct device *dev, u16_t short_addr) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| |
| short_addr = sys_le16_to_cpu(short_addr); |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| |
| if (!write_burst_short_addr(&mcr20a->spi, (u8_t *) &short_addr)) { |
| SYS_LOG_ERR("FAILED"); |
| return -EIO; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| SYS_LOG_DBG("0x%x", short_addr); |
| |
| return 0; |
| } |
| |
| static int mcr20a_set_ieee_addr(struct device *dev, const u8_t *ieee_addr) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| |
| if (!write_burst_ext_addr(&mcr20a->spi, (void *)ieee_addr)) { |
| SYS_LOG_ERR("Failed"); |
| return -EIO; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| SYS_LOG_DBG("IEEE address %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", |
| ieee_addr[7], ieee_addr[6], ieee_addr[5], ieee_addr[4], |
| ieee_addr[3], ieee_addr[2], ieee_addr[1], ieee_addr[0]); |
| |
| return 0; |
| } |
| |
| static int mcr20a_set_filter(struct device *dev, |
| enum ieee802154_filter_type type, |
| const struct ieee802154_filter *filter) |
| { |
| SYS_LOG_DBG("Applying filter %u", type); |
| |
| if (type == IEEE802154_FILTER_TYPE_IEEE_ADDR) { |
| return mcr20a_set_ieee_addr(dev, filter->ieee_addr); |
| } else if (type == IEEE802154_FILTER_TYPE_SHORT_ADDR) { |
| return mcr20a_set_short_addr(dev, filter->short_addr); |
| } else if (type == IEEE802154_FILTER_TYPE_PAN_ID) { |
| return mcr20a_set_pan_id(dev, filter->pan_id); |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int mcr20a_set_txpower(struct device *dev, s16_t dbm) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t pwr; |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| SYS_LOG_DBG("%d", dbm); |
| |
| if ((dbm > MCR20A_OUTPUT_POWER_MAX) || |
| (dbm < MCR20A_OUTPUT_POWER_MIN)) { |
| goto error; |
| } |
| |
| pwr = pow_lt[dbm - MCR20A_OUTPUT_POWER_MIN]; |
| if (!write_reg_pa_pwr(&mcr20a->spi, set_bits_pa_pwr_val(pwr))) { |
| goto error; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| return 0; |
| |
| error: |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| SYS_LOG_DBG("Failed"); |
| return -EIO; |
| } |
| |
| static inline bool write_txfifo_content(struct mcr20a_spi *spi, |
| struct net_pkt *pkt, |
| struct net_buf *frag) |
| { |
| u8_t cmd[2 + MCR20A_PSDU_LENGTH]; |
| u8_t payload_len = net_pkt_ll_reserve(pkt) + frag->len; |
| u8_t *payload = frag->data - net_pkt_ll_reserve(pkt); |
| bool retval; |
| |
| k_sem_take(&spi->spi_sem, K_FOREVER); |
| |
| cmd[0] = MCR20A_BUF_WRITE; |
| /** |
| * The length of the packet (PSDU + FSC), |
| * is stored at index 0, followed by the PSDU. |
| * Note: maximum FRAME_LEN is 125 + MCR20A_FCS_LENGTH |
| */ |
| cmd[1] = payload_len + MCR20A_FCS_LENGTH; |
| |
| if (payload_len > MCR20A_PSDU_LENGTH) { |
| SYS_LOG_ERR("Payload too long"); |
| return 0; |
| } |
| memcpy(&cmd[2], payload, payload_len); |
| |
| spi_slave_select(spi->dev, spi->slave); |
| |
| retval = (spi_transceive(spi->dev, |
| cmd, (2 + payload_len), |
| cmd, (2 + payload_len)) == 0); |
| |
| k_sem_give(&spi->spi_sem); |
| |
| return retval; |
| } |
| |
| static int mcr20a_tx(struct device *dev, |
| struct net_pkt *pkt, |
| struct net_buf *frag) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t seq = MCR20A_AUTOACK_ENABLED ? MCR20A_XCVSEQ_TX_RX : |
| MCR20A_XCVSEQ_TX; |
| int retval; |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| |
| SYS_LOG_DBG("%p (%u)", |
| frag, net_pkt_ll_reserve(pkt) + frag->len); |
| |
| if (!mcr20a_mask_irqb(mcr20a, true)) { |
| SYS_LOG_ERR("Failed to mask IRQ_B"); |
| goto error; |
| } |
| |
| if (mcr20a_abort_sequence(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to reset XCV sequence"); |
| goto error; |
| } |
| |
| if (!write_txfifo_content(&mcr20a->spi, pkt, frag)) { |
| SYS_LOG_ERR("Did not write properly into TX FIFO"); |
| goto error; |
| } |
| |
| k_sem_init(&mcr20a->seq_sync, 0, 1); |
| |
| if (mcr20a_set_sequence(mcr20a, seq)) { |
| SYS_LOG_ERR("Cannot start transmission"); |
| goto error; |
| } |
| |
| if (!mcr20a_mask_irqb(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to unmask IRQ_B"); |
| goto error; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| retval = k_sem_take(&mcr20a->seq_sync, MCR20A_SEQ_SYNC_TIMEOUT); |
| if (retval) { |
| SYS_LOG_ERR("Timeout occurred, %d", retval); |
| return retval; |
| } |
| |
| SYS_LOG_DBG("done"); |
| |
| return mcr20a->seq_retval; |
| |
| error: |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| return -EIO; |
| } |
| |
| static int mcr20a_start(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t timeout = 6; |
| u8_t status; |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| enable_irqb_interrupt(mcr20a, false); |
| |
| if (!write_reg_pwr_modes(&mcr20a->spi, MCR20A_PM_AUTODOZE)) { |
| SYS_LOG_ERR("Error starting MCR20A"); |
| goto error; |
| } |
| |
| do { |
| _usleep(50); |
| timeout--; |
| status = read_reg_pwr_modes(&mcr20a->spi); |
| } while (!(status & MCR20A_PWR_MODES_XTAL_READY) && timeout); |
| |
| if (!(status & MCR20A_PWR_MODES_XTAL_READY)) { |
| SYS_LOG_ERR("Timeout, failed to wake up"); |
| goto error; |
| } |
| |
| /* Clear all interrupt flags */ |
| write_reg_irqsts1(&mcr20a->spi, MCR20A_IRQSTS1_IRQ_MASK); |
| write_reg_irqsts2(&mcr20a->spi, MCR20A_IRQSTS2_IRQ_MASK); |
| write_reg_irqsts3(&mcr20a->spi, MCR20A_IRQSTS3_IRQ_MASK | |
| MCR20A_IRQSTS3_TMR_MASK); |
| |
| if (mcr20a_abort_sequence(mcr20a, true)) { |
| SYS_LOG_ERR("Failed to reset XCV sequence"); |
| goto error; |
| } |
| |
| if (mcr20a_set_sequence(mcr20a, MCR20A_XCVSEQ_RECEIVE)) { |
| SYS_LOG_ERR("Failed to set XCV sequence"); |
| goto error; |
| } |
| |
| enable_irqb_interrupt(mcr20a, true); |
| |
| if (!mcr20a_mask_irqb(mcr20a, false)) { |
| SYS_LOG_ERR("Failed to unmask IRQ_B"); |
| goto error; |
| } |
| |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| SYS_LOG_DBG("started"); |
| |
| return 0; |
| |
| error: |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| return -EIO; |
| } |
| |
| static int mcr20a_stop(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t power_mode; |
| |
| k_mutex_lock(&mcr20a->phy_mutex, K_FOREVER); |
| |
| if (!mcr20a_mask_irqb(mcr20a, true)) { |
| SYS_LOG_ERR("Failed to mask IRQ_B"); |
| goto error; |
| } |
| |
| if (mcr20a_abort_sequence(mcr20a, true)) { |
| SYS_LOG_ERR("Failed to reset XCV sequence"); |
| goto error; |
| } |
| |
| enable_irqb_interrupt(mcr20a, false); |
| |
| if (PART_OF_KW2XD_SIP) { |
| power_mode = MCR20A_PM_DOZE; |
| } else { |
| power_mode = MCR20A_PM_HIBERNATE; |
| } |
| |
| if (!write_reg_pwr_modes(&mcr20a->spi, power_mode)) { |
| goto error; |
| } |
| |
| SYS_LOG_DBG("stopped"); |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| |
| return 0; |
| |
| error: |
| k_mutex_unlock(&mcr20a->phy_mutex); |
| SYS_LOG_ERR("Error stopping MCR20A"); |
| return -EIO; |
| } |
| |
| static int mcr20a_update_overwrites(struct mcr20a_context *dev) |
| { |
| struct mcr20a_spi *spi = &dev->spi; |
| |
| if (!write_reg_overwrite_ver(spi, overwrites_direct[0].data)) { |
| goto error; |
| } |
| |
| k_sem_take(&spi->spi_sem, K_FOREVER); |
| |
| for (u8_t i = 0; |
| i < sizeof(overwrites_indirect) / sizeof(overwrites_t); |
| i++) { |
| |
| spi->cmd_buf[0] = MCR20A_IAR_INDEX | MCR20A_REG_WRITE; |
| spi->cmd_buf[1] = overwrites_indirect[i].address; |
| spi->cmd_buf[2] = overwrites_indirect[i].data; |
| |
| spi_slave_select(spi->dev, spi->slave); |
| |
| if (spi_write(spi->dev, spi->cmd_buf, 3)) { |
| k_sem_give(&spi->spi_sem); |
| goto error; |
| } |
| } |
| |
| k_sem_give(&spi->spi_sem); |
| |
| return 0; |
| |
| error: |
| SYS_LOG_ERR("Error update overwrites"); |
| return -EIO; |
| } |
| |
| static int power_on_and_setup(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t timeout = 6; |
| u32_t status; |
| u8_t tmp = 0; |
| |
| if (!PART_OF_KW2XD_SIP) { |
| set_reset(dev, 0); |
| _usleep(150); |
| set_reset(dev, 1); |
| |
| do { |
| _usleep(50); |
| timeout--; |
| gpio_pin_read(mcr20a->irq_gpio, |
| CONFIG_MCR20A_GPIO_IRQ_B_PIN, &status); |
| } while (status && timeout); |
| |
| if (status) { |
| SYS_LOG_ERR("Timeout, failed to get WAKE IRQ"); |
| return -EIO; |
| } |
| |
| } |
| |
| tmp = MCR20A_CLK_OUT_CONFIG | MCR20A_CLK_OUT_EXTEND; |
| write_reg_clk_out_ctrl(&mcr20a->spi, tmp); |
| |
| if (read_reg_clk_out_ctrl(&mcr20a->spi) != tmp) { |
| SYS_LOG_ERR("Failed to get device up"); |
| return -EIO; |
| } |
| |
| /* Clear all interrupt flags */ |
| write_reg_irqsts1(&mcr20a->spi, MCR20A_IRQSTS1_IRQ_MASK); |
| write_reg_irqsts2(&mcr20a->spi, MCR20A_IRQSTS2_IRQ_MASK); |
| write_reg_irqsts3(&mcr20a->spi, MCR20A_IRQSTS3_IRQ_MASK | |
| MCR20A_IRQSTS3_TMR_MASK); |
| |
| mcr20a_update_overwrites(mcr20a); |
| mcr20a_timer_init(dev, MCR20A_TIMEBASE_62500HZ); |
| |
| mcr20a_set_txpower(dev, MCR20A_DEFAULT_TX_POWER); |
| mcr20a_set_channel(dev, MCR20A_DEFAULT_CHANNEL); |
| mcr20a_set_cca_mode(dev, 1); |
| write_reg_rx_wtr_mark(&mcr20a->spi, 8); |
| |
| /* Configure PHY behaviour */ |
| tmp = MCR20A_PHY_CTRL1_CCABFRTX; |
| if (MCR20A_AUTOACK_ENABLED) { |
| tmp |= MCR20A_PHY_CTRL1_AUTOACK | |
| MCR20A_PHY_CTRL1_RXACKRQD; |
| } |
| write_reg_phy_ctrl1(&mcr20a->spi, tmp); |
| |
| /* Enable Sequence-end interrupt */ |
| tmp = MCR20A_PHY_CTRL2_SEQMSK; |
| write_reg_phy_ctrl2(&mcr20a->spi, ~tmp); |
| |
| setup_gpio_callbacks(mcr20a); |
| |
| return 0; |
| } |
| |
| |
| static inline int configure_gpios(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| |
| /* setup gpio for the modem interrupt */ |
| mcr20a->irq_gpio = device_get_binding(CONFIG_MCR20A_GPIO_IRQ_B_NAME); |
| if (mcr20a->irq_gpio == NULL) { |
| SYS_LOG_ERR("Failed to get pointer to %s device", |
| CONFIG_MCR20A_GPIO_IRQ_B_NAME); |
| return -EINVAL; |
| } |
| |
| gpio_pin_configure(mcr20a->irq_gpio, |
| CONFIG_MCR20A_GPIO_IRQ_B_PIN, |
| GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE | |
| GPIO_PUD_PULL_UP | |
| GPIO_INT_ACTIVE_LOW); |
| |
| /* setup gpio for the modems reset */ |
| mcr20a->reset_gpio = device_get_binding(CONFIG_MCR20A_GPIO_RESET_NAME); |
| if (mcr20a->reset_gpio == NULL) { |
| SYS_LOG_ERR("Failed to get pointer to %s device", |
| CONFIG_MCR20A_GPIO_RESET_NAME); |
| return -EINVAL; |
| } |
| |
| gpio_pin_configure(mcr20a->reset_gpio, CONFIG_MCR20A_GPIO_RESET_PIN, |
| GPIO_DIR_OUT); |
| set_reset(dev, 1); |
| |
| return 0; |
| } |
| |
| static inline int configure_spi(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| struct spi_config spi_conf = { |
| .config = SPI_WORD(8), |
| .max_sys_freq = CONFIG_IEEE802154_MCR20A_SPI_FREQ, |
| }; |
| |
| mcr20a->spi.dev = device_get_binding( |
| CONFIG_IEEE802154_MCR20A_SPI_DRV_NAME); |
| if (!mcr20a->spi.dev) { |
| SYS_LOG_ERR("Unable to get SPI device"); |
| return -ENODEV; |
| } |
| |
| mcr20a->spi.slave = CONFIG_IEEE802154_MCR20A_SPI_SLAVE; |
| |
| if (spi_configure(mcr20a->spi.dev, &spi_conf) != 0 || |
| spi_slave_select(mcr20a->spi.dev, |
| mcr20a->spi.slave) != 0) { |
| mcr20a->spi.dev = NULL; |
| return -EIO; |
| } |
| |
| SYS_LOG_DBG("SPI configured %s, %d", |
| CONFIG_IEEE802154_MCR20A_SPI_DRV_NAME, |
| CONFIG_IEEE802154_MCR20A_SPI_SLAVE); |
| |
| return 0; |
| } |
| |
| static int mcr20a_init(struct device *dev) |
| { |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| |
| k_sem_init(&mcr20a->spi.spi_sem, 1, UINT_MAX); |
| |
| k_mutex_init(&mcr20a->phy_mutex); |
| k_sem_init(&mcr20a->isr_sem, 0, 1); |
| |
| SYS_LOG_DBG("\nInitialize MCR20A Transceiver\n"); |
| |
| if (configure_gpios(dev) != 0) { |
| SYS_LOG_ERR("Configuring GPIOS failed"); |
| return -EIO; |
| } |
| |
| if (configure_spi(dev) != 0) { |
| SYS_LOG_ERR("Configuring SPI failed"); |
| return -EIO; |
| } |
| |
| SYS_LOG_DBG("GPIO and SPI configured"); |
| |
| if (power_on_and_setup(dev) != 0) { |
| SYS_LOG_ERR("Configuring MCR20A failed"); |
| return -EIO; |
| } |
| |
| k_thread_create(&mcr20a->mcr20a_rx_thread, mcr20a->mcr20a_rx_stack, |
| CONFIG_IEEE802154_MCR20A_RX_STACK_SIZE, |
| (k_thread_entry_t)mcr20a_thread_main, |
| dev, NULL, NULL, K_PRIO_COOP(2), 0, 0); |
| |
| return 0; |
| } |
| |
| static void mcr20a_iface_init(struct net_if *iface) |
| { |
| struct device *dev = net_if_get_device(iface); |
| struct mcr20a_context *mcr20a = dev->driver_data; |
| u8_t *mac = get_mac(dev); |
| |
| net_if_set_link_addr(iface, mac, 8, NET_LINK_IEEE802154); |
| |
| mcr20a->iface = iface; |
| |
| ieee802154_init(iface); |
| |
| SYS_LOG_DBG("done"); |
| } |
| |
| static struct mcr20a_context mcr20a_context_data; |
| |
| static struct ieee802154_radio_api mcr20a_radio_api = { |
| .iface_api.init = mcr20a_iface_init, |
| .iface_api.send = ieee802154_radio_send, |
| |
| .get_capabilities = mcr20a_get_capabilities, |
| .cca = mcr20a_cca, |
| .set_channel = mcr20a_set_channel, |
| .set_filter = mcr20a_set_filter, |
| .set_txpower = mcr20a_set_txpower, |
| .start = mcr20a_start, |
| .stop = mcr20a_stop, |
| .tx = mcr20a_tx, |
| }; |
| |
| #if defined(CONFIG_IEEE802154_MCR20A_RAW) |
| DEVICE_AND_API_INIT(mcr20a, CONFIG_IEEE802154_MCR20A_DRV_NAME, |
| mcr20a_init, &mcr20a_context_data, NULL, |
| POST_KERNEL, CONFIG_IEEE802154_MCR20A_INIT_PRIO, |
| &mcr20a_radio_api); |
| #else |
| NET_DEVICE_INIT(mcr20a, CONFIG_IEEE802154_MCR20A_DRV_NAME, |
| mcr20a_init, &mcr20a_context_data, NULL, |
| CONFIG_IEEE802154_MCR20A_INIT_PRIO, |
| &mcr20a_radio_api, IEEE802154_L2, |
| NET_L2_GET_CTX_TYPE(IEEE802154_L2), |
| MCR20A_PSDU_LENGTH); |
| #endif |