/*
 * Copyright (C) 2017 SKF AB
 *
 * 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_kw41zrf
 * @{
 * @file
 * @brief       Internal function of kw41zrf driver
 *
 * @author      Joakim NohlgÄrd <joakim.nohlgard@eistec.se>
 * @}
 */

#include <assert.h>
#include "log.h"
#include "irq.h"
#include "panic.h"
#include "kw41zrf.h"
#include "kw41zrf_getset.h"
#include "kw41zrf_intern.h"

#define ENABLE_DEBUG 0
#include "debug.h"

/**
 * @brief Delay before entering deep sleep mode, in DSM_TIMER ticks (32.768 kHz)
 *
 * @attention must be >= 4 according to SoC ref. manual
 */
#define KW41ZRF_DSM_ENTER_DELAY 5

/**
 * @brief Delay before leaving deep sleep mode, in DSM_TIMER ticks (32.768 kHz)
 *
 * @attention must be >= 4 according to SoC ref. manual
 */
#define KW41ZRF_DSM_EXIT_DELAY 5

struct {
    void (*cb)(void *arg); /**< Callback function called from radio ISR */
    void *arg;             /**< Argument to callback */
} isr_config;

void kw41zrf_set_irq_callback(void (*cb)(void *arg), void *arg)
{
    unsigned int mask = irq_disable();
    isr_config.cb = cb;
    isr_config.arg = arg;
    irq_restore(mask);
}

void kw41zrf_set_power_mode(kw41zrf_t *dev, kw41zrf_powermode_t pm)
{
    DEBUG("[kw41zrf] set power mode to %u\n", pm);
    unsigned state = irq_disable();
    switch (pm) {
        case KW41ZRF_POWER_IDLE:
        {
            /* Disable some CPU power management if we need to be active, otherwise the
             * radio will be stuck in state retention mode. */
            if (!dev->pm_blocked) {
                PM_BLOCK(KW41ZRF_PM_BLOCKER);
                dev->pm_blocked = 1;
            }
            /* Restore saved RF oscillator settings, enable oscillator in RUN mode
             * to allow register access */
            /* This is also where the oscillator is enabled during kw41zrf_init:
             * kw41zrf_init -> kw41zrf_reset_phy -> kw41zrf_set_power_mode
             * => Do not return before this line during init */
            RSIM->CONTROL |= RSIM_CONTROL_RF_OSC_EN(1);
            /* Assume DSM timer has been running since we entered sleep mode */
            /* In case it was not already running, however, we still set the
             * enable flag here. */
            /* RSIM_DSM_CONTROL_ZIG_SYSCLK_REQUEST_EN lets the link layer
             * request the RF oscillator to remain on during STOP and VLPS, to
             * allow stopping the CPU core without affecting TX or RX operations */
            RSIM->DSM_CONTROL = (RSIM_DSM_CONTROL_DSM_TIMER_EN_MASK |
                                RSIM_DSM_CONTROL_ZIG_SYSCLK_REQUEST_EN_MASK);
            /* Wait for oscillator ready signal before attempting to recover from DSM */
            while ((RSIM->CONTROL & RSIM_CONTROL_RF_OSC_READY_MASK) == 0) {}
            KW41ZRF_LED_NDSM_ON;
            /* If we are already awake we can just return now. */
            if (!(kw41zrf_is_dsm())) {
                /* Already awake */
                break;
            }
            /* The wake target must be at least (4 + RSIM_DSM_OSC_OFFSET) ticks
             * into the future, to let the oscillator stabilize before switching
             * on the clocks */
            RSIM->ZIG_WAKE = KW41ZRF_DSM_EXIT_DELAY + RSIM->DSM_TIMER + RSIM->DSM_OSC_OFFSET;
            /* Wait to come out of DSM */
            while (kw41zrf_is_dsm()) {}

            /* Convert DSM ticks (32.768 kHz) to event timer ticks (1 MHz) */
            uint64_t tmp = (uint64_t)(RSIM->ZIG_WAKE - RSIM->ZIG_SLEEP) * 15625ul;
            uint32_t usec = (tmp >> 9); /* equivalent to (usec / 512) */
            /* Add the offset */
            ZLL->EVENT_TMR = ZLL_EVENT_TMR_EVENT_TMR_ADD_MASK |
                ZLL_EVENT_TMR_EVENT_TMR(usec);

            /* Clear IRQ flags */
            uint32_t irqsts = ZLL->IRQSTS;
            DEBUG("[kw41zrf] wake IRQSTS=%" PRIx32 "\n", irqsts);
            ZLL->IRQSTS = irqsts;

            /* Disable DSM timer triggered sleep */
            ZLL->DSM_CTRL = 0;

            break;
        }
        case KW41ZRF_POWER_DSM:
        {
            if (kw41zrf_is_dsm()) {
                /* Already asleep */
                break;
            }
            if (dev->pm_blocked) {
                PM_UNBLOCK(KW41ZRF_PM_BLOCKER);
                dev->pm_blocked = 0;
            }
            /* Race condition: if sleep is re-triggered after wake before the
             * DSM_ZIG_FINISHED flag has been switched off, then the RSIM
             * becomes stuck and never enters DSM.
             * The time from ZIG_WAKE until DSM_ZIG_FINISHED is turned off seem
             * to be constant at 2 DSM ticks */
            while (RSIM->DSM_CONTROL & RSIM_DSM_CONTROL_DSM_ZIG_FINISHED_MASK) {}
            /* Clear IRQ flags */
            uint32_t irqsts = RSIM->DSM_CONTROL;
            RSIM->DSM_CONTROL = irqsts;
            irqsts = ZLL->IRQSTS;
            DEBUG("[kw41zrf] sleep IRQSTS=%" PRIx32 "\n", irqsts);
            ZLL->IRQSTS = irqsts;
            NVIC_ClearPendingIRQ(Radio_1_IRQn);

            /* Enable timer triggered sleep */
            ZLL->DSM_CTRL = ZLL_DSM_CTRL_ZIGBEE_SLEEP_EN_MASK;
            /* The device will automatically wake up 8.5 minutes from now if not
             * awoken sooner by software */
            /* TODO handle automatic wake in the ISR if it becomes an issue */
            RSIM->ZIG_WAKE = RSIM->DSM_TIMER - KW41ZRF_DSM_EXIT_DELAY - RSIM->DSM_OSC_OFFSET;
            /* Set sleep start time */
            /* The target time must be at least 4 DSM_TIMER ticks into the future */
            RSIM->ZIG_SLEEP = RSIM->DSM_TIMER + KW41ZRF_DSM_ENTER_DELAY;
            /* Start the 32.768 kHz DSM timer in case it was not already running */
            /* If ZIG_SYSCLK_REQUEST_EN is not set then the hardware will not
             * enter DSM and we get stuck in the while() below */
            RSIM->DSM_CONTROL = (RSIM_DSM_CONTROL_DSM_TIMER_EN_MASK |
                                RSIM_DSM_CONTROL_ZIG_SYSCLK_REQUEST_EN_MASK);
            while (!(kw41zrf_is_dsm())) {}
            KW41ZRF_LED_NDSM_OFF;
            /* Restore saved RF_OSC_EN bits (from kw41zrf_init)
             * This will disable the RF oscillator unless the system was
             * configured to use the RF oscillator before kw41zrf_init() was
             * called, for example when using the RF oscillator for the CPU core
             * clock. */
            RSIM->CONTROL = (RSIM->CONTROL & ~RSIM_CONTROL_RF_OSC_EN_MASK) |
                dev->rf_osc_en_idle;
            /* Let the DSM timer run until we exit deep sleep mode */
            break;
        }
        default:
            LOG_ERROR("[kw41zrf] Unknown power mode %u\n", pm);
            break;
    }
    irq_restore(state);
}

