From 91787dcb5cd52809ab8fb6e9947fc0a0a6084865 Mon Sep 17 00:00:00 2001
From: Semjon Kerner <semjon.kerner@fu-berlin.de>
Date: Mon, 4 Mar 2019 13:18:47 +0100
Subject: [PATCH] cpu/nrf52: add 802.15.4 radio driver

---
 cpu/nrf52/Makefile                    |   5 +
 cpu/nrf52/Makefile.dep                |   6 +
 cpu/nrf52/Makefile.include            |   4 +
 cpu/nrf52/include/nrf802154.h         |  61 ++++
 cpu/nrf52/radio/nrf802154/Makefile    |   3 +
 cpu/nrf52/radio/nrf802154/nrf802154.c | 446 ++++++++++++++++++++++++++
 6 files changed, 525 insertions(+)
 create mode 100644 cpu/nrf52/Makefile.dep
 create mode 100644 cpu/nrf52/include/nrf802154.h
 create mode 100644 cpu/nrf52/radio/nrf802154/Makefile
 create mode 100644 cpu/nrf52/radio/nrf802154/nrf802154.c

diff --git a/cpu/nrf52/Makefile b/cpu/nrf52/Makefile
index 7600b58931..59c4f48c9b 100644
--- a/cpu/nrf52/Makefile
+++ b/cpu/nrf52/Makefile
@@ -7,4 +7,9 @@ DIRS = periph $(RIOTCPU)/cortexm_common $(RIOTCPU)/nrf5x_common
 # (file triggers compiler bug. see #5775)
 SRC_NOLTO += vectors.c
 
+# build the nrf802154 driver if selected
+ifneq (,$(filter nrf802154,$(USEMODULE)))
+    DIRS += radio/nrf802154
+endif
+
 include $(RIOTBASE)/Makefile.base
diff --git a/cpu/nrf52/Makefile.dep b/cpu/nrf52/Makefile.dep
new file mode 100644
index 0000000000..d5e4d42993
--- /dev/null
+++ b/cpu/nrf52/Makefile.dep
@@ -0,0 +1,6 @@
+ifneq (,$(filter nrf802154,$(USEMODULE)))
+  FEATURES_REQUIRED += periph_timer
+  FEATURES_REQUIRED += radio_nrf802154
+  USEMODULE += luid
+  USEMODULE += netdev_ieee802154
+endif
diff --git a/cpu/nrf52/Makefile.include b/cpu/nrf52/Makefile.include
index d6e26d6147..03647d823b 100644
--- a/cpu/nrf52/Makefile.include
+++ b/cpu/nrf52/Makefile.include
@@ -7,5 +7,9 @@ export MCUBOOT_SLOT0_SIZE = 0x8000
 export MCUBOOT_SLOT1_SIZE = 0x3C000
 export MCUBOOT_SLOT2_SIZE = 0x3C000
 
+ifneq (,$(filter nrf802154,$(USEMODULE)))
+  CFLAGS += -DGNRC_NETIF_MSG_QUEUE_SIZE=16
+endif
+
 include $(RIOTCPU)/nrf5x_common/Makefile.include
 include $(RIOTMAKE)/arch/cortexm.inc.mk
