/*
 * 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 <stdint.h>
#include <string.h>

#include <gpio.h>
#include <i2c.h>
#include <seos.h>
#include <util.h>
#include <gpio.h>
#include <atomicBitset.h>
#include <atomic.h>
#include <platform.h>

#include <plat/inc/cmsis.h>
#include <plat/inc/dma.h>
#include <plat/inc/gpio.h>
#include <plat/inc/i2c.h>
#include <plat/inc/pwr.h>
#include <plat/inc/plat.h>

#include <cpu/inc/barrier.h>

#define I2C_VERBOSE_DEBUG       0
#define I2C_MAX_QUEUE_DEPTH     5

#if I2C_VERBOSE_DEBUG
#define i2c_log_debug(x) osLog(LOG_DEBUG, x "\n")
#else
#define i2c_log_debug(x) do {} while(0)
#endif

#define I2C_CR1_PE          (1 << 0)
#define I2C_CR1_SMBUS       (1 << 1)
#define I2C_CR1_SMBTYPE     (1 << 3)
#define I2C_CR1_ENARP       (1 << 4)
#define I2C_CR1_ENPEC       (1 << 5)
#define I2C_CR1_ENGC        (1 << 6)
#define I2C_CR1_NOSTRETCH   (1 << 7)
#define I2C_CR1_START       (1 << 8)
#define I2C_CR1_STOP        (1 << 9)
#define I2C_CR1_ACK         (1 << 10)
#define I2C_CR1_POS         (1 << 11)
#define I2C_CR1_PEC         (1 << 12)
#define I2C_CR1_ALERT       (1 << 13)
#define I2C_CR1_SWRST       (1 << 15)

#define I2C_CR2_FREQ(x)     ((x) & I2C_CR2_FREQ_MASK)
#define I2C_CR2_FREQ_MASK   0x3F
#define I2C_CR2_ITERREN     (1 << 8)
#define I2C_CR2_ITEVTEN     (1 << 9)
#define I2C_CR2_ITBUFEN     (1 << 10)
#define I2C_CR2_DMAEN       (1 << 11)
#define I2C_CR2_LAST        (1 << 12)

#define I2C_OAR1_ADD7(x)    (((x) & I2C_OAR1_ADD7_MASK) << 1)
#define I2C_OAR1_ADD7_MASK  0x7F
#define I2C_OAR1_ADD10(x)   ((x) & I2C_OAR1_ADD10_MASK)
#define I2C_OAR1_ADD10_MASK 0x3FF
#define I2C_OAR1_ADDMODE    (1 << 15)

#define I2C_SR1_SB          (1 << 0)
#define I2C_SR1_ADDR        (1 << 1)
#define I2C_SR1_BTF         (1 << 2)
#define I2C_SR1_ADD10       (1 << 3)
#define I2C_SR1_STOPF       (1 << 4)
#define I2C_SR1_RXNE        (1 << 6)
#define I2C_SR1_TXE         (1 << 7)
#define I2C_SR1_BERR        (1 << 8)
#define I2C_SR1_ARLO        (1 << 9)
#define I2C_SR1_AF          (1 << 10)
#define I2C_SR1_OVR         (1 << 11)
#define I2C_SR1_PECERR      (1 << 12)
#define I2C_SR1_TIMEOUT     (1 << 14)
#define I2C_SR1_SMBALERT    (1 << 15)

#define I2C_SR2_MSL         (1 << 0)
#define I2C_SR2_BUSY        (1 << 1)
#define I2C_SR2_TRA         (1 << 2)
#define I2C_SR2_GENCALL     (1 << 4)
#define I2C_SR2_SMBDEFAULT  (1 << 5)
#define I2C_SR2_SMBHOST     (1 << 6)
#define I2C_SR2_DUALF       (1 << 7)

#define I2C_CCR(x)          ((x) & I2C_CCR_MASK)
#define I2C_CCR_MASK        0xFFF
#define I2C_CCR_DUTY_16_9   (1 << 14)
#define I2C_CCR_FM          (1 << 15)

#define I2C_TRISE(x)        ((x) & I2C_TRISE_MASK)
#define I2C_TRISE_MASK      0x3F

struct StmI2c {
    volatile uint32_t CR1;
    volatile uint32_t CR2;
    volatile uint32_t OAR1;
    volatile uint32_t OAR2;
    volatile uint32_t DR;
    volatile uint32_t SR1;
    volatile uint32_t SR2;
    volatile uint32_t CCR;
    volatile uint32_t TRISE;
    volatile uint32_t FLTR;
};

enum StmI2cSpiMasterState
{
    STM_I2C_MASTER_IDLE,
    STM_I2C_MASTER_START,
    STM_I2C_MASTER_TX_ADDR,
    STM_I2C_MASTER_TX_DATA,
    STM_I2C_MASTER_RX_ADDR,
    STM_I2C_MASTER_RX_DATA,
};

struct I2cStmState {
    struct {
        union {
            uint8_t *buf;
            const uint8_t *cbuf;
            uint8_t byte;
        };
        size_t size;
        size_t offset;
        bool preamble;

        I2cCallbackF callback;
        void *cookie;
    } rx, tx;

    enum {
        STM_I2C_DISABLED,
        STM_I2C_SLAVE,
        STM_I2C_MASTER,
    } mode;

    enum {
        STM_I2C_SLAVE_IDLE,
        STM_I2C_SLAVE_RX_ARMED,
        STM_I2C_SLAVE_RX,
        STM_I2C_SLAVE_TX_ARMED,
        STM_I2C_SLAVE_TX,
    } slaveState;

    // StmI2cSpiMasterState
    uint8_t masterState;
    uint16_t tid;
};

struct StmI2cCfg {
    struct StmI2c *regs;

    uint32_t clock;

    IRQn_Type irqEv;
    IRQn_Type irqEr;
};

struct StmI2cDev {
    const struct StmI2cCfg *cfg;
    const struct StmI2cBoardCfg *board;
    struct I2cStmState state;

    uint32_t next;
    uint32_t last;

    struct Gpio *scl;
    struct Gpio *sda;

    uint8_t addr;
};

static const struct StmI2cCfg mStmI2cCfgs[] = {
    [0] = {
        .regs = (struct StmI2c *)I2C1_BASE,

        .clock = PERIPH_APB1_I2C1,

        .irqEv = I2C1_EV_IRQn,
        .irqEr = I2C1_ER_IRQn,
    },
    [1] = {
        .regs = (struct StmI2c *)I2C2_BASE,

        .clock = PERIPH_APB1_I2C2,

        .irqEv = I2C2_EV_IRQn,
        .irqEr = I2C2_ER_IRQn,
    },
    [2] = {
        .regs = (struct StmI2c *)I2C3_BASE,

        .clock = PERIPH_APB1_I2C3,

        .irqEv = I2C3_EV_IRQn,
        .irqEr = I2C3_ER_IRQn,
    },
};

static struct StmI2cDev mStmI2cDevs[ARRAY_SIZE(mStmI2cCfgs)];

struct StmI2cXfer
{
    uint32_t        id;
    const void     *txBuf;
    size_t          txSize;
    void           *rxBuf;
    size_t          rxSize;
    I2cCallbackF    callback;
    void           *cookie;
    uint8_t         busId; /* for us these are both fine in a uint 8 */
    uint8_t         addr;
};

