/**
 ******************************************************************************
 * @addtogroup PIOS PIOS Core hardware abstraction layer
 * @{
 * @addtogroup PIOS_SBus Futaba S.Bus receiver functions
 * @brief Code to read Futaba S.Bus receiver serial stream
 * @{
 *
 * @file       pios_sbus.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2011.
 * @brief      Code to read Futaba S.Bus receiver serial stream
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "pios.h"

#ifdef PIOS_INCLUDE_SBUS

#include "pios_sbus_priv.h"

/* Forward Declarations */
static int32_t PIOS_SBus_Get(uint32_t rcvr_id, uint8_t channel);
static uint16_t PIOS_SBus_RxInCallback(uint32_t context,
                                       uint8_t *buf,
                                       uint16_t buf_len,
                                       uint16_t *headroom,
                                       bool *need_yield);
static void PIOS_SBus_Supervisor(uint32_t sbus_id);


/* Local Variables */
const struct pios_rcvr_driver pios_sbus_rcvr_driver = {
    .read = PIOS_SBus_Get,
};

enum pios_sbus_dev_magic {
    PIOS_SBUS_DEV_MAGIC = 0x53427573,
};

struct pios_sbus_state {
    uint16_t channel_data[PIOS_SBUS_NUM_INPUTS];
    uint8_t  received_data[SBUS_FRAME_LENGTH - 2];
    uint8_t  receive_timer;
    uint8_t  failsafe_timer;
    uint8_t  frame_found;
    uint8_t  byte_count;
};

struct pios_sbus_dev {
    enum pios_sbus_dev_magic   magic;
    const struct pios_sbus_cfg *cfg;
    struct pios_sbus_state     state;
};

/* Allocate S.Bus device descriptor */
#if defined(PIOS_INCLUDE_FREERTOS)
static struct pios_sbus_dev *PIOS_SBus_Alloc(void)
{
    struct pios_sbus_dev *sbus_dev;

    sbus_dev = (struct pios_sbus_dev *)pvPortMalloc(sizeof(*sbus_dev));
    if (!sbus_dev) {
        return NULL;
    }

    sbus_dev->magic = PIOS_SBUS_DEV_MAGIC;
    return sbus_dev;
}
#else
static struct pios_sbus_dev pios_sbus_devs[PIOS_SBUS_MAX_DEVS];
static uint8_t pios_sbus_num_devs;
static struct pios_sbus_dev *PIOS_SBus_Alloc(void)
{
    struct pios_sbus_dev *sbus_dev;

    if (pios_sbus_num_devs >= PIOS_SBUS_MAX_DEVS) {
        return NULL;
    }

    sbus_dev = &pios_sbus_devs[pios_sbus_num_devs++];
    sbus_dev->magic = PIOS_SBUS_DEV_MAGIC;

    return sbus_dev;
}
#endif /* if defined(PIOS_INCLUDE_FREERTOS) */

/* Validate S.Bus device descriptor */
static bool PIOS_SBus_Validate(struct pios_sbus_dev *sbus_dev)
{
    return sbus_dev->magic == PIOS_SBUS_DEV_MAGIC;
}

/* Reset channels in case of lost signal or explicit failsafe receiver flag */
static void PIOS_SBus_ResetChannels(struct pios_sbus_state *state)
{
    for (int i = 0; i < PIOS_SBUS_NUM_INPUTS; i++) {
        state->channel_data[i] = PIOS_RCVR_TIMEOUT;
    }
}

/* Reset S.Bus receiver state */
static void PIOS_SBus_ResetState(struct pios_sbus_state *state)
{
    state->receive_timer  = 0;
    state->failsafe_timer = 0;
    state->frame_found    = 0;
    PIOS_SBus_ResetChannels(state);
}

