/*
 * Copyright (c) 2015 - 2020, Nordic Semiconductor ASA
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <nrfx.h>

#if NRFX_CHECK(NRFX_PDM_ENABLED)

#include <nrfx_pdm.h>
#include <hal/nrf_gpio.h>

#define NRFX_LOG_MODULE PDM
#include <nrfx_log.h>

#define EVT_TO_STR(event)                                       \
    (event == NRF_PDM_EVENT_STARTED ? "NRF_PDM_EVENT_STARTED" : \
    (event == NRF_PDM_EVENT_STOPPED ? "NRF_PDM_EVENT_STOPPED" : \
    (event == NRF_PDM_EVENT_END     ? "NRF_PDM_EVENT_END"     : \
                                      "UNKNOWN EVENT")))


/** @brief PDM interface status. */
typedef enum
{
    NRFX_PDM_STATE_IDLE,
    NRFX_PDM_STATE_RUNNING,
    NRFX_PDM_STATE_STARTING,
    NRFX_PDM_STATE_STOPPING
} nrfx_pdm_state_t;

/** @brief PDM interface control block.*/
typedef struct
{
    nrfx_pdm_event_handler_t  event_handler;    ///< Event handler function pointer.
    int16_t *                 buff_address[2];  ///< Sample buffers.
    uint16_t                  buff_length[2];   ///< Length of the sample buffers.
    nrfx_drv_state_t          drv_state;        ///< Driver state.
    volatile nrfx_pdm_state_t op_state;         ///< PDM peripheral operation state.
    uint8_t                   active_buffer;    ///< Number of currently active buffer.
    uint8_t                   error;            ///< Driver error flag.
    volatile uint8_t          irq_buff_request; ///< Request the next buffer in the ISR.
} nrfx_pdm_cb_t;

static nrfx_pdm_cb_t m_cb;


void nrfx_pdm_irq_handler(void)
{
    if (nrf_pdm_event_check(NRF_PDM0, NRF_PDM_EVENT_STARTED))
    {
        nrf_pdm_event_clear(NRF_PDM0, NRF_PDM_EVENT_STARTED);
        NRFX_LOG_DEBUG("Event: %s.", EVT_TO_STR(NRF_PDM_EVENT_STARTED));

        uint8_t finished_buffer = m_cb.active_buffer;

        // Check if the next buffer was set before.
        uint8_t next_buffer = (~m_cb.active_buffer) & 0x01;
        if (m_cb.buff_address[next_buffer] ||
            m_cb.op_state == NRFX_PDM_STATE_STARTING)
        {
            nrfx_pdm_evt_t evt;
            evt.error = NRFX_PDM_NO_ERROR;
            m_cb.error = 0;

            // Release the full buffer if ready and request the next one.
            if (m_cb.op_state == NRFX_PDM_STATE_STARTING)
            {
                evt.buffer_released = 0;
                m_cb.op_state = NRFX_PDM_STATE_RUNNING;
            }
            else
            {
                evt.buffer_released = m_cb.buff_address[finished_buffer];
                m_cb.buff_address[finished_buffer] = 0;
                m_cb.active_buffer = next_buffer;
            }
            evt.buffer_requested = true;
            m_cb.event_handler(&evt);
        }
        else
        {
            // No next buffer available. Report an error.
            // Do not request the new buffer as it was already done.
            if (m_cb.error == 0)
            {
                nrfx_pdm_evt_t const evt = {
                    .buffer_requested = false,
                    .buffer_released  = NULL,
                    .error = NRFX_PDM_ERROR_OVERFLOW
                };
                m_cb.error = 1;
                m_cb.event_handler(&evt);
            }
        }

        if (m_cb.op_state == NRFX_PDM_STATE_STARTING)
        {
            m_cb.op_state = NRFX_PDM_STATE_RUNNING;
        }
    }
    else if (nrf_pdm_event_check(NRF_PDM0, NRF_PDM_EVENT_STOPPED))
    {
        nrf_pdm_event_clear(NRF_PDM0, NRF_PDM_EVENT_STOPPED);
        NRFX_LOG_DEBUG("Event: %s.", EVT_TO_STR(NRF_PDM_EVENT_STOPPED));
        nrf_pdm_disable(NRF_PDM0);
        m_cb.op_state = NRFX_PDM_STATE_IDLE;

        // Release the buffers.
        nrfx_pdm_evt_t evt;
        evt.error = NRFX_PDM_NO_ERROR;
        evt.buffer_requested = false;
        if (m_cb.buff_address[m_cb.active_buffer])
        {
            evt.buffer_released = m_cb.buff_address[m_cb.active_buffer];
            m_cb.buff_address[m_cb.active_buffer] = 0;
            m_cb.event_handler(&evt);
        }

        uint8_t second_buffer = (~m_cb.active_buffer) & 0x01;
        if (m_cb.buff_address[second_buffer])
        {
            evt.buffer_released = m_cb.buff_address[second_buffer];
            m_cb.buff_address[second_buffer] = 0;
            m_cb.event_handler(&evt);
        }
        m_cb.active_buffer = 0;
    }

    if (m_cb.irq_buff_request)
    {
        nrfx_pdm_evt_t const evt =
        {
            .buffer_requested = true,
            .buffer_released  = NULL,
            .error = NRFX_PDM_NO_ERROR,
        };
        m_cb.irq_buff_request = 0;
        m_cb.event_handler(&evt);
    }
}


