/*
 * Copyright (C) 2016 Fundación Inria Chile
 *
 * 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_sx127x
 * @{
 * @file
 * @brief       Netdev adaptation for the sx127x driver
 *
 * @author      Eugene P. <ep@unwds.com>
 * @author      José Ignacio Alamos <jose.alamos@inria.cl>
 * @}
 */

#include <stddef.h>
#include <string.h>
#include <errno.h>

#include "ztimer.h"

#include "net/netopt.h"
#include "net/netdev.h"
#include "net/netdev/lora.h"
#include "net/lora.h"

#include "sx127x_registers.h"
#include "sx127x_internal.h"
#include "sx127x_netdev.h"
#include "sx127x.h"

#define ENABLE_DEBUG (0)
#include "debug.h"

/* Internal helper functions */
static int _set_state(sx127x_t *dev, netopt_state_t state);
static int _get_state(sx127x_t *dev, void *val);
void _on_dio0_irq(void *arg);
void _on_dio1_irq(void *arg);
void _on_dio2_irq(void *arg);
void _on_dio3_irq(void *arg);

static int _send(netdev_t *netdev, const iolist_t *iolist)
{
    sx127x_t *dev = (sx127x_t*) netdev;

    if (sx127x_get_state(dev) == SX127X_RF_TX_RUNNING) {
        DEBUG("[sx127x] Cannot send packet: radio already in transmitting "
              "state.\n");
        return -ENOTSUP;
    }

    uint8_t size = iolist_size(iolist);

    /* Ignore send if packet size is 0 */
    if (size == 0) {
        return 0;
    }

    switch (dev->settings.modem) {
        case SX127X_MODEM_FSK:
            /* todo */
            break;
        case SX127X_MODEM_LORA:
            /* Initializes the payload size */
            if (!sx127x_get_fixed_header_len_mode(dev)) {
                sx127x_set_payload_length(dev, size);
            }

            /* Full buffer used for Tx */
            sx127x_reg_write(dev, SX127X_REG_LR_FIFOTXBASEADDR, 0x00);
            sx127x_reg_write(dev, SX127X_REG_LR_FIFOADDRPTR, 0x00);

            /* FIFO operations can not take place in Sleep mode
             * So wake up the chip */
            if (sx127x_get_op_mode(dev) == SX127X_RF_OPMODE_SLEEP) {
                sx127x_set_standby(dev);
                ztimer_sleep(ZTIMER_MSEC, SX127X_RADIO_WAKEUP_TIME); /* wait for chip wake up */
            }

            /* Write payload buffer */
            for (const iolist_t *iol = iolist; iol; iol = iol->iol_next) {
                if(iol->iol_len > 0) {
                    sx127x_write_fifo(dev, iol->iol_base, iol->iol_len);
                }
            }
            break;
        default:
            DEBUG("[sx127x] netdev: Unsupported modem (%d)\n",
                  dev->settings.modem);
            break;
    }

    sx127x_set_tx(dev);

    return 0;
}

