/*
 * ssp_device.cpp
 *
 *  Created on: 15 нояб. 2018 г.
 *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - 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.
   - Neither the name of ARM 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 COPYRIGHT HOLDERS AND 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 <cassert>
#include "ssp_device.hpp"
#include <mcu_rcc.h>
#include <mcu_ssp.h>
#include <gpio.h>
#include <os_isr.h>
#include <system.h>
#include "hw.h"
#include <cslr.h>
#include <trace_probes.h>

#ifndef trace_ssp_wr
trace_need(ssp_wr)
trace_need(ssp_rd)
trace_need(ssp_tx)
trace_need(ssp_wait)
trace_need(ssp_post)
trace_need(ssp_trx)
trace_need(sspio_wait)
#endif

#if 0
#define ASSERT_SSP(...) ASSERT_SSP(__VA_ARGS__)
#else
#define ASSERT_SSP(...)
#endif

const NVIC_InitTypeDef  SSP1_NVIC = {
        struct_field(NVIC_IRQChannel)                     SSP1_IRQn
      , struct_field(NVIC_IRQChannelPreemptionPriority)   IRQPRIO(IRQP_SPI)
      , struct_field(NVIC_IRQChannelSubPriority)          IRQPRIO(IRQP_SPI)
      , struct_field(NVIC_IRQChannelCmd)                  ENABLE
};

const NVIC_InitTypeDef  SSP2_NVIC = {
        struct_field(NVIC_IRQChannel)                     SSP2_IRQn
      , struct_field(NVIC_IRQChannelPreemptionPriority)   IRQPRIO(IRQP_SPI)
      , struct_field(NVIC_IRQChannelSubPriority)          IRQPRIO(IRQP_SPI)
      , struct_field(NVIC_IRQChannelCmd)                  ENABLE
};

const SSP_InitTypeDef SPIMaster_8bit_CKLoRise = {
        struct_field(SSP_SCR)                       0   //F_SSPCLK / ( CPSDVR * (1 + SCR) )
      , struct_field(SSP_CPSDVSR)                   2   //This parameter is an even number from 2 to 254
      , struct_field(SSP_Mode)                      SSP_ModeMaster
      , struct_field(SSP_WordLength)                SSP_WordLength8b
      , struct_field(SSP_SPH)                       SSP_SPH_1Edge
      , struct_field(SSP_SPO)                       SSP_SPO_Low
      , struct_field(SSP_FRF)                       SSP_FRF_SPI_Motorola
      , struct_field(SSP_HardwareFlowControl)       SSP_HardwareFlowControl_None //SSP_HardwareFlowControl_SSE
};



SSP_SWMasterDevice::SSP_SWMasterDevice(dev_name name)
:inherited()
, nameddev_t(name)
, cfg(NULL), io(NULL)
, css(name)
, msg(NULL)
, fsck_hz(0)
, ssp_mode(0)
{};

int SSP_SWMasterDevice::init( const SSP_INIT* _cfg){
    cfg = _cfg;
    io = cfg->port;
    ASSERT_SSP(io != NULL);
    css.init(cfg->cspins);
    if(cfg->nvic)
        NVIC_Init(cfg->nvic);

    RST_CLK_PCLKcmd( cfg->rcc, ENABLE);
    if(cfg->GPIOPINS){
        gpio_conf_func(cfg->GPIOPINS);
    }
    SSP_BRGInit(io, SSP_HCLKdiv1);
    SSP_Init(io, cfg->cfg);
    fsck_hz = SystemCoreClock/2;
    SSP_Cmd(io, ENABLE);

    return init();
}

// SSP_Device
//virtual
int SSP_SWMasterDevice::init(){
    access.init();
    ctx.reset();
    ctx.finished.init();
    return DEV_OK;
}

//virtual
int SSP_SWMasterDevice::deinit(){
    ctx.finish();
    SSP_Cmd(io, DISABLE);
    RST_CLK_PCLKcmd( cfg->rcc, DISABLE);
    return DEV_OK;
};

// BUS lock access
//virtual
PTResult SSP_SWMasterDevice::aquire(unsigned cs_id){
    PTResult res = access.lock();
    if (PT_SCHEDULE(res))
        return res;
    if (res == ptOK){
        return AS_PTRESULT(cs_select(cs_id));
    }
    return ptNOK;
}

//virtual
int SSP_SWMasterDevice::release(){
    if (!wait_ctx(0)){
        // освобождение линии запрошено до завершения транзакции, поэтому
        //  запрошу его опциями текущей транзакции
        msg->mode &= ~msCS_HOLD;
        return DEV_BUSY;
    }
    cs_release();
    access.unlock();
    return DEV_OK;
}

//virtual
DevResult SSP_SWMasterDevice::trx(Message& mesg){
    TRACE_GUARD(ssp, ssp_trx);
    if (ostimeout_active(&finto))
        ostimeout_stop(&finto);
    DevResult ok = setup_port(&mesg);
    if (ok != DEV_OK)
        return ok;
    msg = &mesg;
    ok = post(mesg.dst, mesg.src, mesg.word_count);
    return ok;
}

//virtual
PTResult SSP_SWMasterDevice::wait(Message& x, unsigned to){
    // задание Х не выполняется сейчас
    if (msg != &x)
        return ptOK;
    PTResult ok = DEV_OK;
    if (to == toInfinite){
        ok = wait_ctx();
    }
    else
        ok = wait_ctx(to);

    if (PT_SCHEDULE(ok))
        return ok;

    if (ok == ptOK)
    if ((msg->mode & msCS_HOLD) == 0)
    {
        // транзакция не удерживает линию связи, поэтому освобожу ее
        wait_port_empty();
        cs_release();
    }
    return ok;
}

void SSP_SWMasterDevice::wait_port_empty(){
    //wait while sending active
    while (SSP_GetFlagStatus(io, SSP_FLAG_BSY) != 0)
    //while ( (io->SR & SSP_FLAG_BSY) != 0)
    {
        wait_fast();
    }
}

bool SSP_SWMasterDevice::is_port_sending(){
    unsigned st = io->SR;
    return ((st & SSP_FLAG_BSY) != 0) || ( (st & SSP_FLAG_TFE) == 0) ;
}

void SSP_SWMasterDevice::wait_port_notfull(){
    //wait while sending active
    while (SSP_GetFlagStatus(io, SSP_FLAG_TNF) == 0)
    //while ( (io->SR & SSP_FLAG_TNF) != 0)
    {
        wait_fast();
    }
}

//virtual
DevResult SSP_SWMasterDevice::abort(Message& msg){
    ctx.finish();
    return DEV_OK;
}

void SSP_SWMasterDevice::ctx_t::start(void* _dst, const void*   _src, unsigned _len){
    this->dst = (u8*)_dst;
    this->src = (const u8*)_src;

    //finished.take(0);
    this->len =     _len;
    this->recv_len =(_dst != NULL)? _len : 0;
}

PTResult SSP_SWMasterDevice::ctx_t::wait(){
    if ((len|recv_len) <= 0)
        return ptOK;
    while (len > 0){
        PTResult ok = finished.take();
        if (ok == ptOK)
            return ptOK;
        if (PT_SCHEDULE(ok))
            return ok;
    }
    return ptNOK;
}

PTResult SSP_SWMasterDevice::wait_ctx(){
    TRACE_GUARD(ssp, ssp_wait);
    PTResult ok;
    if (ctx.len > 0){
        ok = ctx.wait();
        if (PT_SCHEDULE(ok) )
            return ok;
    }
    // если словлю ситуацию потери байта из последовательности,
    //  завершу операцию неудачей
    if (ctx.recv_len > 0)
    while (recv_ctx() && is_port_sending()){
        wait_fast();
    }
    return (ctx.recv_len <= 0)? ptOK: ptNOK;
}

PTResult SSP_SWMasterDevice::wait_ctx(unsigned to){
    // for fast request upload from FIFO
    if (ctx.recv_len > 0)
        recv_ctx();

    if ((ctx.len | ctx.recv_len) <= 0)
        return ptOK;
    if (to <= 0)
        return ptNOK;

    TRACE_GUARD(ssp, ssp_wait);

    if (!ostimeout_active(&finto)){
        ostimeout_init(&finto, to);
    }

    if (ctx.len > 0){
    while ((ctx.len > 0) && !ostimeout_expired(&finto)){
        PTResult ok = ctx.finished.take( ostimeout_least(&finto) );
        if (PT_SCHEDULE(ok))
            return ok;
        if (!ok)
            break;
    }
    if (ctx.len > 0)
        // невозможное тут - предыдущий цикл долженг дождаться ctx.len <= 0
        return ptNOK;

    }//if (ctx.len > 0)

    if (ctx.recv_len > 0)
    while ((recv_ctx() > 0) && !ostimeout_expired(&finto)) {
        wait_fast();
    }
    return (ctx.recv_len <= 0)? ptOK: ptNOK;
}

void SSP_SWMasterDevice::ctx_t::finish(){
    len = 0;
    finished.give();
}

void SSP_SWMasterDevice::ctx_t::finish_isr(){
    len = 0;
    finished.give_isr();
}

void SSP_SWMasterDevice::IRQ(){
    transfer_ctx();
    if (ctx.len == 0){
        SSP_ITConfig(io, SSP_IT_TX, DISABLE);
        ctx.finish_isr();
    }
    SSP_ClearITPendingBit(io, SSP_IT_TX);
}

u8 SSP_SWMasterDevice::pollingRead(u8 x){
    io->DR = x;
    //wait while sending active
    while (SSP_GetFlagStatus(io, SSP_FLAG_RNE) == 0){
        wait_fast();
    }
    return io->DR;
}

// post data to ssp fifo, and takes dst from it. until fifo full.
//      the rest will stored to ctx for IRQ processing.
// \return amount of posted data.
DevResult SSP_SWMasterDevice::post(void *dst, const void *data, size_t size){
    TRACE_GUARD(ssp, ssp_post);
    ctx.wait();
    //if (dst != NULL)
    {
        //завершу предыдущую транзакцию
        wait_port_empty();
        drop_port_rx();
    }
    ctx.start(dst, data, size);
    transfer_ctx();
    if (ctx.len > 0){
        SSP_ITConfig(io, SSP_IT_TX, ENABLE);
        return DEV_BUSY;
    }
    if (ctx.recv_len > 0)
        return DEV_BUSY;
    else {
        if ((msg->mode & msCS_HOLD) == 0)
        {
            // транзакция не удерживает линию связи, поэтому освобожу ее
            wait_port_empty();
            cs_release();
        }
        return DEV_OK;
    }
}


void SSP_SWMasterDevice::drop_port_rx(){
    while (SSP_GetFlagStatus(io, SSP_FLAG_RNE) != 0){
				unsigned x = io->DR;
        (void) (x);
    }
}

int SSP_SWMasterDevice::transfer_ctx(){
    TRACE_GUARD(ssptransfer, ssp_tx);

    if (ctx.dst == NULL){
        return send_ctx();
    }

    for (
        ; send_ctx_free() && (ctx.len > 0)
        ; ctx.len--
        )
    {
        if (ctx.src != 0)
            io->DR = *ctx.src++;
        else
            io->DR = ctx.src_fill;

        //if (ctx.dst != 0)
        if (SSP_GetFlagStatus(io, SSP_FLAG_RNE) != 0)
        {
            *ctx.dst++ = io->DR;
            ctx.recv_len--;
        }
    }
    return ctx.len;
}

int SSP_SWMasterDevice::send_ctx(){
    // send data
    for (
        ; (SSP_GetFlagStatus(io, SSP_FLAG_TNF) != 0) && (ctx.len > 0)
        ; ctx.len--
        )
    {
        io->DR = *ctx.src++;
        (void)io->DR;
    }
    return ctx.len;
}

//
bool SSP_SWMasterDevice::send_ctx_free(){
    unsigned st = io->SR & (SSP_FLAG_TNF | SSP_FLAG_RFF);
    if (ctx.dst == 0)
        st &= ~SSP_FLAG_RFF;
    return st == SSP_FLAG_TNF;
    /*
    if (SSP_GetFlagStatus(io, SSP_FLAG_TNF) == 0)
        return false;
    if (ctx.dst == 0)
        return true;
    return (SSP_GetFlagStatus(io, SSP_FLAG_RFF) == 0);
    */
}