diff --git a/cpu/nrf52/include/nrf802154.h b/cpu/nrf52/include/nrf802154.h
new file mode 100644
index 0000000000..de55018e77
--- /dev/null
+++ b/cpu/nrf52/include/nrf802154.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    drivers_nrf52_802154 IEEE802.15.4 Driver for nRF52840 SoCs
+ * @ingroup     drivers_netdev
+ * @brief       Driver for using the nRF52's radio in IEEE802.15.4 mode
+ *
+ * ## Implementation state ##
+ * Netdev events supported:
+ *
+ * - NETDEV_EVENT_RX_COMPLETE
+ * - NETDEV_EVENT_TX_COMPLETE
+ *
+ * Transmission options not yet impemented:
+ * - Send acknowledgement for packages
+ * - Request acknowledgement
+ * - Retransmit unacked packages
+ * - Carrier Sense Multiple Access (CSMA) and Implementation of Clear Channel
+ *   Assessment Control (CCACTRL)
+ *
+ * @{
+ *
+ * @file
+ * @brief       Driver interface for using the nRF52 in IEEE802.15.4 mode
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ * @author      Semjon Kerner <semjon.kerner@fu-berlin.de>
+ */
+
+#ifndef NRF802154_H
+#define NRF802154_H
+
+#include "net/netdev/ieee802154.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Export the netdev device descriptor
+ */
+extern netdev_ieee802154_t nrf802154_dev;
+
+/**
+ * @brief   IEEE 802.15.4 radio timer configuration
+ *
+ *          this radio relies on a dedicated hardware timer to maintain IFS
+ *          the default timer may be overwritten in the board configuration
+ */
+#ifndef NRF802154_TIMER
+#define NRF802154_TIMER TIMER_DEV(1)
+#endif
+
+#endif /* NRF802154_H */
+/** @} */
diff --git a/cpu/nrf52/radio/nrf802154/Makefile b/cpu/nrf52/radio/nrf802154/Makefile
new file mode 100644
index 0000000000..b2dbcf3dfe
--- /dev/null
+++ b/cpu/nrf52/radio/nrf802154/Makefile
@@ -0,0 +1,3 @@
+MODULE = nrf802154
+
+include $(RIOTBASE)/Makefile.base
diff --git a/cpu/nrf52/radio/nrf802154/nrf802154.c b/cpu/nrf52/radio/nrf802154/nrf802154.c
new file mode 100644
index 0000000000..b69499a84c
--- /dev/null
+++ b/cpu/nrf52/radio/nrf802154/nrf802154.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2019 Freie Universität Berlin
+ *               2019 HAW Hamburg
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     drivers_nrf52_802154
+ * @{
+ *
+ * @file
+ * @brief       Implementation of the radio driver for nRF52 radios
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ * @author      Dimitri Nahm <dimitri.nahm@haw-hamburg.de>
+ * @author      Semjon Kerner <semjon.kerner@fu-berlin.de>
+ * @}
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include "cpu.h"
+#include "luid.h"
+#include "mutex.h"
+
+#include "net/ieee802154.h"
+#include "periph/timer.h"
+#include "net/netdev/ieee802154.h"
+#include "nrf802154.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+static const netdev_driver_t nrf802154_netdev_driver;
+
+netdev_ieee802154_t nrf802154_dev = {
+    {
+        .driver = &nrf802154_netdev_driver,
+        .event_callback = NULL,
+        .context = NULL,
+    },
+#ifdef MODULE_GNRC
+#ifdef MODULE_GNRC_SIXLOWPAN
+    .proto = GNRC_NETTYPE_SIXLOWPAN,
+#else
+    .proto = GNRC_NETTYPE_UNDEF,
+#endif
+#endif
+    .pan = IEEE802154_DEFAULT_PANID,
+    .short_addr = { 0, 0 },
+    .long_addr = { 0, 0, 0, 0, 0, 0, 0, 0 },
+    .chan = IEEE802154_DEFAULT_CHANNEL,
+    .flags = 0
+};
+
+static uint8_t rxbuf[IEEE802154_FRAME_LEN_MAX + 3]; /* len PHR + PSDU + LQI */
+static uint8_t txbuf[IEEE802154_FRAME_LEN_MAX + 3]; /* len PHR + PSDU + LQI */
+
+#define RX_COMPLETE         (0x1)
+#define TX_COMPLETE         (0x2)
+#define LIFS                (40U)
+#define SIFS                (12U)
+#define SIFS_MAXPKTSIZE     (18U)
+#define TIMER_FREQ          (250000UL)
+static volatile uint8_t _state;
+static mutex_t _txlock;
+
+/**
+ * @brief   Set radio into DISABLED state
+ */
+static void _disable(void)
+{
+    /* set device into DISABLED state */
+    if (NRF_RADIO->STATE != RADIO_STATE_STATE_Disabled) {
+        NRF_RADIO->EVENTS_DISABLED = 0;
+        NRF_RADIO->TASKS_DISABLE = 1;
+        while (!(NRF_RADIO->EVENTS_DISABLED)) {};
+        DEBUG("[nrf802154] Device state: DISABLED\n");
+    }
+}
+
+/**
+ * @brief   Set radio into RXIDLE state
+ */
+static void _enable_rx(void)
+{
+    DEBUG("[nrf802154] Set device state to RXIDLE\n");
+    /* set device into RXIDLE state */
+    if (NRF_RADIO->STATE != RADIO_STATE_STATE_RxIdle) {
+        _disable();
+    }
+    NRF_RADIO->PACKETPTR = (uint32_t)rxbuf;
+    NRF_RADIO->EVENTS_RXREADY = 0;
+    NRF_RADIO->TASKS_RXEN = 1;
+    while (!(NRF_RADIO->EVENTS_RXREADY)) {};
+    DEBUG("[nrf802154] Device state: RXIDLE\n");
+}
+
+/**
+ * @brief   Set radio into TXIDLE state
+ */
+static void _enable_tx(void)
+{
+    DEBUG("[nrf802154] Set device state to TXIDLE\n");
+    /* set device into TXIDLE state */
+    if (NRF_RADIO->STATE != RADIO_STATE_STATE_TxIdle) {
+        _disable();
+    }
+    NRF_RADIO->PACKETPTR = (uint32_t)txbuf;
+    NRF_RADIO->EVENTS_TXREADY = 0;
+    NRF_RADIO->TASKS_TXEN = 1;
+    while (!(NRF_RADIO->EVENTS_TXREADY)) {};
+    DEBUG("[nrf802154] Device state: TXIDLE\n");
+}
+
+/**
+ * @brief   Reset the RXIDLE state
+ */
+ static void _reset_rx(void)
+ {
+    if (NRF_RADIO->STATE != RADIO_STATE_STATE_RxIdle) {
+        return;
+    }
+
+    /* reset RX state and listen for new packets */
+    _state &= ~RX_COMPLETE;
+    NRF_RADIO->TASKS_START = 1;
+ }
+
+static void _set_chan(uint16_t chan)
+{
+    assert((chan >= IEEE802154_CHANNEL_MIN) && (chan <= IEEE802154_CHANNEL_MAX));
+    /* Channel map between 2400 MHZ ... 2500 MHz
+     * -> Frequency = 2400 + FREQUENCY (MHz) */
+    NRF_RADIO->FREQUENCY = (chan - 10) * 5;
+    nrf802154_dev.chan = chan;
+}
+
+static int16_t _get_txpower(void)
+{
+    int8_t txpower = (int8_t)NRF_RADIO->TXPOWER;
+    if (txpower < 0) {
+        return (int16_t)(0xff00 | txpower);
+    }
+    return (int16_t)txpower;
+}
+
+static void _set_txpower(int16_t txpower)
+{
+    if (txpower > 8) {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos8dBm;
+    }
+    if (txpower > 1) {
+        NRF_RADIO->TXPOWER = (uint32_t)txpower;
+    }
+    else if (txpower > -1) {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_0dBm;
+    }
+    else if (txpower > -5) {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg4dBm;
+    }
+    else if (txpower > -9) {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg8dBm;
+    }
+    else if (txpower > -13) {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg12dBm;
+    }
+    else if (txpower > -17) {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg16dBm;
+    }
+    else if (txpower > -21) {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg20dBm;
+    }
+    else {
+        NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg40dBm;
+    }
+}
+
+static void _timer_cb(void *arg, int chan)
+{
+    (void)arg;
+    (void)chan;
+    mutex_unlock(&_txlock);
+    timer_stop(NRF802154_TIMER);
+    timer_clear(NRF802154_TIMER, 0);
+}
+
+static int _init(netdev_t *dev)
+{
+    (void)dev;
+
+    int result = timer_init(NRF802154_TIMER, TIMER_FREQ, _timer_cb, NULL);
+    assert(result >= 0);
+    (void)result;
+
+    /* initialize local variables */
+    mutex_init(&_txlock);
+
+    /* reset buffer */
+    rxbuf[0] = 0;
+    txbuf[0] = 0;
+    _state = 0;
+
+    /* power on peripheral */
+    NRF_RADIO->POWER = 1;
+
+    /* make sure the radio is disabled/stopped */
+    _disable();
+
+    /* we configure it to run in IEEE802.15.4 mode */
+    NRF_RADIO->MODE = RADIO_MODE_MODE_Ieee802154_250Kbit;
+    /* and set some fitting configuration */
+    NRF_RADIO->PCNF0 = ((8 << RADIO_PCNF0_LFLEN_Pos) |
+                        (RADIO_PCNF0_PLEN_32bitZero << RADIO_PCNF0_PLEN_Pos) |
+                        (RADIO_PCNF0_CRCINC_Include << RADIO_PCNF0_CRCINC_Pos));
+    NRF_RADIO->PCNF1 = IEEE802154_FRAME_LEN_MAX;
+    /* set start frame delimiter */
+    NRF_RADIO->SFD = IEEE802154_SFD;
+    /* set MHR filters */
+    NRF_RADIO->MHRMATCHCONF = 0;              /* Search Pattern Configuration */
+    NRF_RADIO->MHRMATCHMAS = 0xff0007ff;      /* Pattern mask */
+    /* configure CRC conform to IEEE802154 */
+    NRF_RADIO->CRCCNF = ((RADIO_CRCCNF_LEN_Two << RADIO_CRCCNF_LEN_Pos) |
+                         (RADIO_CRCCNF_SKIPADDR_Ieee802154 << RADIO_CRCCNF_SKIPADDR_Pos));
+    NRF_RADIO->CRCPOLY = 0x011021;
+    NRF_RADIO->CRCINIT = 0;
+
+    /* assign default addresses */
+    luid_get(nrf802154_dev.short_addr, IEEE802154_SHORT_ADDRESS_LEN);
+    luid_get(nrf802154_dev.long_addr, IEEE802154_LONG_ADDRESS_LEN);
+
+    /* set default channel */
+    _set_chan(nrf802154_dev.chan);
+
+    /* configure some shortcuts */
+    NRF_RADIO->SHORTS = RADIO_SHORTS_RXREADY_START_Msk | RADIO_SHORTS_TXREADY_START_Msk;
+
+    /* enable interrupts */
+    NVIC_EnableIRQ(RADIO_IRQn);
+    NRF_RADIO->INTENSET = RADIO_INTENSET_END_Msk;
+
+    /* switch to RX mode */
+    _enable_rx();
+
+    return 0;
+}
+
+static int _send(netdev_t *dev,  const iolist_t *iolist)
+{
+    (void)dev;
+
+    DEBUG("[nrf802154] Send a packet\n");
+
+    assert(iolist);
+
+    mutex_lock(&_txlock);
+
+    /* copy packet data into the transmit buffer */
+    unsigned int len = 0;
+    for (; iolist; iolist = iolist->iol_next) {
+        if ((IEEE802154_FCS_LEN + len + iolist->iol_len) > (IEEE802154_FRAME_LEN_MAX)) {
+            DEBUG("[nrf802154] send: unable to do so, packet is too large!\n");
+            mutex_unlock(&_txlock);
+            return -EOVERFLOW;
+        }
+        memcpy(&txbuf[len + 1], iolist->iol_base, iolist->iol_len);
+        len += iolist->iol_len;
+    }
+
+    /* specify the length of the package. */
+    txbuf[0] = len + IEEE802154_FCS_LEN;
+
+    /* trigger the actual transmission */
+    _enable_tx();
+    DEBUG("[nrf802154] send: putting %i byte into the ether\n", len);
+
+    /* set interframe spacing based on packet size */
+    unsigned int ifs = (len > SIFS_MAXPKTSIZE) ? LIFS : SIFS;
+    timer_set_absolute(NRF802154_TIMER, 0, ifs);
+
+    return len;
+}
+
+static int _recv(netdev_t *dev, void *buf, size_t len, void *info)
+{
+    (void)dev;
+    (void)info;
+
+    size_t pktlen = (size_t)rxbuf[0] - IEEE802154_FCS_LEN;
+
+    /* check if packet data is readable */
+    if (!(_state & RX_COMPLETE)) {
+        DEBUG("[nrf802154] recv: no packet data available\n");
+        return 0;
+    }
+
+    if (buf == NULL) {
+        if (len > 0) {
+            /* drop packet */
+            DEBUG("[nrf802154] recv: dropping packet of length %i\n", pktlen);
+        }
+        else {
+          /* return packet length */
+          DEBUG("[nrf802154] recv: return packet length: %i\n", pktlen);
+          return pktlen;
+        }
+    }
+    else if (len < pktlen) {
+        DEBUG("[nrf802154] recv: buffer is to small\n");
+        return -ENOBUFS;
+    }
+    else {
+        DEBUG("[nrf802154] recv: reading packet of length %i\n", pktlen);
+        memcpy(buf, &rxbuf[1], pktlen);
+    }
+
+    _reset_rx();
+
+    return (int)pktlen;
+}
+
+static void _isr(netdev_t *dev)
+{
+    if (!nrf802154_dev.netdev.event_callback) {
+        return;
+    }
+    if (_state & RX_COMPLETE) {
+        nrf802154_dev.netdev.event_callback(dev, NETDEV_EVENT_RX_COMPLETE);
+    }
+    if (_state & TX_COMPLETE) {
+        nrf802154_dev.netdev.event_callback(dev, NETDEV_EVENT_TX_COMPLETE);
+        _state &= ~TX_COMPLETE;
+    }
+}
+
+static int _get(netdev_t *dev, netopt_t opt, void *value, size_t max_len)
+{
+    assert(dev);
+
+#ifdef MODULE_NETOPT
+    DEBUG("[nrf802154] get: %s\n", netopt2str(opt));
+#else
+    DEBUG("[nrf802154] get: %d\n", opt);
+#endif
+
+    switch (opt) {
+        case NETOPT_CHANNEL:
+            assert(max_len >= sizeof(uint16_t));
+            *((uint16_t *)value) = nrf802154_dev.chan;
+            return sizeof(uint16_t);
+        case NETOPT_TX_POWER:
+            assert(max_len >= sizeof(int16_t));
+            *((int16_t *)value) = _get_txpower();
+            return sizeof(int16_t);
+
+        default:
+            return netdev_ieee802154_get((netdev_ieee802154_t *)dev,
+                                          opt, value, max_len);
+    }
+}
+
+static int _set(netdev_t *dev, netopt_t opt,
+                const void *value, size_t value_len)
+{
+    assert(dev);
+
+#ifdef MODULE_NETOPT
+    DEBUG("[nrf802154] set: %s\n", netopt2str(opt));
+#else
+    DEBUG("[nrf802154] set: %d\n", opt);
+#endif
+
+    switch (opt) {
+        case NETOPT_CHANNEL:
+            assert(value_len == sizeof(uint16_t));
+            _set_chan(*((uint16_t *)value));
+            return sizeof(uint16_t);
+        case NETOPT_TX_POWER:
+            assert(value_len == sizeof(int16_t));
+            _set_txpower(*((int16_t *)value));
+            return sizeof(int16_t);
+
+        default:
+            return netdev_ieee802154_set((netdev_ieee802154_t *)dev,
+                                          opt, value, value_len);
+    }
+}
+
+void isr_radio(void)
+{
+    /* Clear flag */
+    if (NRF_RADIO->EVENTS_END) {
+        NRF_RADIO->EVENTS_END = 0;
+
+        /* did we just send or receive something? */
+        uint8_t state = (uint8_t)NRF_RADIO->STATE;
+        switch(state) {
+            case RADIO_STATE_STATE_RxIdle:
+                /* only process packet if event callback is set and CRC is valid */
+                if ((nrf802154_dev.netdev.event_callback) &&
+                    (NRF_RADIO->CRCSTATUS == 1) &&
+                    (netdev_ieee802154_dst_filter(&nrf802154_dev,
+                                                  &rxbuf[1]) == 0)) {
+                    _state |= RX_COMPLETE;
+                }
+                else {
+                    _reset_rx();
+                }
+                break;
+            case RADIO_STATE_STATE_Tx:
+            case RADIO_STATE_STATE_TxIdle:
+            case RADIO_STATE_STATE_TxDisable:
+                timer_start(NRF802154_TIMER);
+                DEBUG("[nrf802154] TX state: %x\n", (uint8_t)NRF_RADIO->STATE);
+                _state |= TX_COMPLETE;
+                _enable_rx();
+                break;
+            default:
+                DEBUG("[nrf802154] Unhandled state: %x\n", (uint8_t)NRF_RADIO->STATE);
+        }
+        if (_state) {
+            nrf802154_dev.netdev.event_callback(&nrf802154_dev.netdev, NETDEV_EVENT_ISR);
+        }
+    }
+    else {
+        DEBUG("[nrf802154] Unknown interrupt triggered\n");
+    }
+
+    cortexm_isr_end();
+}
+
+/**
+ * @brief   Export of the netdev interface
+ */
+static const netdev_driver_t nrf802154_netdev_driver = {
+    .send = _send,
+    .recv = _recv,
+    .init = _init,
+    .isr  = _isr,
+    .get  = _get,
+    .set  = _set
+};
-- 
GitLab