ATOMIC_BITSET_DECL(mXfersValid, I2C_MAX_QUEUE_DEPTH, static);
static struct StmI2cXfer mXfers[I2C_MAX_QUEUE_DEPTH] = { };

static inline struct StmI2cXfer *stmI2cGetXfer(void)
{
    int32_t idx = atomicBitsetFindClearAndSet(mXfersValid);

    if (idx < 0)
        return NULL;
    else
        return mXfers + idx;
}

static inline void stmI2cPutXfer(struct StmI2cXfer *xfer)
{
    if (xfer)
        atomicBitsetClearBit(mXfersValid, xfer - mXfers);
}

static inline void stmI2cAckEnable(struct StmI2cDev *pdev)
{
    pdev->cfg->regs->CR1 |= I2C_CR1_ACK;
}

static inline void stmI2cAckDisable(struct StmI2cDev *pdev)
{
    pdev->cfg->regs->CR1 &= ~I2C_CR1_ACK;
}

static inline void stmI2cDmaEnable(struct StmI2cDev *pdev)
{
    pdev->cfg->regs->CR2 |= I2C_CR2_DMAEN;
}

static inline void stmI2cDmaDisable(struct StmI2cDev *pdev)
{
    pdev->cfg->regs->CR2 &= ~I2C_CR2_DMAEN;
}

static inline void stmI2cStopEnable(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;

    while (regs->CR1 & (I2C_CR1_STOP | I2C_CR1_START))
        ;
    regs->CR1 |= I2C_CR1_STOP;
}

static inline void stmI2cStartEnable(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;

    while (regs->CR1 & (I2C_CR1_STOP | I2C_CR1_START))
        ;
    regs->CR1 |= I2C_CR1_START;
}