static int _recv(netdev_t *netdev, void *buf, size_t len, void *info)
{
    sx127x_t *dev = (sx127x_t*) netdev;
    volatile uint8_t irq_flags = 0;
    uint8_t size = 0;
    switch (dev->settings.modem) {
        case SX127X_MODEM_FSK:
            /* todo */
            break;
        case SX127X_MODEM_LORA:
            /* Clear IRQ */
            sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS, SX127X_RF_LORA_IRQFLAGS_RXDONE);

            irq_flags = sx127x_reg_read(dev, SX127X_REG_LR_IRQFLAGS);
            if ( (irq_flags & SX127X_RF_LORA_IRQFLAGS_PAYLOADCRCERROR_MASK) ==
                 SX127X_RF_LORA_IRQFLAGS_PAYLOADCRCERROR) {
                /* Clear IRQ */
                sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS,
                                 SX127X_RF_LORA_IRQFLAGS_PAYLOADCRCERROR);

                if (!(dev->settings.lora.flags & SX127X_RX_CONTINUOUS_FLAG)) {
                    sx127x_set_state(dev, SX127X_RF_IDLE);
                }

                ztimer_remove(ZTIMER_MSEC, &dev->_internal.rx_timeout_timer);
                netdev->event_callback(netdev, NETDEV_EVENT_CRC_ERROR);
                return -EBADMSG;
            }

            netdev_lora_rx_info_t *packet_info = info;
            if (packet_info) {
                uint8_t snr_value = sx127x_reg_read(dev, SX127X_REG_LR_PKTSNRVALUE);
                if (snr_value & 0x80) { /* The SNR is negative */
                    /* Invert and divide by 4 */
                    packet_info->snr = -1 * ((~snr_value + 1) & 0xFF) >> 2;
                }
                else {
                    /* Divide by 4 */
                    packet_info->snr = (snr_value & 0xFF) >> 2;
                }

                int16_t rssi = sx127x_reg_read(dev, SX127X_REG_LR_PKTRSSIVALUE);

                if (packet_info->snr < 0) {
#if defined(MODULE_SX1272)
                    packet_info->rssi = SX127X_RSSI_OFFSET + rssi + (rssi >> 4) + packet_info->snr;
#else /* MODULE_SX1276 */
                    if (dev->settings.channel > SX127X_RF_MID_BAND_THRESH) {
                        packet_info->rssi = SX127X_RSSI_OFFSET_HF + rssi + (rssi >> 4) + packet_info->snr;
                    }
                    else {
                        packet_info->rssi = SX127X_RSSI_OFFSET_LF + rssi + (rssi >> 4) + packet_info->snr;
                    }
#endif
                }
                else {
#if defined(MODULE_SX1272)
                    packet_info->rssi = SX127X_RSSI_OFFSET + rssi + (rssi >> 4);
#else /* MODULE_SX1276 */
                    if (dev->settings.channel > SX127X_RF_MID_BAND_THRESH) {
                        packet_info->rssi = SX127X_RSSI_OFFSET_HF + rssi + (rssi >> 4);
                    }
                    else {
                        packet_info->rssi = SX127X_RSSI_OFFSET_LF + rssi + (rssi >> 4);
                    }
#endif
                }
            }

            size = sx127x_reg_read(dev, SX127X_REG_LR_RXNBBYTES);
            if (buf == NULL) {
                return size;
            }

            if (size > len) {
                return -ENOBUFS;
            }

            if (!(dev->settings.lora.flags & SX127X_RX_CONTINUOUS_FLAG)) {
                sx127x_set_state(dev, SX127X_RF_IDLE);
            }

            ztimer_remove(ZTIMER_MSEC, &dev->_internal.rx_timeout_timer);
            /* Read the last packet from FIFO */
            uint8_t last_rx_addr = sx127x_reg_read(dev, SX127X_REG_LR_FIFORXCURRENTADDR);
            sx127x_reg_write(dev, SX127X_REG_LR_FIFOADDRPTR, last_rx_addr);
            sx127x_read_fifo(dev, (uint8_t*)buf, size);
            break;
        default:
            break;
    }

    return size;
}

static int _init(netdev_t *netdev)
{
    sx127x_t *sx127x = (sx127x_t*) netdev;

    sx127x->irq = 0;
    sx127x_radio_settings_t settings;
    settings.channel = SX127X_CHANNEL_DEFAULT;
    settings.modem = SX127X_MODEM_DEFAULT;
    settings.state = SX127X_RF_IDLE;

    sx127x->settings = settings;

    /* Launch initialization of driver and device */
    DEBUG("[sx127x] netdev: initializing driver...\n");
    if (sx127x_init(sx127x) != SX127X_INIT_OK) {
        DEBUG("[sx127x] netdev: initialization failed\n");
        return -1;
    }

    sx127x_init_radio_settings(sx127x);
    /* Put chip into sleep */
    sx127x_set_sleep(sx127x);

    DEBUG("[sx127x] netdev: initialization done\n");

    return 0;
}