void kw41zrf_set_sequence(kw41zrf_t *dev, uint32_t seq)
{
    (void) dev;
    DEBUG("[kw41zrf] set sequence to %x\n", (unsigned)seq);
    assert(!kw41zrf_is_dsm());
    unsigned back_to_sleep = 0;
    if (seq == XCVSEQ_DSM_IDLE) {
        back_to_sleep = 1;
        seq = XCVSEQ_IDLE;
    }
    else if ((seq == XCVSEQ_RECEIVE) && dev->recv_blocked) {
        /* Wait in standby until recv has been called to avoid corrupting the RX
         * buffer before the frame has been received by the higher layers */
        seq = XCVSEQ_IDLE;
    }
    uint32_t seq_old = ZLL->PHY_CTRL & ZLL_PHY_CTRL_XCVSEQ_MASK;
    if (seq_old != XCVSEQ_IDLE && seq_old != XCVSEQ_RECEIVE) {
        LOG_ERROR("[kw41zrf] seq not idle: 0x%" PRIu32 "\n", seq_old);
        assert(0);
    }

    kw41zrf_abort_sequence(dev);

    ZLL->PHY_CTRL = (ZLL->PHY_CTRL & ~(ZLL_PHY_CTRL_XCVSEQ_MASK | ZLL_PHY_CTRL_SEQMSK_MASK)) | seq;
    while (((ZLL->SEQ_CTRL_STS & ZLL_SEQ_CTRL_STS_XCVSEQ_ACTUAL_MASK) >>
        ZLL_SEQ_CTRL_STS_XCVSEQ_ACTUAL_SHIFT) != (ZLL_PHY_CTRL_XCVSEQ_MASK & seq)) {}
    if (back_to_sleep) {
        kw41zrf_set_power_mode(dev, KW41ZRF_POWER_DSM);
    }
}

int kw41zrf_can_switch_to_idle(kw41zrf_t *dev)
{
    (void) dev;
    if (!kw41zrf_is_dsm()) {
        uint8_t seq = (ZLL->PHY_CTRL & ZLL_PHY_CTRL_XCVSEQ_MASK) >> ZLL_PHY_CTRL_XCVSEQ_SHIFT;

        DEBUG("[kw41zrf] XCVSEQ=0x%x, SEQ_STATE=0x%" PRIx32 ", SEQ_CTRL_STS=0x%" PRIx32 "\n", seq,
            ZLL->SEQ_STATE, ZLL->SEQ_CTRL_STS);

        switch (seq)
        {
            case XCVSEQ_TRANSMIT:
            case XCVSEQ_TX_RX:
            case XCVSEQ_CCA:
                /* We should wait until TX or CCA has finished before moving to
                 * another mode */
                return 0;
            default:
                break;
        }
    }

    return 1;
}

void isr_radio_1(void)
{
    DEBUG("[kw41zrf] INT1\n");
    if (isr_config.cb != NULL) {
        isr_config.cb(isr_config.arg);
    }
    cortexm_isr_end();
}