static inline void stmI2cIrqEnable(struct StmI2cDev *pdev,
        uint32_t mask)
{
    pdev->cfg->regs->CR2 |= mask;
}

static inline void stmI2cIrqDisable(struct StmI2cDev *pdev,
        uint32_t mask)
{
    pdev->cfg->regs->CR2 &= ~mask;
}

static inline void stmI2cEnable(struct StmI2cDev *pdev)
{
    pdev->cfg->regs->CR1 |= I2C_CR1_PE;
}

static inline void stmI2cDisable(struct StmI2cDev *pdev)
{
    pdev->cfg->regs->CR1 &= ~I2C_CR1_PE;
}

static inline void stmI2cSpeedSet(struct StmI2cDev *pdev,
        const uint32_t speed)
{
    struct StmI2c *regs = pdev->cfg->regs;
    int ccr, ccr_1, ccr_2;
    int apb1_clk;

    apb1_clk = pwrGetBusSpeed(PERIPH_BUS_APB1);

    regs->CR2 = (regs->CR2 & ~I2C_CR2_FREQ_MASK) |
                 I2C_CR2_FREQ(apb1_clk / 1000000);

    if (speed <= 100000) {
        ccr = apb1_clk / (speed * 2);
        if (ccr < 4)
            ccr = 4;
        regs->CCR = I2C_CCR(ccr);

        regs->TRISE = I2C_TRISE((apb1_clk / 1000000) + 1);
    } else if (speed <= 400000) {
        ccr_1 = apb1_clk / (speed * 3);
        if (ccr_1 == 0 || apb1_clk / (ccr_1 * 3) > speed)
            ccr_1 ++;
        ccr_2 = apb1_clk / (speed * 25);
        if (ccr_2 == 0 || apb1_clk / (ccr_2 * 25) > speed)
            ccr_2 ++;

        if ((apb1_clk / (ccr_1 * 3)) > (apb1_clk / (ccr_2 * 25)))
            regs->CCR = I2C_CCR_FM | I2C_CCR(ccr_1);
        else
            regs->CCR = I2C_CCR_FM | I2C_CCR_DUTY_16_9 | I2C_CCR(ccr_2);

        regs->TRISE = I2C_TRISE(((3*apb1_clk)/10000000) + 1);
    }
}

static inline void stmI2cSlaveIdle(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;

    state->slaveState = STM_I2C_SLAVE_RX_ARMED;
    stmI2cAckEnable(pdev);
    stmI2cIrqDisable(pdev, I2C_CR2_ITBUFEN | I2C_CR2_ITERREN);
}

static inline void stmI2cInvokeRxCallback(struct I2cStmState *state, size_t tx, size_t rx, int err)
{
    uint16_t oldTid = osSetCurrentTid(state->tid);
    state->rx.callback(state->rx.cookie, tx, rx, err);
    osSetCurrentTid(oldTid);
}

static inline void stmI2cInvokeTxCallback(struct I2cStmState *state, size_t tx, size_t rx, int err)
{
    uint16_t oldTid = osSetCurrentTid(state->tid);
    state->tx.callback(state->tx.cookie, tx, rx, err);
    osSetCurrentTid(oldTid);
}

static inline void stmI2cSlaveRxDone(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    size_t rxOffst = state->rx.offset;

    state->rx.offset = 0;
    stmI2cInvokeRxCallback(state, 0, rxOffst, 0);
}

static inline void stmI2cSlaveTxDone(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    size_t txOffst = state->tx.offset;

    stmI2cSlaveIdle(pdev);
    stmI2cInvokeTxCallback(state, txOffst, 0, 0);
}

static void stmI2cSlaveTxNextByte(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;

    if (state->tx.preamble) {
        regs->DR = state->tx.byte;
        state->tx.offset++;
    } else if (state->tx.offset < state->tx.size) {
        regs->DR = state->tx.cbuf[state->tx.offset];
        state->tx.offset++;
    } else {
        state->slaveState = STM_I2C_SLAVE_TX_ARMED;
        stmI2cIrqDisable(pdev, I2C_CR2_ITBUFEN);
        stmI2cInvokeTxCallback(state, state->tx.offset, 0, 0);
    }
}

static void stmI2cSlaveAddrMatched(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;

    i2c_log_debug("addr");

    if (state->slaveState == STM_I2C_SLAVE_RX_ARMED) {
        state->slaveState = STM_I2C_SLAVE_RX;
        stmI2cIrqEnable(pdev, I2C_CR2_ITBUFEN | I2C_CR2_ITERREN);
    } else if (state->slaveState == STM_I2C_SLAVE_TX) {
        stmI2cIrqEnable(pdev, I2C_CR2_ITBUFEN | I2C_CR2_ITERREN);
    }
    /* clear ADDR by doing a dummy reads from SR1 (already read) then SR2 */
    (void)regs->SR2;
}

