/*
 * i2s_init.c
 *
 *  Created on: 20 ÿíâ. 2016 ã.
 *      Author: Andrey
 */

#include <inc/hw_types.h>

#include <driverlib/prcm.h>
#include <driverlib/i2s.h>

#include <ti/sysbios/family/arm/cc26xx/Power.h>
#include <ti/sysbios/family/arm/cc26xx/PowerCC2650.h>
#include <ti/drivers/pin/PINCC26XX.h>

#include "util.h"
#include "Board.h"

#define BCLK_DIV (16)                           /* 48MHz/16 = 3MHz */

#define WCLK (IOID_1)
#define BCLK (IOID_15)
#define MOSI (IOID_18)
#define MISO (IOID_19)

#define I2S_BUFF_SIZE    (256)
#define I2S_BUFF_IN_NUM  (2)
#define I2S_BUFF_OUT_NUM (1)

uint16_t i2s_in [I2S_BUFF_SIZE * I2S_BUFF_IN_NUM];
uint16_t i2s_out[I2S_BUFF_SIZE * I2S_BUFF_OUT_NUM];

static PIN_State pinState;
static PIN_Handle pinHandle;
static ti_sysbios_family_arm_m3_Hwi_Struct hwiStruct;

static uint32_t index_in;
static uint32_t index_out;


static inline uint16_t* i2s_in_iter(void)
{
    uint16_t* addr = &(i2s_in[I2S_BUFF_SIZE * index_in]);
    index_in++;
    index_in %= I2S_BUFF_IN_NUM;

    return addr;
}

static inline uint16_t* i2s_out_iter(void)
{
    uint16_t* addr = &(i2s_out[I2S_BUFF_SIZE * index_out]);
    index_out++;
    index_out %= I2S_BUFF_OUT_NUM;

    return addr;
}

static inline void i2s_iter(void)
{
    // DMA Output Buffer Next Pointer
    HWREG(I2S0_BASE + I2S_O_AIFOUTPTRNEXT) = (uint32_t)i2s_out_iter();
    // DMA Input Buffer Next Pointer
    HWREG(I2S0_BASE + I2S_O_AIFINPTRNEXT)  = (uint32_t)i2s_in_iter();
}

static inline void i2s_extractData(void)
{
}

/*
 *  Termination Sequence implemented according to
 *  <swcu117d.pdf> 22.9.2. Termination Sequence.
 */
static void i2s_terminate(void)
{
    HWREG(I2S0_BASE + I2S_O_AIFDMACFG) = 0;
    HWREG(I2S0_BASE + I2S_O_STMPCTL) = 0;
    HWREG(I2S0_BASE + I2S_O_IRQCLR) = 0x3F;
    HWREG(PRCM_BASE + PRCM_O_I2SCLKCTL) = 0;
    HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;
    HWREG(I2S0_BASE + I2S_O_IRQMASK) = 0;
}

/*
 *  Interrupt service routine.
 *
 */
static void i2s_Isr(UArg arg) {

    uint32_t flags = 0xf & HWREG(I2S0_BASE + I2S_O_IRQFLAGS);

    if (flags)
    {
        i2s_terminate();
    }
    else
    {
        i2s_iter();
    }

    i2s_extractData();
}

/*
 *  ======== i2s_init ========
 *
 *  Initialization Sequence implemented according to
 *  <swcu117d.pdf> 22.9.1. Start-up Sequence.
 */
