/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <errno.h>
#include <string.h>

#include <gpio.h>
#include <spi.h>
#include <spi_priv.h>
#include <util.h>
#include <atomicBitset.h>
#include <atomic.h>
#include <platform.h>

#include <plat/cmsis.h>
#include <plat/dma.h>
#include <plat/gpio.h>
#include <plat/pwr.h>
#include <plat/exti.h>
#include <plat/syscfg.h>
#include <plat/spi.h>
#include <plat/plat.h>

#define SPI_CR1_CPHA                (1 << 0)
#define SPI_CR1_CPOL                (1 << 1)
#define SPI_CR1_MSTR                (1 << 2)

#define SPI_CR1_BR(x)               ((LOG2_CEIL(x) - 1) << 3)
#define SPI_CR1_BR_MIN              2
#define SPI_CR1_BR_MAX              256
#define SPI_CR1_BR_MASK             (0x7 << 3)

#define SPI_CR1_SPE                 (1 << 6)
#define SPI_CR1_LSBFIRST            (1 << 7)
#define SPI_CR1_SSI                 (1 << 8)
#define SPI_CR1_SSM                 (1 << 9)
#define SPI_CR1_RXONLY              (1 << 10)
#define SPI_CR1_DFF                 (1 << 11)
#define SPI_CR1_BIDIOE              (1 << 14)
#define SPI_CR1_BIDIMODE            (1 << 15)

#define SPI_CR2_TXEIE               (1 << 7)
#define SPI_CR2_RXNEIE              (1 << 6)
#define SPI_CR2_ERRIE               (1 << 5)
#define SPI_CR2_TXDMAEN             (1 << 1)
#define SPI_CR2_RXDMAEN             (1 << 0)
#define SPI_CR2_INT_MASK            (SPI_CR2_TXEIE | SPI_CR2_RXNEIE | SPI_CR2_ERRIE)

#define SPI_CR2_SSOE                (1 << 2)

#define SPI_SR_RXNE                 (1 << 0)
#define SPI_SR_TXE                  (1 << 1)
#define SPI_SR_BSY                  (1 << 7)

struct StmSpi {
    volatile uint32_t CR1;
    volatile uint32_t CR2;
    volatile uint32_t SR;
    volatile uint32_t DR;
    volatile uint32_t CRCPR;
    volatile uint32_t RXCRCR;
    volatile uint32_t TXCRCR;
    volatile uint32_t I2SCFGR;
    volatile uint32_t I2SPR;
};

struct StmSpiState {
    uint8_t bitsPerWord;
    uint8_t xferEnable;

    uint16_t rxWord;
    uint16_t txWord;

    bool rxDone;
    bool txDone;

    struct ChainedIsr isrNss;

    bool nssChange;
};

struct StmSpiCfg {
    struct StmSpi *regs;

    uint32_t clockBus;
    uint32_t clockUnit;

    IRQn_Type irq;

    uint8_t dmaBus;
};

struct StmSpiDev {
    struct SpiDevice *base;
    const struct StmSpiCfg *cfg;
    const struct StmSpiBoardCfg *board;
    struct StmSpiState state;

    struct Gpio *miso;
    struct Gpio *mosi;
    struct Gpio *sck;
    struct Gpio *nss;
};

static inline struct Gpio *stmSpiGpioInit(uint32_t gpioNum, enum StmGpioSpeed speed, enum StmGpioAltFunc func)
{
    struct Gpio *gpio = gpioRequest(gpioNum);

    if (gpio)
        gpioConfigAlt(gpio, speed, GPIO_PULL_NONE, GPIO_OUT_PUSH_PULL, func);

    return gpio;
}

static inline void stmSpiDataPullMode(struct StmSpiDev *pdev, enum StmGpioSpeed dataSpeed, enum GpioPullMode dataPull)
{
    gpioConfigAlt(pdev->miso, dataSpeed, dataPull, GPIO_OUT_PUSH_PULL, pdev->board->gpioFunc);
    gpioConfigAlt(pdev->mosi, dataSpeed, dataPull, GPIO_OUT_PUSH_PULL, pdev->board->gpioFunc);
}

static inline void stmSpiSckPullMode(struct StmSpiDev *pdev, enum StmGpioSpeed sckSpeed, enum GpioPullMode sckPull)
{
    gpioConfigAlt(pdev->sck, sckSpeed, sckPull, GPIO_OUT_PUSH_PULL, pdev->board->gpioFunc);
}