static void _isr(netdev_t *netdev)
{
    sx127x_t *dev = (sx127x_t *)netdev;

    uint8_t interruptReg = sx127x_reg_read(dev, SX127X_REG_LR_IRQFLAGS);

    if (interruptReg & (SX127X_RF_LORA_IRQFLAGS_TXDONE |
                        SX127X_RF_LORA_IRQFLAGS_RXDONE)) {
        _on_dio0_irq(dev);
    }

    if (interruptReg & SX127X_RF_LORA_IRQFLAGS_RXTIMEOUT) {
        _on_dio1_irq(dev);
    }

    if (interruptReg & SX127X_RF_LORA_IRQFLAGS_FHSSCHANGEDCHANNEL) {
        _on_dio2_irq(dev);
    }

    if (interruptReg & (SX127X_RF_LORA_IRQFLAGS_CADDETECTED |
                        SX127X_RF_LORA_IRQFLAGS_CADDONE |
                        SX127X_RF_LORA_IRQFLAGS_VALIDHEADER)) {
        _on_dio3_irq(dev);
    }
}

static int _get(netdev_t *netdev, netopt_t opt, void *val, size_t max_len)
{
    (void) max_len;  /* unused when compiled without debug, assert empty */
    sx127x_t *dev = (sx127x_t*) netdev;

    if (dev == NULL) {
        return -ENODEV;
    }

    switch(opt) {
        case NETOPT_STATE:
            assert(max_len >= sizeof(netopt_state_t));
            return _get_state(dev, val);

        case NETOPT_DEVICE_TYPE:
            assert(max_len >= sizeof(uint16_t));
            *((uint16_t*) val) = NETDEV_TYPE_LORA;
            return sizeof(uint16_t);

        case NETOPT_CHANNEL_FREQUENCY:
            assert(max_len >= sizeof(uint32_t));
            *((uint32_t*) val) = sx127x_get_channel(dev);
            return sizeof(uint32_t);

        case NETOPT_BANDWIDTH:
            assert(max_len >= sizeof(uint8_t));
            *((uint8_t*) val) = sx127x_get_bandwidth(dev);
            return sizeof(uint8_t);

        case NETOPT_SPREADING_FACTOR:
            assert(max_len >= sizeof(uint8_t));
            *((uint8_t*) val) = sx127x_get_spreading_factor(dev);
            return sizeof(uint8_t);

        case NETOPT_CODING_RATE:
            assert(max_len >= sizeof(uint8_t));
            *((uint8_t*) val) = sx127x_get_coding_rate(dev);
            return sizeof(uint8_t);

        case NETOPT_MAX_PDU_SIZE:
            assert(max_len >= sizeof(uint8_t));
            *((uint8_t*) val) = sx127x_get_max_payload_len(dev);
            return sizeof(uint8_t);

        case NETOPT_INTEGRITY_CHECK:
            assert(max_len >= sizeof(netopt_enable_t));
            *((netopt_enable_t*) val) = sx127x_get_crc(dev) ? NETOPT_ENABLE : NETOPT_DISABLE;
            return sizeof(netopt_enable_t);

        case NETOPT_CHANNEL_HOP:
            assert(max_len >= sizeof(netopt_enable_t));
            *((netopt_enable_t*) val) = (dev->settings.lora.flags & SX127X_CHANNEL_HOPPING_FLAG) ? NETOPT_ENABLE : NETOPT_DISABLE;
            return sizeof(netopt_enable_t);

        case NETOPT_CHANNEL_HOP_PERIOD:
            assert(max_len >= sizeof(uint8_t));
            *((uint8_t*) val) = sx127x_get_hop_period(dev);
            return sizeof(uint8_t);

        case NETOPT_SINGLE_RECEIVE:
            assert(max_len >= sizeof(uint8_t));
            *((netopt_enable_t*) val) = sx127x_get_rx_single(dev) ? NETOPT_ENABLE : NETOPT_DISABLE;
            return sizeof(netopt_enable_t);

        case NETOPT_TX_POWER:
            assert(max_len >= sizeof(int16_t));
            *((int16_t*) val) = (int16_t)sx127x_get_tx_power(dev);
            return sizeof(int16_t);

        case NETOPT_SYNCWORD:
            assert(max_len >= sizeof(uint8_t));
            *((uint8_t*) val) = (uint8_t) sx127x_get_syncword(dev);
            return sizeof(uint8_t);

        case NETOPT_RANDOM:
            assert(max_len >= sizeof(uint32_t));
            *((uint32_t*) val) = (uint32_t) sx127x_random(dev);
            return sizeof(uint32_t);

        case NETOPT_IQ_INVERT:
            assert(max_len >= sizeof(uint8_t));
            *((netopt_enable_t*) val) = sx127x_get_iq_invert(dev) ? NETOPT_ENABLE : NETOPT_DISABLE;
            return sizeof(netopt_enable_t);

        default:
            break;
    }

    return -ENOTSUP;
}