void i2s_init(void)
{
    index_in  = 0;
    index_out = 0;


    // step 1. Set up and configure required ADx and clock pins (set externally in the IOC module).
    {
        PIN_Status res = PIN_SUCCESS;
        PIN_Config pinTable[] = {

                WCLK | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH,
                BCLK | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW,
                MOSI | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH,
                MISO | PIN_INPUT_EN | PIN_PULLUP,
                PIN_TERMINATE
        };

        /* Open and assign pins through pin driver */
        pinHandle = PIN_open(&pinState, pinTable);

        /* Are pins already allocated */
        if (NULL == pinHandle) {
            while(1);
        }

        /* Set IO muxing for the I2S pins */
        res |= PINCC26XX_setMux(pinHandle, WCLK, IOC_PORT_MCU_I2S_WCLK);
        res |= PINCC26XX_setMux(pinHandle, BCLK, IOC_PORT_MCU_I2S_BCLK);
        res |= PINCC26XX_setMux(pinHandle, MOSI, IOC_PORT_MCU_I2S_AD0);
        res |= PINCC26XX_setMux(pinHandle, MISO, IOC_PORT_MCU_I2S_AD1);

        if (PIN_SUCCESS != res) {
            while(1);
        }
    }

    // step 2. Enable I2S peripheral and configure WCLK and MCLK audio clocks (set externally in the PRCM
    // module).
    {
        Power_setConstraint(Power_SB_DISALLOW);
        if (!Power_setDependency(PERIPH_I2S)) while(1);

        // I2S Clock Gate For Run Mode
        HWREG(PRCM_BASE + PRCM_O_I2SCLKGR) = PRCM_I2SCLKGR_CLK_EN;
        // I2S Clock Gate For Sleep Mode
        HWREG(PRCM_BASE + PRCM_O_I2SCLKGS) = PRCM_I2SCLKGS_CLK_EN;

        // I2S Clock Control. (data and WCLK are sampled on the positive edge of BCLK, User Defined WCLK, CLK Disabled)
        HWREG(PRCM_BASE + PRCM_O_I2SCLKCTL) = (PRCM_I2SCLKCTL_SMPL_ON_POSEDGE | (2 << PRCM_I2SCLKCTL_WCLK_PHASE_S));
        // WCLK Division Ratio. For User Defined HI+LO WCLK = MCUCLK / (BDIV*(WDIV[7:0] + WDIV[15:8]) [Hz]
        HWREG(PRCM_BASE + PRCM_O_I2SWCLKDIV) = (7 << 0) | (17 << 8);
        // MCLK Division Ratio (MCLK = MCUCLK/MDIV[Hz] = 48MHz/2 = 24Mhz)
        HWREG(PRCM_BASE + PRCM_O_I2SMCLKDIV) = 0;

        // Load Settings from buffers to actual registers.
        HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;
    }

    // step 3. Configure the serial audio interface format and the memory interface controller:
    // • Set the following registers: I2S:AIFWCLKSRC, I2S:AIFDIRCFG, I2S:AIFFMTCFG,
    //   I2S:AIFWMSK0, I2S:AIFWMSK1, and I2S:AIFWMSK2. BCLK must not be running when changing
    //   the I2S:AIFWCLKSRC register.
    {
        // WCLK Source Selection. (Invert WCLK, Internal WCLK generator)
        HWREG(I2S0_BASE + I2S_O_AIFWCLKSRC) = (I2S_AIFWCLKSRC_WCLK_INV | I2S_AIFWCLKSRC_WCLK_SRC_INT);

        // Pin Direction
        HWREG(I2S0_BASE + I2S_O_AIFDIRCFG) = (I2S_AIFDIRCFG_AD1_IN | I2S_AIFDIRCFG_AD0_OUT);

        // Serial Interface Format Configuration.
        HWREG(I2S0_BASE + I2S_O_AIFFMTCFG) = (0 << I2S_AIFFMTCFG_DATA_DELAY_S) |
                                             (I2S_AIFFMTCFG_MEM_LEN_24_16BIT)  |
                                             (I2S_AIFFMTCFG_SMPL_EDGE_POS)     |
                                             (0 << I2S_AIFFMTCFG_DUAL_PHASE_S) |
                                             (16 << I2S_AIFFMTCFG_WORD_LEN_S);

        // Word Selection Bit Mask for Pin 0. (MONO)
        HWREG(I2S0_BASE + I2S_O_AIFWMASK0) = (1 << I2S_AIFWMASK0_MASK_S);

        // Word Selection Bit Mask for Pin 1. (MONO)
        HWREG(I2S0_BASE + I2S_O_AIFWMASK1) = (1 << I2S_AIFWMASK1_MASK_S);

        // Word Selection Bit Mask for Pin 2. (OFF)
        HWREG(I2S0_BASE + I2S_O_AIFWMASK2) = (0 << I2S_AIFWMASK2_MASK_S);
    }

    // step 4. Enable BCLK (set externally in the PRCM module).
    {
        // I2S Clock Control. (data and WCLK are sampled on the positive edge of BCLK, User Defined WCLK, CLK Enable)
        HWREG(PRCM_BASE + PRCM_O_I2SCLKCTL) = (PRCM_I2SCLKCTL_SMPL_ON_POSEDGE | (2 << PRCM_I2SCLKCTL_WCLK_PHASE_S) | PRCM_I2SCLKCTL_EN);
        // I2S Clock Control. (Use internally generated clock)
        HWREG(PRCM_BASE + PRCM_O_I2SBCLKSEL) = PRCM_I2SBCLKSEL_SRC;
        // BCLK Division Ratio. (BCLK = MCUCLK/BDIV[Hz] = 48MHz/2 = 24Mhz)
        HWREG(PRCM_BASE + PRCM_O_I2SBCLKDIV) = BCLK_DIV;

        // Load Settings from buffers to actual registers.
        HWREG(PRCM_BASE + PRCM_O_CLKLOADCTL) = PRCM_CLKLOADCTL_LOAD;
    }


    // step 5. Configure and prepare the samplestamp generator:
    // • Set the I2S:STMPWPER register. This number corresponds to the total size of the sample ring
    //   buffer used by the system.
    // • Set the two registers I2S:STMPINTRIG and I2S:STMPOUTTRIG > I2S:STMPWPER to avoid false
    //   triggers before the samplestamp generator is started.
    {
        uint16_t tmp = I2S_BUFF_SIZE + 6;

        // WCLK Counter Period Value
        HWREG(I2S0_BASE + I2S_O_STMPWPER) = tmp++;
        // WCLK Counter Trigger Value for Input Pins
        HWREG(I2S0_BASE + I2S_O_STMPINTRIG) = tmp;
        // WCLK Counter Trigger Value for Output Pins
        HWREG(I2S0_BASE + I2S_O_STMPOUTTRIG) = tmp;
    }

    // step 6. Enable the samplestamp generator:
    // • Set I2S:STMPCTRL.STMP_EN = 1
    // • Optional steps:
    //   – Poll the I2S:STMPWCNT register and wait until the counter value is 2 or higher:
    // • When the value is 2 or higher, there are no more false increments (as described in
    //   Section 22.8.1, Counters and Registers).
    // • When the value is 4 or higher, the WCLK period is read out from the I2S:STMPXPER
    //   register. This is used to determine the sample rate when using an external clock source.
    // • Reset the WCLK counter by writing I2S:STMPWSET = 0
    {
        // SampleStaMP Generator Control Register
        HWREG(I2S0_BASE + I2S_O_STMPCTL) = I2S_STMPCTL_STMP_EN;
    }

    // step 7. Enable the serial audio interface:
    // • Set the I2S:AIFINPTRNEXT and the I2S:AIFOUTPTRNEXT registers for first memory interface
    //   buffers.
    // • Set the I2S:AIFDMACFG register; This number corresponds to the length of each block in the
    //   sample ring buffer used by the system.
    // • Set the I2S:AIFINPTRNEXT and the I2S:AIFOUTPTRNEXT registers for second memory interface
    //   buffers.
    {
        Hwi_Params hwiParams;

        // Setup HWI handler
        Hwi_Params_init(&hwiParams);
        Hwi_construct(&hwiStruct, INT_I2S, i2s_Isr, &hwiParams, NULL);
    }

    {
        // Masked Interrupt Status Register. (enable DMA_IN interrupt)
        HWREG(I2S0_BASE + I2S_O_IRQMASK) = I2S_IRQMASK_AIF_DMA_IN;

        i2s_iter();

        // DMA Buffer Size Configuration
        HWREG(I2S0_BASE + I2S_O_AIFDMACFG) = ((I2S_BUFF_SIZE - 1) << I2S_AIFDMACFG_END_FRAME_IDX_S);

        i2s_iter();
    }

    // step 8. Start input and output audio streaming:
    // • Set the I2S:STMPINTRIG and the I2S:STMPOUTTRIG registers so they correctly match the
    //   I2S:AIFINPTR and the I2S:AIFOUTPTR registers.
    {
        // Current Value of WCNT
        while (5 > HWREG(I2S0_BASE + I2S_O_STMPWCNT));

        // WCLK Counter Trigger Value for Input Pins
        HWREG(I2S0_BASE + I2S_O_STMPINTRIG) = 0;
        // WCLK Counter Trigger Value for Output Pins
        HWREG(I2S0_BASE + I2S_O_STMPOUTTRIG) = 0;

        // Interrupt Clear Register
        HWREG(I2S0_BASE + I2S_O_IRQCLR) = 0xF;
    }
}