static inline void stmSpiStartDma(struct StmSpiDev *pdev,
        const struct StmSpiDmaCfg *dmaCfg, const void *buf, uint8_t bitsPerWord,
        bool minc, size_t size, DmaCallbackF callback, bool rx)
{
    struct StmSpi *regs = pdev->cfg->regs;
    struct dmaMode mode;

    memset(&mode, 0, sizeof(mode));

    if (bitsPerWord == 8) {
        mode.psize = DMA_SIZE_8_BITS;
        mode.msize = DMA_SIZE_8_BITS;
    } else {
        mode.psize = DMA_SIZE_16_BITS;
        mode.msize = DMA_SIZE_16_BITS;
    }
    mode.priority = DMA_PRIORITY_HIGH;
    mode.direction = rx ? DMA_DIRECTION_PERIPH_TO_MEM :
            DMA_DIRECTION_MEM_TO_PERIPH;
    mode.periphAddr = (uintptr_t)&regs->DR;
    mode.minc = minc;
    mode.channel = dmaCfg->channel;

    dmaStart(pdev->cfg->dmaBus, dmaCfg->stream, buf, size, &mode, callback,
            pdev);
}

static inline int stmSpiEnable(struct StmSpiDev *pdev,
        const struct SpiMode *mode, bool master)
{
    struct StmSpi *regs = pdev->cfg->regs;
    struct StmSpiState *state = &pdev->state;

    if (mode->bitsPerWord != 8 &&
            mode->bitsPerWord != 16)
        return -EINVAL;

    unsigned int div;
    if (master) {
        if (!mode->speed)
            return -EINVAL;

        uint32_t pclk = pwrGetBusSpeed(pdev->cfg->clockBus);
        div = pclk / mode->speed;
        if (div > SPI_CR1_BR_MAX)
            return -EINVAL;
        else if (div < SPI_CR1_BR_MIN)
            div = SPI_CR1_BR_MIN;
    }

    atomicWriteByte(&state->xferEnable, false);

    state->txWord = mode->txWord;
    state->bitsPerWord = mode->bitsPerWord;

    pwrUnitClock(pdev->cfg->clockBus, pdev->cfg->clockUnit, true);

    if (master) {
        regs->CR1 &= ~SPI_CR1_BR_MASK;
        regs->CR1 |= SPI_CR1_BR(div);
    }

    if (mode->cpol == SPI_CPOL_IDLE_LO)
        regs->CR1 &= ~SPI_CR1_CPOL;
    else
        regs->CR1 |= SPI_CR1_CPOL;

    if (mode->cpha == SPI_CPHA_LEADING_EDGE)
        regs->CR1 &= ~SPI_CR1_CPHA;
    else
        regs->CR1 |= SPI_CR1_CPHA;

    if (mode->bitsPerWord == 8)
        regs->CR1 &= ~SPI_CR1_DFF;
    else
        regs->CR1 |= SPI_CR1_DFF;

    if (mode->format == SPI_FORMAT_MSB_FIRST)
        regs->CR1 &= ~SPI_CR1_LSBFIRST;
    else
        regs->CR1 |= SPI_CR1_LSBFIRST;

    if (master)
        regs->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM | SPI_CR1_MSTR;
    else
        regs->CR1 &= ~(SPI_CR1_SSM | SPI_CR1_MSTR);

    return 0;
}

static int stmSpiMasterStartSync(struct SpiDevice *dev, spi_cs_t cs,
        const struct SpiMode *mode)
{
    struct StmSpiDev *pdev = dev->pdata;

    int err = stmSpiEnable(pdev, mode, true);
    if (err < 0)
        return err;

    stmSpiDataPullMode(pdev, pdev->board->gpioSpeed, pdev->board->gpioPull);
    stmSpiSckPullMode(pdev, pdev->board->gpioSpeed, mode->cpol ? GPIO_PULL_UP : GPIO_PULL_DOWN);

    if (!pdev->nss)
        pdev->nss = gpioRequest(cs);
    if (!pdev->nss)
        return -ENODEV;
    gpioConfigOutput(pdev->nss, pdev->board->gpioSpeed, pdev->board->gpioPull, GPIO_OUT_PUSH_PULL, 1);

    return 0;
}

static int stmSpiSlaveStartSync(struct SpiDevice *dev,
        const struct SpiMode *mode)
{
    struct StmSpiDev *pdev = dev->pdata;

    stmSpiDataPullMode(pdev, pdev->board->gpioSpeed, GPIO_PULL_NONE);
    stmSpiSckPullMode(pdev, pdev->board->gpioSpeed, GPIO_PULL_NONE);

    if (!pdev->nss)
        pdev->nss = stmSpiGpioInit(pdev->board->gpioNss, pdev->board->gpioSpeed, pdev->board->gpioFunc);
    if (!pdev->nss)
        return -ENODEV;

    return stmSpiEnable(pdev, mode, false);
}