int SSP_SWMasterDevice::recv_ctx(){
    // load received data
    if (ctx.recv_len > 0)
    for (
        ; (ctx.recv_len > 0) && (SSP_GetFlagStatus(io, SSP_FLAG_RNE) != 0)
        ; ctx.recv_len--
        )
    {
        *ctx.dst++ = io->DR;
    }
    return ctx.recv_len;
}


DevResult SSP_SWMasterDevice::setup_mode(unsigned mode){
    const unsigned mode_mask = msCPOL | msCPHA | msENDIAN;
    if ( (ssp_mode& mode_mask) == (mode & mode_mask))
        return DEV_OK;

    uint32_t    tmpreg = 0;
    /* SSPx CR0 Configuration */
    bool is_clkhi = ((mode & msCPOL) == msCLKHI );
    if (is_clkhi)
        tmpreg |= SSP_SPO_High;
    else
        tmpreg |= SSP_SPO_Low;

    bool is_clkfall = ((mode & msCPHA) == msCLKFALL);
    if ( is_clkfall == is_clkhi )
        tmpreg |= SSP_SPH_1Edge;
    else
        tmpreg |= SSP_SPH_2Edge;

    if ((mode & msENDIAN) != msMSB_FIRST)
        return errMODE_NOT_SUPP;

    ASSERT_SSP(io != NULL);
    unsigned tmpcr = io->CR0;
    tmpcr &= ~(SSP_CR0_SPO | SSP_CR0_SPH);
    tmpreg |= tmpcr;

    unsigned NB = CSL_FEXT(mode, msNB);
    if (NB != 0){
        NB = NB-1;
        if ((NB < SSP_CR0_DSS_4_BITS) || (NB > SSP_CR0_DSS_16_BITS))
            return errMODE_NOT_SUPP;
        CSL_FINS(tmpreg, SSP_CR0_DSS, NB);
    }

    // изменение режима порта означает что мы не можем пользоваться ФИФО, и должны
    //  закрыть текущую операцию
    if (tmpreg != io->CR0){
        ctx.wait();
        SSP_Cmd(io, DISABLE);
        io->CR0 = tmpreg;
        SSP_Cmd(io, ENABLE);
    }

    return DEV_OK;
}