nrfx_err_t nrfx_pdm_init(nrfx_pdm_config_t const * p_config,
                         nrfx_pdm_event_handler_t  event_handler)
{
    NRFX_ASSERT(p_config);
    NRFX_ASSERT(event_handler);
    nrfx_err_t err_code;

    if (m_cb.drv_state != NRFX_DRV_STATE_UNINITIALIZED)
    {
        err_code = NRFX_ERROR_INVALID_STATE;
        NRFX_LOG_WARNING("Function: %s, error code: %s.",
                         __func__,
                         NRFX_LOG_ERROR_STRING_GET(err_code));
        return err_code;
    }

    if (p_config->gain_l > NRF_PDM_GAIN_MAXIMUM ||
        p_config->gain_r > NRF_PDM_GAIN_MAXIMUM)
    {
        err_code = NRFX_ERROR_INVALID_PARAM;
        NRFX_LOG_WARNING("Function: %s, error code: %s.",
                         __func__,
                         NRFX_LOG_ERROR_STRING_GET(err_code));
        return err_code;
    }

    m_cb.buff_address[0] = 0;
    m_cb.buff_address[1] = 0;
    m_cb.active_buffer = 0;
    m_cb.error = 0;
    m_cb.event_handler = event_handler;
    m_cb.op_state = NRFX_PDM_STATE_IDLE;

#if NRF_PDM_HAS_RATIO_CONFIG
    nrf_pdm_ratio_set(NRF_PDM0, p_config->ratio);
#endif

#if NRF_PDM_HAS_MCLKCONFIG
    nrf_pdm_mclksrc_configure(NRF_PDM0, p_config->mclksrc);
#endif
    nrf_pdm_clock_set(NRF_PDM0, p_config->clock_freq);
    nrf_pdm_mode_set(NRF_PDM0, p_config->mode, p_config->edge);
    nrf_pdm_gain_set(NRF_PDM0, p_config->gain_l, p_config->gain_r);

    nrf_gpio_cfg_output(p_config->pin_clk);
    nrf_gpio_pin_clear(p_config->pin_clk);
    nrf_gpio_cfg_input(p_config->pin_din, NRF_GPIO_PIN_NOPULL);
    nrf_pdm_psel_connect(NRF_PDM0, p_config->pin_clk, p_config->pin_din);

    nrf_pdm_event_clear(NRF_PDM0, NRF_PDM_EVENT_STARTED);
    nrf_pdm_event_clear(NRF_PDM0, NRF_PDM_EVENT_END);
    nrf_pdm_event_clear(NRF_PDM0, NRF_PDM_EVENT_STOPPED);
    nrf_pdm_int_enable(NRF_PDM0, NRF_PDM_INT_STARTED | NRF_PDM_INT_STOPPED);
    NRFX_IRQ_PRIORITY_SET(nrfx_get_irq_number(NRF_PDM0), p_config->interrupt_priority);
    NRFX_IRQ_ENABLE(nrfx_get_irq_number(NRF_PDM0));
    m_cb.drv_state = NRFX_DRV_STATE_INITIALIZED;

    err_code = NRFX_SUCCESS;
    NRFX_LOG_INFO("Function: %s, error code: %s.",
                  __func__,
                  NRFX_LOG_ERROR_STRING_GET(err_code));
    return err_code;
}

void nrfx_pdm_uninit(void)
{
    nrf_pdm_disable(NRF_PDM0);

    nrf_gpio_cfg_default(nrf_pdm_clk_pin_get(NRF_PDM0));
    nrf_gpio_cfg_default(nrf_pdm_din_pin_get(NRF_PDM0));

    m_cb.drv_state = NRFX_DRV_STATE_UNINITIALIZED;
    NRFX_LOG_INFO("Uninitialized.");
}

static void pdm_start()
{
    m_cb.drv_state = NRFX_DRV_STATE_POWERED_ON;
    nrf_pdm_enable(NRF_PDM0);
    nrf_pdm_event_clear(NRF_PDM0, NRF_PDM_EVENT_STARTED);
    nrf_pdm_task_trigger(NRF_PDM0, NRF_PDM_TASK_START);
}