static void stmI2cSlaveStopRxed(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;

    i2c_log_debug("stopf");

    (void)regs->SR1;
    stmI2cEnable(pdev);
    /* clear STOPF by doing a dummy read from SR1 and strobing the PE bit */

    stmI2cSlaveIdle(pdev);
    stmI2cSlaveRxDone(pdev);
}

static inline void stmI2cSlaveRxBufNotEmpty(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;
    uint8_t data = regs->DR;

    i2c_log_debug("rxne");

    if (state->rx.offset < state->rx.size) {
        state->rx.buf[state->rx.offset] = data;
        state->rx.offset++;
    } else {
        stmI2cAckDisable(pdev);
        /* TODO: error on overflow */
    }
}

static void stmI2cSlaveTxBufEmpty(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;

    i2c_log_debug("txe");

    if (state->slaveState == STM_I2C_SLAVE_RX) {
        state->slaveState = STM_I2C_SLAVE_TX_ARMED;
        stmI2cIrqDisable(pdev, I2C_CR2_ITBUFEN);
        stmI2cAckDisable(pdev);
        stmI2cSlaveRxDone(pdev);
        /* stmI2cTxNextByte() will happen when the task provides a
           TX buffer; the I2C controller will stretch the clock until then */
    } else {
        stmI2cSlaveTxNextByte(pdev);
    }
}

static void stmI2cSlaveNakRxed(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;

    i2c_log_debug("af");

    if (state->slaveState == STM_I2C_SLAVE_TX) {
        state->tx.offset--;
        /* NACKs seem to be preceded by a spurious TXNE, so adjust the offset to
           compensate (the corresponding byte written to DR was never actually
           transmitted) */
        stmI2cSlaveTxDone(pdev);
    }
    regs->SR1 &= ~I2C_SR1_AF;
}

static inline void stmI2cMasterTxRxDone(struct StmI2cDev *pdev, int err)
{
    struct I2cStmState *state = &pdev->state;
    size_t txOffst = state->tx.offset;
    size_t rxOffst = state->rx.offset;
    uint32_t id;
    int i;
    struct StmI2cXfer *xfer;

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

    state->tx.offset = 0;
    state->rx.offset = 0;
    stmI2cInvokeTxCallback(state, txOffst, rxOffst, err);

    do {
        id = atomicAdd32bits(&pdev->next, 1);
    } while (!id);

    for (i=0; i<I2C_MAX_QUEUE_DEPTH; i++) {
        xfer = &mXfers[i];

        if (xfer->busId == (pdev - mStmI2cDevs) &&
                atomicCmpXchg32bits(&xfer->id, id, 0)) {
            pdev->addr = xfer->addr;
            state->tx.cbuf = xfer->txBuf;
            state->tx.offset = 0;
            state->tx.size = xfer->txSize;
            state->tx.callback = xfer->callback;
            state->tx.cookie = xfer->cookie;
            state->rx.buf = xfer->rxBuf;
            state->rx.offset = 0;
            state->rx.size = xfer->rxSize;
            state->rx.callback = NULL;
            state->rx.cookie = NULL;
            atomicWriteByte(&state->masterState, STM_I2C_MASTER_START);
            if (pdev->board->sleepDev >= 0)
                platRequestDevInSleepMode(pdev->board->sleepDev, 12);
            stmI2cPutXfer(xfer);
            stmI2cStartEnable(pdev);
            return;
        }
    }

    atomicWriteByte(&state->masterState, STM_I2C_MASTER_IDLE);
}

static void stmI2cMasterDmaTxDone(void *cookie, uint16_t bytesLeft, int err)
{
    struct StmI2cDev *pdev = cookie;
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;

    state->tx.offset = state->tx.size - bytesLeft;
    state->tx.size = 0;
    stmI2cDmaDisable(pdev);
    if (err == 0 && state->rx.size > 0) {
        atomicWriteByte(&state->masterState, STM_I2C_MASTER_START);
        stmI2cStartEnable(pdev);
    } else {
        while (!(regs->SR1 & I2C_SR1_BTF))
            ;

        stmI2cStopEnable(pdev);
        stmI2cMasterTxRxDone(pdev, err);
    }
}