DevResult SSP_SWMasterDevice::setup_byte(){
    ASSERT_SSP(io != NULL);
    uint32_t    tmpreg;
    unsigned tmpcr = io->CR0;
    CSL_FINS(tmpreg, SSP_CR0_DSS, SSP_CR0_DSS_8_BITS);

    // изменение режима порта означает что мы не можем пользоваться ФИФО, и должны
    //  закрыть текущую операцию
    if (tmpreg != io->CR0)
        ctx.wait();

    io->CR0 = tmpreg;
    return DEV_OK;
}

DevResult SSP_SWMasterDevice::setup_port(const Message* msg){
    ASSERT_SSP(io != NULL);

    DevResult ok = setup_mode(msg->mode);
    if (ok != DEV_OK)
        return ok;

    unsigned cs = CSL_FEXT(msg->mode, msCS);
    if (cs == CSL_FEXT(msCS_KEEP, msCS))
        cs =  CSL_FEXT(ssp_mode, msCS);
    int selected = css.selected();
    if (selected != cs)
    {
        if (selected != css.selNONE){
            // переключение линии требует завершения текущей операции на
            //  текущей линии
            ctx.wait();
            wait_port_empty();
        }
        ok = css.select(cs);
    }
    ssp_mode = msg->mode;
    if (msg->freq != speedKEEP){
        setup_freq(msg->freq);
    }
    return ok;
}