/* Initialise S.Bus receiver interface */
int32_t PIOS_SBus_Init(uint32_t *sbus_id,
                       const struct pios_sbus_cfg *cfg,
                       const struct pios_com_driver *driver,
                       uint32_t lower_id)
{
    PIOS_DEBUG_Assert(sbus_id);
    PIOS_DEBUG_Assert(cfg);
    PIOS_DEBUG_Assert(driver);

    struct pios_sbus_dev *sbus_dev;

    sbus_dev = (struct pios_sbus_dev *)PIOS_SBus_Alloc();
    if (!sbus_dev) {
        goto out_fail;
    }

    /* Bind the configuration to the device instance */
    sbus_dev->cfg = cfg;

    PIOS_SBus_ResetState(&(sbus_dev->state));

    *sbus_id = (uint32_t)sbus_dev;

    /* Enable inverter clock and enable the inverter */
    (*cfg->gpio_clk_func)(cfg->gpio_clk_periph, ENABLE);
    GPIO_Init(cfg->inv.gpio, &cfg->inv.init);
    GPIO_WriteBit(cfg->inv.gpio, cfg->inv.init.GPIO_Pin, cfg->gpio_inv_enable);

    /* Set comm driver callback */
    (driver->bind_rx_cb)(lower_id, PIOS_SBus_RxInCallback, *sbus_id);

    if (!PIOS_RTC_RegisterTickCallback(PIOS_SBus_Supervisor, *sbus_id)) {
        PIOS_DEBUG_Assert(0);
    }

    return 0;

out_fail:
    return -1;
}

/**
 * Get the value of an input channel
 * \param[in] channel Number of the channel desired (zero based)
 * \output PIOS_RCVR_INVALID channel not available
 * \output PIOS_RCVR_TIMEOUT failsafe condition or missing receiver
 * \output >=0 channel value
 */
static int32_t PIOS_SBus_Get(uint32_t rcvr_id, uint8_t channel)
{
    struct pios_sbus_dev *sbus_dev = (struct pios_sbus_dev *)rcvr_id;

    if (!PIOS_SBus_Validate(sbus_dev)) {
        return PIOS_RCVR_INVALID;
    }

    /* return error if channel is not available */
    if (channel >= PIOS_SBUS_NUM_INPUTS) {
        return PIOS_RCVR_INVALID;
    }

    return sbus_dev->state.channel_data[channel];
}

/**
 * Compute channel_data[] from received_data[].
 * For efficiency it unrolls first 8 channels without loops and does the
 * same for other 8 channels. Also 2 discrete channels will be set.
 */
static void PIOS_SBus_UnrollChannels(struct pios_sbus_state *state)
{
    uint8_t *s  = state->received_data;
    uint16_t *d = state->channel_data;

#define F(v, s) (((v) >> (s)) & 0x7ff)

    /* unroll channels 1-8 */
    *d++ = F(s[0] | s[1] << 8, 0);
    *d++ = F(s[1] | s[2] << 8, 3);
    *d++ = F(s[2] | s[3] << 8 | s[4] << 16, 6);
    *d++ = F(s[4] | s[5] << 8, 1);
    *d++ = F(s[5] | s[6] << 8, 4);
    *d++ = F(s[6] | s[7] << 8 | s[8] << 16, 7);
    *d++ = F(s[8] | s[9] << 8, 2);
    *d++ = F(s[9] | s[10] << 8, 5);

    /* unroll channels 9-16 */
    *d++ = F(s[11] | s[12] << 8, 0);
    *d++ = F(s[12] | s[13] << 8, 3);
    *d++ = F(s[13] | s[14] << 8 | s[15] << 16, 6);
    *d++ = F(s[15] | s[16] << 8, 1);
    *d++ = F(s[16] | s[17] << 8, 4);
    *d++ = F(s[17] | s[18] << 8 | s[19] << 16, 7);
    *d++ = F(s[19] | s[20] << 8, 2);
    *d++ = F(s[20] | s[21] << 8, 5);

    /* unroll discrete channels 17 and 18 */
    *d++ = (s[22] & SBUS_FLAG_DC1) ? SBUS_VALUE_MAX : SBUS_VALUE_MIN;
    *d++ = (s[22] & SBUS_FLAG_DC2) ? SBUS_VALUE_MAX : SBUS_VALUE_MIN;
}