static void stmI2cMasterDmaRxDone(void *cookie, uint16_t bytesLeft, int err)
{
    struct StmI2cDev *pdev = cookie;
    struct I2cStmState *state = &pdev->state;

    state->rx.offset = state->rx.size - bytesLeft;
    state->rx.size = 0;

    stmI2cDmaDisable(pdev);
    stmI2cStopEnable(pdev);
    stmI2cMasterTxRxDone(pdev, err);
}

static inline void stmI2cMasterDmaCancel(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;

    dmaStop(I2C_DMA_BUS, pdev->board->dmaRx.stream);
    state->rx.offset = state->rx.size - dmaBytesLeft(I2C_DMA_BUS,
            pdev->board->dmaRx.stream);
    dmaStop(I2C_DMA_BUS, pdev->board->dmaTx.stream);
    state->tx.offset = state->tx.size - dmaBytesLeft(I2C_DMA_BUS,
            pdev->board->dmaTx.stream);

    stmI2cDmaDisable(pdev);
}

static inline void stmI2cMasterStartDma(struct StmI2cDev *pdev,
        const struct StmI2cDmaCfg *dmaCfg, const void *buf,
        size_t size, DmaCallbackF callback, bool rx, bool last)
{
    struct StmI2c *regs = pdev->cfg->regs;
    struct dmaMode mode;

    memset(&mode, 0, sizeof(mode));
    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 = true;
    mode.channel = dmaCfg->channel;

    dmaStart(I2C_DMA_BUS, dmaCfg->stream, buf, size, &mode, callback, pdev);
    if (last)
        stmI2cIrqEnable(pdev, I2C_CR2_LAST);
    else
        stmI2cIrqDisable(pdev, I2C_CR2_LAST);
    stmI2cDmaEnable(pdev);
}

static void stmI2cMasterSentStart(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;

    if (atomicReadByte(&state->masterState) == STM_I2C_MASTER_START) {
        if (state->tx.size > 0) {
            atomicWriteByte(&state->masterState, STM_I2C_MASTER_TX_ADDR);
            regs->DR = pdev->addr << 1;
        } else {
            atomicWriteByte(&state->masterState, STM_I2C_MASTER_RX_ADDR);
            stmI2cAckEnable(pdev);
            regs->DR = (pdev->addr << 1) | 0x01;
        }
    }
}

static void stmI2cMasterSentAddr(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;
    uint8_t masterState = atomicReadByte(&state->masterState);

    if (masterState == STM_I2C_MASTER_TX_ADDR) {
        stmI2cMasterStartDma(pdev, &pdev->board->dmaTx, state->tx.cbuf,
                state->tx.size, stmI2cMasterDmaTxDone, false, !!state->rx.size);
        regs->SR2; // Clear ADDR
        atomicWriteByte(&state->masterState, STM_I2C_MASTER_TX_DATA);
    } else if (masterState == STM_I2C_MASTER_RX_ADDR) {
        if (state->rx.size == 1) // Generate NACK here for 1 byte transfers
            stmI2cAckDisable(pdev);

        stmI2cMasterStartDma(pdev, &pdev->board->dmaRx, state->rx.buf,
                state->rx.size, stmI2cMasterDmaRxDone, true,
                state->rx.size > 1);
        regs->SR2; // Clear ADDR
        atomicWriteByte(&state->masterState, STM_I2C_MASTER_RX_DATA);
    }
}

static void stmI2cMasterNakRxed(struct StmI2cDev *pdev)
{
    struct I2cStmState *state = &pdev->state;
    struct StmI2c *regs = pdev->cfg->regs;
    uint8_t masterState = atomicReadByte(&state->masterState);
    int err = 0;

    if (masterState == STM_I2C_MASTER_TX_ADDR ||
            masterState == STM_I2C_MASTER_RX_ADDR) {
        err = -ENXIO;
    } else if (masterState == STM_I2C_MASTER_TX_DATA ||
            masterState == STM_I2C_MASTER_RX_DATA) {
        stmI2cMasterDmaCancel(pdev);
        err = -EIO;
    }

    if (err) {
        regs->SR1 &= ~I2C_SR1_AF;
        stmI2cStopEnable(pdev);
        stmI2cMasterTxRxDone(pdev, err);
    }
}

static void stmI2cMasterBusError(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;

    stmI2cMasterDmaCancel(pdev);
    regs->SR1 &= ~I2C_SR1_BERR;
    stmI2cMasterTxRxDone(pdev, -EIO);
}

static void stmI2cMasterArbitrationLoss(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;

    stmI2cMasterDmaCancel(pdev);
    regs->SR1 &= ~I2C_SR1_ARLO;
    stmI2cMasterTxRxDone(pdev, -EBUSY);
}