static int _set(netdev_t *netdev, netopt_t opt, const void *val, size_t len)
{
    (void)len; /* unused when compiled without debug, assert empty */

    sx127x_t *dev = (sx127x_t*) netdev;
    int res = -ENOTSUP;

    if (dev == NULL) {
        return -ENODEV;
    }

    switch(opt) {
        case NETOPT_STATE:
            assert(len == sizeof(netopt_state_t));
            return _set_state(dev, *((const netopt_state_t*) val));

        case NETOPT_DEVICE_TYPE:
            assert(len <= sizeof(uint16_t));
            /* Only LoRa modem is supported for the moment */
            if (*(const uint16_t*) val == NETDEV_TYPE_LORA) {
                sx127x_set_modem(dev, SX127X_MODEM_LORA);
                return sizeof(uint16_t);
            }
            else {
                return -EINVAL;
            }

        case NETOPT_CHANNEL_FREQUENCY:
            assert(len <= sizeof(uint32_t));
            sx127x_set_channel(dev, *((const uint32_t*) val));
            return sizeof(uint32_t);

        case NETOPT_BANDWIDTH:
            assert(len <= sizeof(uint8_t));
            uint8_t bw = *((const uint8_t *)val);
            if (bw > LORA_BW_500_KHZ) {
                res = -EINVAL;
                break;
            }
            sx127x_set_bandwidth(dev, bw);
            return sizeof(uint8_t);

        case NETOPT_SPREADING_FACTOR:
            assert(len <= sizeof(uint8_t));
            uint8_t sf = *((const uint8_t *)val);
            if ((sf < LORA_SF6) || (sf > LORA_SF12)) {
                res = -EINVAL;
                break;
            }
            sx127x_set_spreading_factor(dev, sf);
            return sizeof(uint8_t);

        case NETOPT_CODING_RATE:
            assert(len <= sizeof(uint8_t));
            uint8_t cr = *((const uint8_t *)val);
            if ((cr < LORA_CR_4_5) || (cr > LORA_CR_4_8)) {
                res = -EINVAL;
                break;
            }
            sx127x_set_coding_rate(dev, cr);
            return sizeof(uint8_t);

        case NETOPT_MAX_PDU_SIZE:
            assert(len <= sizeof(uint8_t));
            sx127x_set_max_payload_len(dev, *((const uint8_t*) val));
            return sizeof(uint8_t);

        case NETOPT_INTEGRITY_CHECK:
            assert(len <= sizeof(netopt_enable_t));
            sx127x_set_crc(dev, *((const netopt_enable_t*) val) ? true : false);
            return sizeof(netopt_enable_t);

        case NETOPT_CHANNEL_HOP:
            assert(len <= sizeof(netopt_enable_t));
            sx127x_set_freq_hop(dev, *((const netopt_enable_t*) val) ? true : false);
            return sizeof(netopt_enable_t);

        case NETOPT_CHANNEL_HOP_PERIOD:
            assert(len <= sizeof(uint8_t));
            sx127x_set_hop_period(dev, *((const uint8_t*) val));
            return sizeof(uint8_t);

        case NETOPT_SINGLE_RECEIVE:
            assert(len <= sizeof(netopt_enable_t));
            sx127x_set_rx_single(dev, *((const netopt_enable_t*) val) ? true : false);
            return sizeof(netopt_enable_t);

        case NETOPT_RX_SYMBOL_TIMEOUT:
            assert(len <= sizeof(uint16_t));
            sx127x_set_symbol_timeout(dev, *((const uint16_t*) val));
            return sizeof(uint16_t);

        case NETOPT_RX_TIMEOUT:
            assert(len <= sizeof(uint32_t));
            sx127x_set_rx_timeout(dev, *((const uint32_t*) val));
            return sizeof(uint32_t);

        case NETOPT_TX_TIMEOUT:
            assert(len <= sizeof(uint32_t));
            sx127x_set_tx_timeout(dev, *((const uint32_t*) val));
            return sizeof(uint32_t);

        case NETOPT_TX_POWER:
            assert(len <= sizeof(int16_t));
            int16_t power = *((const int16_t *)val);
            if ((power < INT8_MIN) || (power > INT8_MAX)) {
                res = -EINVAL;
                break;
            }
            sx127x_set_tx_power(dev, (int8_t)power);
            return sizeof(int16_t);

        case NETOPT_FIXED_HEADER:
            assert(len <= sizeof(netopt_enable_t));
            sx127x_set_fixed_header_len_mode(dev, *((const netopt_enable_t*) val) ? true : false);
            return sizeof(netopt_enable_t);

        case NETOPT_PDU_SIZE:
            assert(len <= sizeof(uint16_t));
            sx127x_set_payload_length(dev, *((const uint16_t*) val));
            return sizeof(uint16_t);

        case NETOPT_PREAMBLE_LENGTH:
            assert(len <= sizeof(uint16_t));
            sx127x_set_preamble_length(dev, *((const uint16_t*) val));
            return sizeof(uint16_t);

        case NETOPT_SYNCWORD:
            assert(len <= sizeof(uint8_t));
            sx127x_set_syncword(dev, *((uint8_t*) val));
            return sizeof(uint8_t);

        case NETOPT_IQ_INVERT:
            assert(len <= sizeof(netopt_enable_t));
            sx127x_set_iq_invert(dev, *((const netopt_enable_t*) val) ? true : false);
            return sizeof(bool);

        default:
            break;
    }

    return res;
}