/* Update decoder state processing input byte from the S.Bus stream */
static void PIOS_SBus_UpdateState(struct pios_sbus_state *state, uint8_t b)
{
    /* should not process any data until new frame is found */
    if (!state->frame_found) {
        return;
    }

    if (state->byte_count == 0) {
        if (b != SBUS_SOF_BYTE) {
            /* discard the whole frame if the 1st byte is not correct */
            state->frame_found = 0;
        } else {
            /* do not store the SOF byte */
            state->byte_count++;
        }
        return;
    }

    /* do not store last frame byte as well */
    if (state->byte_count < SBUS_FRAME_LENGTH - 1) {
        /* store next byte */
        state->received_data[state->byte_count - 1] = b;
        state->byte_count++;
    } else {
        if (b == SBUS_EOF_BYTE) {
            /* full frame received */
            uint8_t flags = state->received_data[SBUS_FRAME_LENGTH - 3];
            if (flags & SBUS_FLAG_FL) {
                /* frame lost, do not update */
            } else if (flags & SBUS_FLAG_FS) {
                /* failsafe flag active */
                PIOS_SBus_ResetChannels(state);
            } else {
                /* data looking good */
                PIOS_SBus_UnrollChannels(state);
                state->failsafe_timer = 0;
            }
        } else {
            /* discard whole frame */
        }

        /* prepare for the next frame */
        state->frame_found = 0;
    }
}

/* Comm byte received callback */
static uint16_t PIOS_SBus_RxInCallback(uint32_t context,
                                       uint8_t *buf,
                                       uint16_t buf_len,
                                       uint16_t *headroom,
                                       bool *need_yield)
{
    struct pios_sbus_dev *sbus_dev = (struct pios_sbus_dev *)context;

    bool valid = PIOS_SBus_Validate(sbus_dev);

    PIOS_Assert(valid);

    struct pios_sbus_state *state = &(sbus_dev->state);

    /* process byte(s) and clear receive timer */
    for (uint8_t i = 0; i < buf_len; i++) {
        PIOS_SBus_UpdateState(state, buf[i]);
        state->receive_timer = 0;
    }

    /* Always signal that we can accept another byte */
    if (headroom) {
        *headroom = SBUS_FRAME_LENGTH;
    }

    /* We never need a yield */
    *need_yield = false;

    /* Always indicate that all bytes were consumed */
    return buf_len;
}

/**
 * Input data supervisor is called periodically and provides
 * two functions: frame syncing and failsafe triggering.
 *
 * S.Bus frames come at 7ms (HS) or 14ms (FS) rate at 100000bps.
 * RTC timer is running at 625Hz (1.6ms). So with divider 2 it gives
 * 3.2ms pause between frames which is good for both S.Bus frame rates.
 *
 * Data receive function must clear the receive_timer to confirm new
 * data reception. If no new data received in 100ms, we must call the
 * failsafe function which clears all channels.
 */
static void PIOS_SBus_Supervisor(uint32_t sbus_id)
{
    struct pios_sbus_dev *sbus_dev = (struct pios_sbus_dev *)sbus_id;

    bool valid = PIOS_SBus_Validate(sbus_dev);

    PIOS_Assert(valid);

    struct pios_sbus_state *state = &(sbus_dev->state);

    /* waiting for new frame if no bytes were received in 3.2ms */
    if (++state->receive_timer > 2) {
        state->frame_found   = 1;
        state->byte_count    = 0;
        state->receive_timer = 0;
    }

    /* activate failsafe if no frames have arrived in 102.4ms */
    if (++state->failsafe_timer > 64) {
        PIOS_SBus_ResetChannels(state);
        state->failsafe_timer = 0;
    }
}

#endif /* PIOS_INCLUDE_SBUS */

/**
 * @}
 * @}
 */