static void pdm_buf_request()
{
    m_cb.irq_buff_request = 1;
    NRFX_IRQ_PENDING_SET(nrfx_get_irq_number(NRF_PDM0));
}

nrfx_err_t nrfx_pdm_start(void)
{
    NRFX_ASSERT(m_cb.drv_state != NRFX_DRV_STATE_UNINITIALIZED);
    nrfx_err_t err_code;

    if (m_cb.op_state != NRFX_PDM_STATE_IDLE)
    {
        if (m_cb.op_state == NRFX_PDM_STATE_RUNNING)
        {
            err_code = NRFX_SUCCESS;
            NRFX_LOG_INFO("Function: %s, error code: %s.",
                          __func__,
                          NRFX_LOG_ERROR_STRING_GET(err_code));
            return err_code;
        }
        err_code = NRFX_ERROR_BUSY;
        NRFX_LOG_WARNING("Function: %s, error code: %s.",
                         __func__,
                         NRFX_LOG_ERROR_STRING_GET(err_code));
        return err_code;
    }

    m_cb.op_state = NRFX_PDM_STATE_STARTING;
    pdm_buf_request();

    err_code = NRFX_SUCCESS;
    NRFX_LOG_INFO("Function: %s, error code: %s.",
                  __func__,
                  NRFX_LOG_ERROR_STRING_GET(err_code));
    return err_code;
}

nrfx_err_t nrfx_pdm_buffer_set(int16_t * buffer, uint16_t buffer_length)
{
    if (m_cb.drv_state == NRFX_DRV_STATE_UNINITIALIZED)
    {
        return NRFX_ERROR_INVALID_STATE;
    }
    if (m_cb.op_state == NRFX_PDM_STATE_STOPPING)
    {
        return NRFX_ERROR_BUSY;
    }
    if ((buffer == NULL) || (buffer_length > NRFX_PDM_MAX_BUFFER_SIZE))
    {
        return NRFX_ERROR_INVALID_PARAM;
    }

    nrfx_err_t err_code = NRFX_SUCCESS;

    // Enter the PDM critical section.
    NRFX_IRQ_DISABLE(nrfx_get_irq_number(NRF_PDM0));

    uint8_t next_buffer = (~m_cb.active_buffer) & 0x01;
    if (m_cb.op_state == NRFX_PDM_STATE_STARTING)
    {
        next_buffer = 0;
    }

    if (m_cb.buff_address[next_buffer])
    {
        // Buffer already set.
        err_code = NRFX_ERROR_BUSY;
    }
    else
    {
        m_cb.buff_address[next_buffer] = buffer;
        m_cb.buff_length[next_buffer] = buffer_length;
        nrf_pdm_buffer_set(NRF_PDM0, (uint32_t *)buffer, buffer_length);

        if (m_cb.drv_state != NRFX_DRV_STATE_POWERED_ON)
        {
            pdm_start();
        }
    }

    NRFX_IRQ_ENABLE(nrfx_get_irq_number(NRF_PDM0));
    return err_code;
}

nrfx_err_t nrfx_pdm_stop(void)
{
    NRFX_ASSERT(m_cb.drv_state != NRFX_DRV_STATE_UNINITIALIZED);
    nrfx_err_t err_code;

    if (m_cb.op_state != NRFX_PDM_STATE_RUNNING)
    {
        if (m_cb.op_state == NRFX_PDM_STATE_IDLE ||
            m_cb.op_state == NRFX_PDM_STATE_STARTING)
        {
            nrf_pdm_disable(NRF_PDM0);
            m_cb.op_state = NRFX_PDM_STATE_IDLE;
            err_code = NRFX_SUCCESS;
            NRFX_LOG_INFO("Function: %s, error code: %s.",
                          __func__,
                          NRFX_LOG_ERROR_STRING_GET(err_code));
            return err_code;
        }
        err_code = NRFX_ERROR_BUSY;
        NRFX_LOG_WARNING("Function: %s, error code: %s.",
                         __func__,
                         NRFX_LOG_ERROR_STRING_GET(err_code));
        return err_code;
    }
    m_cb.drv_state = NRFX_DRV_STATE_INITIALIZED;
    m_cb.op_state = NRFX_PDM_STATE_STOPPING;

    nrf_pdm_task_trigger(NRF_PDM0, NRF_PDM_TASK_STOP);
    err_code = NRFX_SUCCESS;
    NRFX_LOG_INFO("Function: %s, error code: %s.", __func__, NRFX_LOG_ERROR_STRING_GET(err_code));
    return err_code;
}

#endif // NRFX_CHECK(NRFX_PDM_ENABLED)