static int _set_state(sx127x_t *dev, netopt_state_t state)
{
    switch (state) {
        case NETOPT_STATE_SLEEP:
            sx127x_set_sleep(dev);
            break;

        case NETOPT_STATE_STANDBY:
            sx127x_set_standby(dev);
            break;

        case NETOPT_STATE_IDLE:
            /* set permanent listening */
            sx127x_set_rx_timeout(dev, 0);
            sx127x_set_rx(dev);
            break;

        case NETOPT_STATE_RX:
            sx127x_set_rx(dev);
            break;

        case NETOPT_STATE_TX:
            sx127x_set_tx(dev);
            break;

        case NETOPT_STATE_RESET:
            sx127x_reset(dev);
            break;

        default:
            return -ENOTSUP;
    }
    return sizeof(netopt_state_t);
}

static int _get_state(sx127x_t *dev, void *val)
{
    uint8_t op_mode;
    op_mode = sx127x_get_op_mode(dev);
    netopt_state_t state = NETOPT_STATE_OFF;
    switch(op_mode) {
        case SX127X_RF_OPMODE_SLEEP:
            state = NETOPT_STATE_SLEEP;
            break;

        case SX127X_RF_OPMODE_STANDBY:
            state = NETOPT_STATE_STANDBY;
            break;

        case SX127X_RF_OPMODE_TRANSMITTER:
            state = NETOPT_STATE_TX;
            break;

        case SX127X_RF_OPMODE_RECEIVER:
        case SX127X_RF_LORA_OPMODE_RECEIVER_SINGLE:
            /* Sx127x is in receive mode:
             * -> need to check if the device is currently receiving a packet */
            if (sx127x_reg_read(dev, SX127X_REG_LR_MODEMSTAT) &
                SX127X_RF_LORA_MODEMSTAT_MODEM_STATUS_SIGNAL_DETECTED) {
                state = NETOPT_STATE_RX;
            }
            else {
                state = NETOPT_STATE_IDLE;
            }
            break;

        default:
            break;
    }
    memcpy(val, &state, sizeof(netopt_state_t));
    return sizeof(netopt_state_t);
}