DevResult SSP_SWMasterDevice::cs_select(unsigned csid){
    CSL_FINS(ssp_mode, msCS, csid);
    return css.select(csid);
}

DevResult SSP_SWMasterDevice::cs_select(){
    return css.select( CSL_FEXT(ssp_mode, msCS) );
}

int SSP_SWMasterDevice::setup_freq(unsigned fhz){
    if (fhz == speedKEEP)
        return fsck_hz;
    if (fsck_hz == fhz)
        return fsck_hz;
    unsigned scr = 0;
    unsigned pscr = 2;
    unsigned fssp = SystemCoreClock/2;
    if (fssp > fhz)
        scr = ((fssp+fhz-1)/fhz)-1;
    if (scr > 0xff){
        pscr = ((scr >> 8) + 3) & ~1;
        scr = scr / pscr;
    }
    io->CPSR = pscr;
    CSL_FINS(io->CR0, SSP_CR0_SCR, scr);
    fsck_hz = fhz;
    return fsck_hz;
}




//  chip specific perifery
#ifdef MDR1986VE94T
const GPIOFUNC_INIT SSP1B_GPIO = {
     struct_field(GPIO_PORT) MDR_PORTB
   , struct_field(GPIO_PINS) (PORT_Pin_13 | PORT_Pin_14 | PORT_Pin_15)
   , struct_field(GPIO_FUNC) PORT_FUNC_ALTER
};