static void stmI2cMasterUnexpectedError(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;

    osLog(LOG_ERROR, "Unexpected I2C ERR interrupt: SR1 = %04lX, SR2 = %04lX\n",
            regs->SR1, regs->SR2);

    stmI2cMasterDmaCancel(pdev);
    regs->SR1 = 0;
    stmI2cMasterTxRxDone(pdev, -EIO);
}

static void stmI2cIsrEvent(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;
    uint16_t sr1 = regs->SR1;

    if (pdev->state.mode == STM_I2C_SLAVE) {
        if (sr1 & I2C_SR1_ADDR) {
            stmI2cSlaveAddrMatched(pdev);
        } else if (sr1 & I2C_SR1_RXNE) {
            stmI2cSlaveRxBufNotEmpty(pdev);
        } else if (sr1 & I2C_SR1_TXE) {
            stmI2cSlaveTxBufEmpty(pdev);
        } else if (sr1 & I2C_SR1_BTF) {
            if (regs->SR2 & I2C_SR2_TRA)
                stmI2cSlaveTxBufEmpty(pdev);
           else
                stmI2cSlaveRxBufNotEmpty(pdev);
        } else if (sr1 & I2C_SR1_STOPF) {
            stmI2cSlaveStopRxed(pdev);
        }
        /* TODO: other flags */
    } else if (pdev->state.mode == STM_I2C_MASTER) {
        if (sr1 & I2C_SR1_SB)
            stmI2cMasterSentStart(pdev);
        else if (sr1 & I2C_SR1_ADDR)
            stmI2cMasterSentAddr(pdev);
    }
}

static void stmI2cIsrError(struct StmI2cDev *pdev)
{
    struct StmI2c *regs = pdev->cfg->regs;
    uint16_t sr1 = regs->SR1;

    if (pdev->state.mode == STM_I2C_SLAVE) {
        if (sr1 & I2C_SR1_AF)
            stmI2cSlaveNakRxed(pdev);
        /* TODO: other flags */
    } else if (pdev->state.mode == STM_I2C_MASTER) {
        if (sr1 & I2C_SR1_AF)
            stmI2cMasterNakRxed(pdev);
        else if (sr1 & I2C_SR1_BERR)
            stmI2cMasterBusError(pdev);
        else if (sr1 & I2C_SR1_ARLO)
            stmI2cMasterArbitrationLoss(pdev);
        else
            stmI2cMasterUnexpectedError(pdev);
    }
}

#define DECLARE_IRQ_HANDLERS(_n)                \
    extern void I2C##_n##_EV_IRQHandler();      \
    extern void I2C##_n##_ER_IRQHandler();      \
                                                \
    extern void I2C##_n##_EV_IRQHandler()       \
    {                                           \
        stmI2cIsrEvent(&mStmI2cDevs[_n - 1]);  \
    }                                           \
                                                \
    extern void I2C##_n##_ER_IRQHandler()       \
    {                                           \
        stmI2cIsrError(&mStmI2cDevs[_n - 1]);  \
    }

DECLARE_IRQ_HANDLERS(1);
DECLARE_IRQ_HANDLERS(3);

static inline struct Gpio* stmI2cGpioInit(const struct StmI2cBoardCfg *board, const struct StmI2cGpioCfg *cfg)
{
    struct Gpio* gpio = gpioRequest(cfg->gpioNum);
    gpioConfigAlt(gpio, board->gpioSpeed, board->gpioPull, GPIO_OUT_OPEN_DRAIN,
            cfg->func);

    return gpio;
}

int i2cMasterRequest(uint32_t busId, uint32_t speed)
{
    if (busId >= ARRAY_SIZE(mStmI2cDevs))
        return -EINVAL;

    const struct StmI2cBoardCfg *board = boardStmI2cCfg(busId);
    if (!board)
        return -EINVAL;

    struct StmI2cDev *pdev = &mStmI2cDevs[busId];
    struct I2cStmState *state = &pdev->state;
    const struct StmI2cCfg *cfg = &mStmI2cCfgs[busId];

    if (state->mode == STM_I2C_DISABLED) {
        state->mode = STM_I2C_MASTER;

        pdev->cfg = cfg;
        pdev->board = board;
        pdev->next = 2;
        pdev->last = 1;
        atomicBitsetInit(mXfersValid, I2C_MAX_QUEUE_DEPTH);

        pdev->scl = stmI2cGpioInit(board, &board->gpioScl);
        pdev->sda = stmI2cGpioInit(board, &board->gpioSda);

        pwrUnitClock(PERIPH_BUS_APB1, cfg->clock, true);

        stmI2cDisable(pdev);

        pwrUnitReset(PERIPH_BUS_APB1, cfg->clock, true);
        pwrUnitReset(PERIPH_BUS_APB1, cfg->clock, false);

        stmI2cIrqEnable(pdev, I2C_CR2_ITEVTEN | I2C_CR2_ITERREN);
        stmI2cSpeedSet(pdev, speed);
        atomicWriteByte(&state->masterState, STM_I2C_MASTER_IDLE);

        NVIC_EnableIRQ(cfg->irqEr);
        NVIC_EnableIRQ(cfg->irqEv);

        stmI2cEnable(pdev);
        return 0;
    } else {
        return -EBUSY;
    }
}