void _on_dio0_irq(void *arg)
{
    sx127x_t *dev = (sx127x_t *) arg;
    netdev_t *netdev = (netdev_t*) &dev->netdev;

    switch (dev->settings.state) {
        case SX127X_RF_RX_RUNNING:
            netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
            break;
        case SX127X_RF_TX_RUNNING:
            ztimer_remove(ZTIMER_MSEC, &dev->_internal.tx_timeout_timer);
            switch (dev->settings.modem) {
                case SX127X_MODEM_LORA:
                    /* Clear IRQ */
                    sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS,
                                     SX127X_RF_LORA_IRQFLAGS_TXDONE);
                /* Intentional fall-through */
                case SX127X_MODEM_FSK:
                default:
                    sx127x_set_state(dev, SX127X_RF_IDLE);
                    netdev->event_callback(netdev, NETDEV_EVENT_TX_COMPLETE);
                    break;
            }
            break;
        case SX127X_RF_IDLE:
            DEBUG("[sx127x] netdev: sx127x_on_dio0: IDLE state\n");
            break;
        default:
            DEBUG("[sx127x] netdev: sx127x_on_dio0: unknown state [%d]\n",
                  dev->settings.state);
            break;
    }
}

void _on_dio1_irq(void *arg)
{
    /* Get interrupt context */
    sx127x_t *dev = (sx127x_t *) arg;
    netdev_t *netdev = (netdev_t*) &dev->netdev;

    switch (dev->settings.state) {
        case SX127X_RF_RX_RUNNING:
            switch (dev->settings.modem) {
                case SX127X_MODEM_FSK:
                    /* todo */
                    break;
                case SX127X_MODEM_LORA:
                    ztimer_remove(ZTIMER_MSEC, &dev->_internal.rx_timeout_timer);
                    /*  Clear Irq */
                    sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS, SX127X_RF_LORA_IRQFLAGS_RXTIMEOUT);
                    sx127x_set_state(dev, SX127X_RF_IDLE);
                    netdev->event_callback(netdev, NETDEV_EVENT_RX_TIMEOUT);
                    break;
                default:
                    break;
            }
            break;
        case SX127X_RF_TX_RUNNING:
            switch (dev->settings.modem) {
                case SX127X_MODEM_FSK:
                    /* todo */
                    break;
                case SX127X_MODEM_LORA:
                    break;
                default:
                    break;
            }
            break;
        default:
            puts("[sx127x] netdev: sx127x_on_dio1: unknown state");
            break;
    }
}

