/*
 * Copyright (C) 2019 ML!PA Consulting GmbH
 *
 * 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_at86rf215
 * @{
 *
 * @file
 * @brief       Getter and setter functions for the AT86RF215 driver
 *
 * @author      Benjamin Valentin <benjamin.valentin@ml-pa.com>
 * @}
 */

#include <string.h>

#include "at86rf215.h"
#include "at86rf215_internal.h"
#include "periph/spi.h"

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

/* we can still go +3 dBm higher by increasing PA current */
#define PAC_DBM_MIN                             (-31)     /* dBm */

uint16_t at86rf215_get_addr_short(const at86rf215_t *dev, uint8_t filter)
{
    if (filter > 3) {
        return 0;
    }

    /* Each frame filter has a 2 byte short addr + 2 byte PAN ID, so we have to
       skip 4 bytes per filter */
    return ntohs(at86rf215_reg_read16(dev, dev->BBC->RG_MACSHA0F0 + (4 * filter)));
}

void at86rf215_set_addr_short(at86rf215_t *dev, uint8_t filter, uint16_t addr)
{
    /* do not overwrite node address for filters other than 0 */
    if (filter == 0) {
        dev->netdev.short_addr[0] = (uint8_t)(addr);
        dev->netdev.short_addr[1] = (uint8_t)(addr >> 8);
    }

    if (filter > 3) {
        return;
    }

    at86rf215_reg_write16(dev, dev->BBC->RG_MACSHA0F0 + (4 * filter), htons(addr));
}

bool at86rf215_get_framefilter_enabled(at86rf215_t *dev, uint8_t filter)
{
    return (at86rf215_reg_read(dev, dev->BBC->RG_AFC0) >> (AFC0_AFEN0_SHIFT + filter)) & 1;
}

void at86rf215_enable_framefilter(at86rf215_t *dev, uint8_t filter)
{
    at86rf215_reg_or(dev, dev->BBC->RG_AFC0, 1 << (AFC0_AFEN0_SHIFT + filter));
}

void at86rf215_disable_framefilter(at86rf215_t *dev, uint8_t filter)
{
    at86rf215_reg_and(dev, dev->BBC->RG_AFC0, 1 << (AFC0_AFEN0_SHIFT + filter));
}

uint64_t at86rf215_get_addr_long(const at86rf215_t *dev)
{
    uint64_t addr;
    at86rf215_reg_read_bytes(dev, dev->BBC->RG_MACEA0, &addr, sizeof(addr));
    return addr;
}

void at86rf215_set_addr_long(at86rf215_t *dev, uint64_t addr)
{
    memcpy(dev->netdev.long_addr, &addr, sizeof(addr));
    addr = ntohll(addr);
    at86rf215_reg_write_bytes(dev, dev->BBC->RG_MACEA0, &addr, sizeof(addr));
}

uint8_t at86rf215_get_chan(const at86rf215_t *dev)
{
    return at86rf215_reg_read16(dev, dev->RF->RG_CNL);
}

void at86rf215_set_chan(at86rf215_t *dev, uint16_t channel)
{
    at86rf215_await_state_end(dev, RF_STATE_TX);

    uint8_t old_state = at86rf215_get_rf_state(dev);

    /* frequency has to be updated in TRXOFF or TXPREP (datatsheet: 6.3.2) */
    if (old_state == RF_STATE_RX) {
        at86rf215_rf_cmd(dev, CMD_RF_TXPREP);
    }

    at86rf215_reg_write16(dev, dev->RF->RG_CNL, channel);
    dev->netdev.chan = channel;

    /* enable the radio again */
    if (old_state == RF_STATE_RX) {
        at86rf215_rf_cmd(dev, old_state);
    }
}

uint16_t at86rf215_get_channel_spacing(at86rf215_t *dev) {
    /* 25 kHz resolution */
    return 25 * at86rf215_reg_read(dev, dev->RF->RG_CS);
}

uint8_t at86rf215_get_phy_mode(at86rf215_t *dev)
{
    switch (at86rf215_reg_read(dev, dev->BBC->RG_PC) & PC_PT_MASK) {
    case 0x1: return IEEE802154_PHY_MR_FSK;
    case 0x2: return IEEE802154_PHY_MR_OFDM;
    case 0x3: return at86rf215_OQPSK_is_legacy(dev)
                ? IEEE802154_PHY_OQPSK
                : IEEE802154_PHY_MR_OQPSK;
    default:  return IEEE802154_PHY_DISABLED;
    }
}

uint16_t at86rf215_get_pan(const at86rf215_t *dev, uint8_t filter)
{
    if (filter > 3) {
        return 0;
    }

    return at86rf215_reg_read16(dev, dev->BBC->RG_MACPID0F0 + (4 * filter));
}

void at86rf215_set_pan(at86rf215_t *dev, uint8_t filter, uint16_t pan)
{
    if (filter == 0) {
        dev->netdev.pan = pan;
    }

    if (filter > 3) {
        return;
    }

    at86rf215_reg_write16(dev, dev->BBC->RG_MACPID0F0 + (4 * filter), pan);
}

// TODO: take modulation into account
int16_t at86rf215_get_txpower(const at86rf215_t *dev)
{
    uint8_t pac = at86rf215_reg_read(dev, dev->RF->RG_PAC);

    /* almost linear, each PACUR step adds ~1 dBm */
    return PAC_DBM_MIN + (pac & PAC_TXPWR_MASK) +
           ((pac & PAC_PACUR_MASK) >> PAC_PACUR_SHIFT);
}