const GPIOFUNC_INIT SSP2B_GPIO = {
     struct_field(GPIO_PORT) MDR_PORTB
   , struct_field(GPIO_PINS) (PORT_Pin_13 | PORT_Pin_14 | PORT_Pin_15)
   , struct_field(GPIO_FUNC) PORT_FUNC_OVERRID
};

const GPIOFUNC_INIT SSP2C_GPIO = {
     struct_field(GPIO_PORT) MDR_PORTC
   , struct_field(GPIO_PINS) (PORT_Pin_1 | PORT_Pin_2 | PORT_Pin_3)
   , struct_field(GPIO_FUNC) PORT_FUNC_OVERRID
};

const GPIOFUNC_INIT SSP2D_GPIO = {
     struct_field(GPIO_PORT) MDR_PORTD
   , struct_field(GPIO_PINS) (PORT_Pin_2 | PORT_Pin_5 | PORT_Pin_6)
   , struct_field(GPIO_FUNC) PORT_FUNC_ALTER
};

const GPIOFUNC_INIT SSP1D_GPIO = {
     struct_field(GPIO_PORT) MDR_PORTD
   , struct_field(GPIO_PINS) (PORT_Pin_10 | PORT_Pin_11 | PORT_Pin_12)
   , struct_field(GPIO_FUNC) PORT_FUNC_OVERRID
};

const GPIOFUNC_INIT SSP1F_GPIO = {
     struct_field(GPIO_PORT) MDR_PORTF
   , struct_field(GPIO_PINS) (PORT_Pin_0 | PORT_Pin_1 | PORT_Pin_3)
   , struct_field(GPIO_FUNC) PORT_FUNC_ALTER
};


const GPIOFUNC_INIT SSP2F_GPIO = {
     struct_field(GPIO_PORT) MDR_PORTF
   , struct_field(GPIO_PINS) (PORT_Pin_13 | PORT_Pin_14 | PORT_Pin_15)
   , struct_field(GPIO_FUNC) PORT_FUNC_OVERRID
};
#endif
