1489 lines
34 KiB
C
1489 lines
34 KiB
C
/* 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;
|
||
}
|
||
|
||
mcr20a->lqi = read_reg_lqi_value(&mcr20a->spi);
|
||
SYS_LOG_DBG("Caught a packet (%u) (LQI: %u, RSSI: %u)",
|
||
pkt_len, mcr20a->lqi,
|
||
mcr20a_get_rssi(mcr20a->lqi));
|
||
|
||
#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;
|
||
}
|
||
|
||
/* 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_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 u8_t mcr20a_get_lqi(struct device *dev)
|
||
{
|
||
struct mcr20a_context *mcr20a = dev->driver_data;
|
||
|
||
SYS_LOG_DBG("");
|
||
return mcr20a->lqi;
|
||
}
|
||
|
||
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,
|
||
|
||
.cca = mcr20a_cca,
|
||
.set_channel = mcr20a_set_channel,
|
||
.set_pan_id = mcr20a_set_pan_id,
|
||
.set_short_addr = mcr20a_set_short_addr,
|
||
.set_ieee_addr = mcr20a_set_ieee_addr,
|
||
.set_txpower = mcr20a_set_txpower,
|
||
.start = mcr20a_start,
|
||
.stop = mcr20a_stop,
|
||
.tx = mcr20a_tx,
|
||
.get_lqi = mcr20a_get_lqi,
|
||
};
|
||
|
||
#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
|