static inline bool stmSpiIsMaster(struct StmSpiDev *pdev)
{
    struct StmSpi *regs = pdev->cfg->regs;
    return !!(regs->CR1 & SPI_CR1_MSTR);
}

static void stmSpiDone(struct StmSpiDev *pdev, int err)
{
    struct StmSpi *regs = pdev->cfg->regs;
    struct StmSpiState *state = &pdev->state;

    if (pdev->board->sleepDev >= 0)
        platReleaseDevInSleepMode(pdev->board->sleepDev);

    while (regs->SR & SPI_SR_BSY)
        ;

    if (stmSpiIsMaster(pdev)) {
        if (state->nssChange && pdev->nss)
            gpioSet(pdev->nss, 1);
        spiMasterRxTxDone(pdev->base, err);
    } else {
        regs->CR2 = SPI_CR2_TXEIE;
        spiSlaveRxTxDone(pdev->base, err);
    }
}

static void stmSpiRxDone(void *cookie, uint16_t bytesLeft, int err)
{
    struct StmSpiDev *pdev = cookie;
    struct StmSpi *regs = pdev->cfg->regs;
    struct StmSpiState *state = &pdev->state;

    regs->CR2 &= ~SPI_CR2_RXDMAEN;
    state->rxDone = true;

    if (state->txDone) {
        atomicWriteByte(&state->xferEnable, false);
        stmSpiDone(pdev, err);
    }
}

static void stmSpiTxDone(void *cookie, uint16_t bytesLeft, int err)
{
    struct StmSpiDev *pdev = cookie;
    struct StmSpi *regs = pdev->cfg->regs;
    struct StmSpiState *state = &pdev->state;

    regs->CR2 &= ~SPI_CR2_TXDMAEN;
    state->txDone = true;

    if (state->rxDone) {
        atomicWriteByte(&state->xferEnable, false);
        stmSpiDone(pdev, err);
    }
}

static int stmSpiRxTx(struct SpiDevice *dev, void *rxBuf, const void *txBuf,
        size_t size, const struct SpiMode *mode)
{
    struct StmSpiDev *pdev = dev->pdata;
    struct StmSpi *regs = pdev->cfg->regs;
    struct StmSpiState *state = &pdev->state;
    bool rxMinc = true, txMinc = true;
    uint32_t cr2 = SPI_CR2_TXDMAEN;

    if (atomicXchgByte(&state->xferEnable, true) == true)
        return -EBUSY;

    if (stmSpiIsMaster(pdev) && pdev->nss)
        gpioSet(pdev->nss, 0);

    state->rxDone = false;
    state->txDone = false;
    state->nssChange = mode->nssChange;

    /* In master mode, if RX is ignored at any point, then turning it on
     * later may cause the SPI/DMA controllers to "receive" a stale byte
     * sitting in a FIFO somewhere (even when their respective registers say
     * their FIFOs are empty, and even if the SPI FIFO is explicitly cleared).
     * Work around this by DMAing bytes we don't care about into a throwaway
     * 1-word buffer.
     *
     * In slave mode, this specific WAR sometimes causes bigger problems
     * (the first byte TXed is sometimes dropped or corrupted).  Slave mode
     * has its own WARs below.
     */
    if (!rxBuf && stmSpiIsMaster(pdev)) {
        rxBuf = &state->rxWord;
        rxMinc = false;
    }

    if (rxBuf) {
        stmSpiStartDma(pdev, &pdev->board->dmaRx, rxBuf, mode->bitsPerWord,
                rxMinc, size, stmSpiRxDone, true);
        cr2 |= SPI_CR2_RXDMAEN;
    } else {
        state->rxDone = true;
    }

    if (!txBuf) {
        txBuf = &state->txWord;
        txMinc = false;
    }
    stmSpiStartDma(pdev, &pdev->board->dmaTx, txBuf, mode->bitsPerWord, txMinc,
            size, stmSpiTxDone, false);

    /* Ensure the TXE and RXNE bits are cleared; otherwise the DMA controller
     * may "receive" the byte sitting in the SPI controller's FIFO right now,
     * or drop/corrupt the first TX byte.  Timing is crucial here, so do it
     * right before enabling DMA.
     */
    if (!stmSpiIsMaster(pdev)) {
        regs->CR2 &= ~SPI_CR2_TXEIE;
        NVIC_ClearPendingIRQ(pdev->cfg->irq);

        if (regs->SR & SPI_SR_RXNE)
            (void)regs->DR;

        if (regs->SR & SPI_SR_TXE)
            regs->DR = mode->txWord;
    }

    if (pdev->board->sleepDev >= 0)
        platRequestDevInSleepMode(pdev->board->sleepDev, 12);

    regs->CR2 = cr2;
    regs->CR1 |= SPI_CR1_SPE;


    return 0;
}