int i2cMasterRelease(uint32_t busId)
{
    if (busId >= ARRAY_SIZE(mStmI2cDevs))
        return -EINVAL;

    struct StmI2cDev *pdev = &mStmI2cDevs[busId];
    struct I2cStmState *state = &pdev->state;
    const struct StmI2cCfg *cfg = pdev->cfg;

    if (state->mode == STM_I2C_MASTER) {
        if (atomicReadByte(&state->masterState) == STM_I2C_MASTER_IDLE) {
            state->mode = STM_I2C_DISABLED;
            stmI2cIrqEnable(pdev, I2C_CR2_ITERREN | I2C_CR2_ITEVTEN);
            stmI2cDisable(pdev);
            pwrUnitClock(PERIPH_BUS_APB1, cfg->clock, false);

            gpioRelease(pdev->scl);
            gpioRelease(pdev->sda);

            return 0;
        } else {
            return -EBUSY;
        }
    } else {
        return -EINVAL;
    }
}


int i2cMasterTxRx(uint32_t busId, uint32_t addr,
        const void *txBuf, size_t txSize, void *rxBuf, size_t rxSize,
        I2cCallbackF callback, void *cookie)
{
    uint32_t id;

    if (busId >= ARRAY_SIZE(mStmI2cDevs))
        return -EINVAL;
    else if (addr & 0x80)
        return -ENXIO;

    struct StmI2cDev *pdev = &mStmI2cDevs[busId];
    struct I2cStmState *state = &pdev->state;

    if (state->mode != STM_I2C_MASTER)
        return -EINVAL;

    struct StmI2cXfer *xfer = stmI2cGetXfer();

    if (xfer) {
        xfer->busId = busId;
        xfer->addr = addr;
        xfer->txBuf = txBuf;
        xfer->txSize = txSize;
        xfer->rxBuf = rxBuf;
        xfer->rxSize = rxSize;
        xfer->callback = callback;
        xfer->cookie = cookie;

        do {
            id = atomicAdd32bits(&pdev->last, 1);
        } while (!id);

        // after this point the transfer can be picked up by the transfer
        // complete interrupt
        atomicWrite32bits(&xfer->id, id);

        // only initiate transfer here if we are in IDLE. Otherwise the transfer
        // completion interrupt will start the next transfer (not necessarily
        // this one)
        if (atomicCmpXchgByte((uint8_t *)&state->masterState,
                STM_I2C_MASTER_IDLE, STM_I2C_MASTER_START)) {
            // it is possible for this transfer to already be complete by the
            // time we get here. if so, transfer->id will have been set to 0.
            if (atomicCmpXchg32bits(&xfer->id, id, 0)) {
                pdev->addr = xfer->addr;
                state->tx.cbuf = xfer->txBuf;
                state->tx.offset = 0;
                state->tx.size = xfer->txSize;
                state->tx.callback = xfer->callback;
                state->tx.cookie = xfer->cookie;
                state->rx.buf = xfer->rxBuf;
                state->rx.offset = 0;
                state->rx.size = xfer->rxSize;
                state->rx.callback = NULL;
                state->rx.cookie = NULL;
                state->tid = osGetCurrentTid();
                if (pdev->board->sleepDev >= 0)
                    platRequestDevInSleepMode(pdev->board->sleepDev, 12);
                stmI2cPutXfer(xfer);
                stmI2cStartEnable(pdev);
            }
        }
        return 0;
    } else {
        return -EBUSY;
    }
}