void _on_dio2_irq(void *arg)
{
    /* Get interrupt context */
    sx127x_t *dev = (sx127x_t *) arg;
    netdev_t *netdev = (netdev_t*) dev;

    switch (dev->settings.state) {
        case SX127X_RF_RX_RUNNING:
            switch (dev->settings.modem) {
                case SX127X_MODEM_FSK:
                    /* todo */
                    break;
                case SX127X_MODEM_LORA:
                    if (dev->settings.lora.flags & SX127X_CHANNEL_HOPPING_FLAG) {
                        /* Clear IRQ */
                        sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS,
                                         SX127X_RF_LORA_IRQFLAGS_FHSSCHANGEDCHANNEL);

                        dev->_internal.last_channel = (sx127x_reg_read(dev, SX127X_REG_LR_HOPCHANNEL) &
                                                       SX127X_RF_LORA_HOPCHANNEL_CHANNEL_MASK);
                        netdev->event_callback(netdev, NETDEV_EVENT_FHSS_CHANGE_CHANNEL);
                    }

                    break;
                default:
                    break;
            }
            break;
        case SX127X_RF_TX_RUNNING:
            switch (dev->settings.modem) {
                case SX127X_MODEM_FSK:
                    break;
                case SX127X_MODEM_LORA:
                    if (dev->settings.lora.flags & SX127X_CHANNEL_HOPPING_FLAG) {
                        /* Clear IRQ */
                        sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS,
                                         SX127X_RF_LORA_IRQFLAGS_FHSSCHANGEDCHANNEL);

                        dev->_internal.last_channel = (sx127x_reg_read(dev, SX127X_REG_LR_HOPCHANNEL) &
                                                       SX127X_RF_LORA_HOPCHANNEL_CHANNEL_MASK);
                        netdev->event_callback(netdev, NETDEV_EVENT_FHSS_CHANGE_CHANNEL);
                    }
                    break;
                default:
                    break;
            }
            break;
        default:
            puts("[sx127x] netdev: sx127x_on_dio2: unknown state");
            break;
    }
}

void _on_dio3_irq(void *arg)
{
    /* Get interrupt context */
    sx127x_t *dev = (sx127x_t *) arg;
    netdev_t *netdev = (netdev_t *) dev;

    switch (dev->settings.state) {
        case SX127X_RF_CAD:
            switch (dev->settings.modem) {
                case SX127X_MODEM_FSK:
                    break;
                case SX127X_MODEM_LORA:
                    /* Clear IRQ */
                    sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS,
                                     SX127X_RF_LORA_IRQFLAGS_CADDETECTED |
                                     SX127X_RF_LORA_IRQFLAGS_CADDONE);

                    /* Send event message */
                    dev->_internal.is_last_cad_success = ((sx127x_reg_read(dev, SX127X_REG_LR_IRQFLAGS) &
                                                           SX127X_RF_LORA_IRQFLAGS_CADDETECTED) ==
                                                          SX127X_RF_LORA_IRQFLAGS_CADDETECTED);
                    netdev->event_callback(netdev, NETDEV_EVENT_CAD_DONE);
                    break;
                default:
                    puts("[sx127x] netdev: sx127x_on_dio3: unknown modem");
                    break;
            }
            break;
        case SX127X_RF_RX_RUNNING:
            switch (dev->settings.modem) {
                case SX127X_MODEM_FSK:
                    break;
                case SX127X_MODEM_LORA:
                    /* Clear IRQ */
                    sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS, SX127X_RF_LORA_IRQFLAGS_VALIDHEADER);

                    netdev->event_callback(netdev, NETDEV_EVENT_RX_STARTED);
                    break;
                default:
                    break;
            }
            break;
        default:
            DEBUG("[sx127x] netdev: sx127x_on_dio3: unknown state");
            /* at least the related interrupts should be cleared in this case */
            sx127x_reg_write(dev, SX127X_REG_LR_IRQFLAGS,
                             SX127X_RF_LORA_IRQFLAGS_VALIDHEADER |
                             SX127X_RF_LORA_IRQFLAGS_CADDETECTED |
                             SX127X_RF_LORA_IRQFLAGS_CADDONE);
            break;
    }
}

const netdev_driver_t sx127x_driver = {
    .send = _send,
    .recv = _recv,
    .init = _init,
    .isr = _isr,
    .get = _get,
    .set = _set,
};