static int stmSpiSlaveIdle(struct SpiDevice *dev, const struct SpiMode *mode)
{
    struct StmSpiDev *pdev = dev->pdata;
    struct StmSpi *regs = pdev->cfg->regs;
    struct StmSpiState *state = &pdev->state;

    if (atomicXchgByte(&state->xferEnable, true) == true)
        return -EBUSY;

    regs->CR2 = SPI_CR2_TXEIE;
    regs->CR1 |= SPI_CR1_SPE;

    atomicXchgByte(&state->xferEnable, false);
    return 0;
}

static inline void stmSpiDisable(struct SpiDevice *dev, bool master)
{
    struct StmSpiDev *pdev = dev->pdata;
    struct StmSpi *regs = pdev->cfg->regs;

    while (regs->SR & SPI_SR_BSY)
        ;

    if (master) {
        stmSpiSckPullMode(pdev, pdev->board->gpioSpeed, pdev->board->gpioPull);
    }

    regs->CR2 &= ~(SPI_CR2_RXDMAEN | SPI_CR2_TXDMAEN | SPI_CR2_TXEIE);
    regs->CR1 &= ~SPI_CR1_SPE;
    pwrUnitClock(pdev->cfg->clockBus, pdev->cfg->clockUnit, false);
}

static int stmSpiMasterStopSync(struct SpiDevice *dev)
{
    struct StmSpiDev *pdev = dev->pdata;

    if (pdev->nss) {
        gpioSet(pdev->nss, 1);
        gpioRelease(pdev->nss);
    }

    stmSpiDisable(dev, true);
    pdev->nss = NULL;
    return 0;
}

static int stmSpiSlaveStopSync(struct SpiDevice *dev)
{
    struct StmSpiDev *pdev = dev->pdata;

    if (pdev->nss)
        gpioRelease(pdev->nss);

    stmSpiDisable(dev, false);
    pdev->nss = NULL;
    return 0;
}

static bool stmSpiExtiIsr(struct ChainedIsr *isr)
{
    struct StmSpiState *state = container_of(isr, struct StmSpiState, isrNss);
    struct StmSpiDev *pdev = container_of(state, struct StmSpiDev, state);

    if (pdev->nss && !extiIsPendingGpio(pdev->nss))
        return false;

    spiSlaveCsInactive(pdev->base);
    if (pdev->nss)
        extiClearPendingGpio(pdev->nss);
    return true;
}

static void stmSpiSlaveSetCsInterrupt(struct SpiDevice *dev, bool enabled)
{
    struct StmSpiDev *pdev = dev->pdata;
    struct ChainedIsr *isr = &pdev->state.isrNss;

    if (enabled) {
        isr->func = stmSpiExtiIsr;

        if (pdev->nss) {
            syscfgSetExtiPort(pdev->nss);
            extiEnableIntGpio(pdev->nss, EXTI_TRIGGER_RISING);
        }
        extiChainIsr(pdev->board->irqNss, isr);
    } else {
        extiUnchainIsr(pdev->board->irqNss, isr);
        if (pdev->nss)
            extiDisableIntGpio(pdev->nss);
    }
}

static bool stmSpiSlaveCsIsActive(struct SpiDevice *dev)
{
    struct StmSpiDev *pdev = dev->pdata;
    return pdev->nss && !gpioGet(pdev->nss);
}

static inline void stmSpiTxe(struct StmSpiDev *pdev)
{
    struct StmSpi *regs = pdev->cfg->regs;

    /**
     * n.b.: if nothing handles the TXE interrupt in slave mode, the SPI
     * controller will just keep reading the existing value from DR anytime it
     * needs data
     */
    regs->DR = pdev->state.txWord;
    regs->CR2 &= ~SPI_CR2_TXEIE;
}

static void stmSpiIsr(struct StmSpiDev *pdev)
{
    struct StmSpi *regs = pdev->cfg->regs;

    if (regs->SR & SPI_SR_TXE) {
        stmSpiTxe(pdev);
    }

    /* TODO: error conditions */
}