int i2cSlaveRequest(uint32_t busId, uint32_t addr)
{
    if (busId >= ARRAY_SIZE(mStmI2cDevs))
        return -EINVAL;

    const struct StmI2cBoardCfg *board = boardStmI2cCfg(busId);
    if (!board)
        return -EINVAL;

    struct StmI2cDev *pdev = &mStmI2cDevs[busId];
    const struct StmI2cCfg *cfg = &mStmI2cCfgs[busId];

    if (pdev->state.mode == STM_I2C_DISABLED) {
        pdev->state.mode = STM_I2C_SLAVE;

        pdev->addr = addr;
        pdev->cfg = cfg;
        pdev->board = board;

        pdev->scl = stmI2cGpioInit(board, &board->gpioScl);
        pdev->sda = stmI2cGpioInit(board, &board->gpioSda);

        return 0;
    } else {
        return -EBUSY;
    }
}

int i2cSlaveRelease(uint32_t busId)
{
    if (busId >= ARRAY_SIZE(mStmI2cDevs))
        return -EINVAL;

    struct StmI2cDev *pdev = &mStmI2cDevs[busId];
    const struct StmI2cCfg *cfg = pdev->cfg;

    if (pdev->state.mode == STM_I2C_SLAVE) {
        pdev->state.mode = STM_I2C_DISABLED;
        stmI2cIrqDisable(pdev, I2C_CR2_ITERREN | I2C_CR2_ITEVTEN);
        stmI2cAckDisable(pdev);
        stmI2cDisable(pdev);
        pwrUnitClock(PERIPH_BUS_APB1, cfg->clock, false);

        gpioRelease(pdev->scl);
        gpioRelease(pdev->sda);

        return 0;
    } else {
        return -EBUSY;
    }
}

void i2cSlaveEnableRx(uint32_t busId, void *rxBuf, size_t rxSize,
        I2cCallbackF callback, void *cookie)
{
    struct StmI2cDev *pdev = &mStmI2cDevs[busId];
    const struct StmI2cCfg *cfg = pdev->cfg;
    struct I2cStmState *state = &pdev->state;

    if (pdev->state.mode == STM_I2C_SLAVE) {
        state->rx.buf = rxBuf;
        state->rx.offset = 0;
        state->rx.size = rxSize;
        state->rx.callback = callback;
        state->rx.cookie = cookie;
        state->slaveState = STM_I2C_SLAVE_RX_ARMED;
        state->tid = osGetCurrentTid();

        pwrUnitClock(PERIPH_BUS_APB1, cfg->clock, true);
        pwrUnitReset(PERIPH_BUS_APB1, cfg->clock, true);
        pwrUnitReset(PERIPH_BUS_APB1, cfg->clock, false);

        NVIC_EnableIRQ(cfg->irqEr);
        NVIC_EnableIRQ(cfg->irqEv);

        stmI2cEnable(pdev);
        cfg->regs->OAR1 = I2C_OAR1_ADD7(pdev->addr);
        stmI2cIrqEnable(pdev, I2C_CR2_ITERREN | I2C_CR2_ITEVTEN);
        stmI2cAckEnable(pdev);
    }
}

static int i2cSlaveTx(uint32_t busId, const void *txBuf, uint8_t byte,
        size_t txSize, I2cCallbackF callback, void *cookie)
{
    struct StmI2cDev *pdev = &mStmI2cDevs[busId];
    struct I2cStmState *state = &pdev->state;

    if (pdev->state.mode == STM_I2C_SLAVE) {
        if (state->slaveState == STM_I2C_SLAVE_RX)
            return -EBUSY;

        if (txBuf) {
            state->tx.cbuf = txBuf;
            state->tx.preamble = false;
        } else {
            state->tx.byte = byte;
            state->tx.preamble = true;
        }
        state->tx.offset = 0;
        state->tx.size = txSize;
        state->tx.callback = callback;
        state->tx.cookie = cookie;

        if (state->slaveState == STM_I2C_SLAVE_TX_ARMED) {
            state->slaveState = STM_I2C_SLAVE_TX;
            stmI2cSlaveTxNextByte(pdev);
            stmI2cIrqEnable(pdev, I2C_CR2_ITBUFEN);
        } else {
            state->slaveState = STM_I2C_SLAVE_TX;
        }

        return 0;
    } else {
        return -EBUSY;
    }
}

int i2cSlaveTxPreamble(uint32_t busId, uint8_t byte, I2cCallbackF callback,
        void *cookie)
{
    return i2cSlaveTx(busId, NULL, byte, 0, callback, cookie);
}

int i2cSlaveTxPacket(uint32_t busId, const void *txBuf, size_t txSize,
        I2cCallbackF callback, void *cookie)
{
    return i2cSlaveTx(busId, txBuf, 0, txSize, callback, cookie);
}