// TODO: take modulation into account
void at86rf215_set_txpower(const at86rf215_t *dev, int16_t txpower)
{
    uint8_t pacur = 0;

    txpower -= PAC_DBM_MIN;

    if (txpower < 0) {
            txpower = 0;
    }

    if (txpower > PAC_TXPWR_MASK) {
        switch (txpower - PAC_TXPWR_MASK) {
            case 1:
                pacur = 1 << PAC_PACUR_SHIFT;
                break;
            case 2:
                pacur = 2 << PAC_PACUR_SHIFT;
                break;
            default:
                pacur = 3 << PAC_PACUR_SHIFT;
                break;
        }

        txpower = PAC_TXPWR_MASK;
    }

    at86rf215_reg_write(dev, dev->RF->RG_PAC, pacur | txpower);
}

int8_t at86rf215_get_cca_threshold(const at86rf215_t *dev)
{
    return dev->csma_ed;
}

void at86rf215_set_cca_threshold(at86rf215_t *dev, int8_t value)
{
    dev->csma_ed = value;
    at86rf215_reg_write(dev, dev->BBC->RG_AMEDT, value);
}

int8_t at86rf215_get_ed_level(at86rf215_t *dev)
{
    return at86rf215_reg_read(dev, dev->RF->RG_EDV);
}

void at86rf215_set_option(at86rf215_t *dev, uint16_t option, bool state)
{
    /* set option field */
    dev->flags = (state) ? (dev->flags |  option)
                         : (dev->flags & ~option);

    switch (option) {
        case AT86RF215_OPT_TELL_RX_START:
            if (state) {
                at86rf215_reg_or(dev, dev->BBC->RG_IRQM, BB_IRQ_RXAM);
            } else {
                at86rf215_reg_and(dev, dev->BBC->RG_IRQM, ~BB_IRQ_RXAM);
            }

            break;
        case AT86RF215_OPT_PROMISCUOUS:
            if (state) {
                at86rf215_reg_or(dev, dev->BBC->RG_AFC0, AFC0_PM_MASK);
            } else {
                at86rf215_reg_and(dev, dev->BBC->RG_AFC0, ~AFC0_PM_MASK);
            }

            break;
        case AT86RF215_OPT_AUTOACK:
            if (state) {
                at86rf215_reg_or(dev, dev->BBC->RG_AMCS, AMCS_AACK_MASK);
            } else {
                at86rf215_reg_and(dev, dev->BBC->RG_AMCS, ~AMCS_AACK_MASK);
            }

            break;
        case AT86RF215_OPT_CCATX:
            if (state){
                at86rf215_reg_or(dev, dev->BBC->RG_AMCS, AMCS_CCATX_MASK);
            } else {
                at86rf215_reg_and(dev, dev->BBC->RG_AMCS, ~AMCS_CCATX_MASK);
            }

            break;

        default:
            /* do nothing */
            break;
    }
}

static void _wake_from_sleep(at86rf215_t *dev)
{
    /* wake the transceiver */
    at86rf215_rf_cmd(dev, CMD_RF_TRXOFF);
    at86rf215_await_state(dev, RF_STATE_TRXOFF);

    /* config is lost after SLEEP */
    at86rf215_reset(dev);

    /* if both transceivers were sleeping, the chip entered DEEP_SLEEP.
       Waking one device in that mode wakes the other one too. */
    if (dev->sibling && dev->sibling->state == AT86RF215_STATE_SLEEP) {
        at86rf215_rf_cmd(dev->sibling, CMD_RF_SLEEP);
    }
}

bool at86rf215_set_rx_from_idle(at86rf215_t *dev, uint8_t *state)
{
    if (dev->state == AT86RF215_STATE_SLEEP) {
        _wake_from_sleep(dev);
    }

    uint8_t s;
    while ((s = at86rf215_get_rf_state(dev)) == RF_STATE_TRANSITION) {}

    if (state) {
        *state = s;
    }

    if (s == RF_STATE_RESET) {
        at86rf215_rf_cmd(dev, CMD_RF_TRXOFF);
        at86rf215_await_state(dev, RF_STATE_TRXOFF);
        s = RF_STATE_TRXOFF;
    }

    if (s == RF_STATE_TRXOFF) {
        at86rf215_rf_cmd(dev, CMD_RF_RX);
        at86rf215_await_state(dev, RF_STATE_RX);
        s = RF_STATE_RX;
    }

    if (s == RF_STATE_RX) {
        return true;
    }

    return false;
}

bool at86rf215_set_idle_from_rx(at86rf215_t *dev, uint8_t state)
{
    if (state == RF_STATE_TX || state == CMD_RF_TXPREP) {
        return false;
    }

    if (dev->state == AT86RF215_STATE_SLEEP) {
        if (state == CMD_RF_SLEEP) {
            return true;
        }

        _wake_from_sleep(dev);
    }

    uint8_t s;
    while ((s = at86rf215_get_rf_state(dev)) == RF_STATE_TRANSITION) {}

    if (s != RF_STATE_RX) {
        return false;
    }

    if (state == RF_STATE_RX) {
        return true;
    }

    at86rf215_rf_cmd(dev, CMD_RF_TRXOFF);
    at86rf215_await_state(dev, RF_STATE_TRXOFF);

    if (state == RF_STATE_TRXOFF) {
        return true;
    }

    /* clear IRQ */
    at86rf215_reg_read(dev, dev->BBC->RG_IRQS);
    at86rf215_reg_read(dev, dev->RF->RG_IRQS);
    at86rf215_rf_cmd(dev, CMD_RF_SLEEP);

    dev->state = AT86RF215_STATE_SLEEP;

    if (state == RF_STATE_RESET) {
        return true;
    }

    return false;
}