static int stmSpiRelease(struct SpiDevice *dev)
{
    struct StmSpiDev *pdev = dev->pdata;

    NVIC_DisableIRQ(pdev->cfg->irq);

    pdev->base = NULL;
    return 0;
}

#define DECLARE_IRQ_HANDLER(_n)             \
    void SPI##_n##_IRQHandler();            \
    void SPI##_n##_IRQHandler()             \
    {                                       \
        stmSpiIsr(&mStmSpiDevs[_n - 1]); \
    }

const struct SpiDevice_ops mStmSpiOps = {
    .masterStartSync = stmSpiMasterStartSync,
    .masterRxTx = stmSpiRxTx,
    .masterStopSync = stmSpiMasterStopSync,

    .slaveStartSync = stmSpiSlaveStartSync,
    .slaveIdle = stmSpiSlaveIdle,
    .slaveRxTx = stmSpiRxTx,
    .slaveStopSync = stmSpiSlaveStopSync,

    .slaveSetCsInterrupt = stmSpiSlaveSetCsInterrupt,
    .slaveCsIsActive = stmSpiSlaveCsIsActive,

    .release = stmSpiRelease,
};

static const struct StmSpiCfg mStmSpiCfgs[] = {
    [0] = {
        .regs = (struct StmSpi *)SPI1_BASE,

        .clockBus = PERIPH_BUS_APB2,
        .clockUnit = PERIPH_APB2_SPI1,

        .irq = SPI1_IRQn,

        .dmaBus = SPI1_DMA_BUS,
    },
    [1] = {
        .regs = (struct StmSpi *)SPI2_BASE,

        .clockBus = PERIPH_BUS_APB1,
        .clockUnit = PERIPH_APB1_SPI2,

        .irq = SPI2_IRQn,

        .dmaBus = SPI2_DMA_BUS,
    },
    [2] = {
        .regs = (struct StmSpi *)SPI3_BASE,

        .clockBus = PERIPH_BUS_APB1,
        .clockUnit = PERIPH_APB1_SPI3,

        .irq = SPI3_IRQn,

        .dmaBus = SPI3_DMA_BUS,
    },
};

static struct StmSpiDev mStmSpiDevs[ARRAY_SIZE(mStmSpiCfgs)];
DECLARE_IRQ_HANDLER(1)
DECLARE_IRQ_HANDLER(2)
DECLARE_IRQ_HANDLER(3)

static void stmSpiInit(struct StmSpiDev *pdev, const struct StmSpiCfg *cfg,
        const struct StmSpiBoardCfg *board, struct SpiDevice *dev)
{
    pdev->miso = stmSpiGpioInit(board->gpioMiso, board->gpioSpeed, board->gpioFunc);
    pdev->mosi = stmSpiGpioInit(board->gpioMosi, board->gpioSpeed, board->gpioFunc);
    pdev->sck = stmSpiGpioInit(board->gpioSclk, board->gpioSpeed, board->gpioFunc);

    NVIC_EnableIRQ(cfg->irq);

    pdev->base = dev;
    pdev->cfg = cfg;
    pdev->board = board;
}

int spiRequest(struct SpiDevice *dev, uint8_t busId)
{
    if (busId >= ARRAY_SIZE(mStmSpiDevs))
        return -ENODEV;

    const struct StmSpiBoardCfg *board = boardStmSpiCfg(busId);
    if (!board)
        return -ENODEV;

    struct StmSpiDev *pdev = &mStmSpiDevs[busId];
    const struct StmSpiCfg *cfg = &mStmSpiCfgs[busId];
    if (!pdev->base)
        stmSpiInit(pdev, cfg, board, dev);

    memset(&pdev->state, 0, sizeof(pdev->state));
    dev->ops = &mStmSpiOps;
    dev->pdata = pdev;
    return 0;
}

const enum IRQn spiRxIrq(uint8_t busId)
{
    if (busId >= ARRAY_SIZE(mStmSpiDevs))
        return -ENODEV;

    struct StmSpiDev *pdev = &mStmSpiDevs[busId];

    return dmaIrq(pdev->cfg->dmaBus, pdev->board->dmaRx.stream);
}

const enum IRQn spiTxIrq(uint8_t busId)
{
    if (busId >= ARRAY_SIZE(mStmSpiDevs))
        return -ENODEV;

    struct StmSpiDev *pdev = &mStmSpiDevs[busId];

    return dmaIrq(pdev->cfg->dmaBus, pdev->board->dmaTx.stream);
}