/* mbed Microcontroller Library
 * Copyright (c) 2019, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 "i2c_api.h"
#include "cyhal_i2c.h"
#include "cyhal_utils.h"
#include "cyhal_hwmgr.h"
#include "mbed_error.h"
#include "mbed_assert.h"
#include "mbed_critical.h"

#if DEVICE_I2C

#define CY_I2C_DEFAULT_TIMEOUT 200

#define CY_I2C_SLAVE_EVENT_IDLE 0
#define CY_I2C_SLAVE_EVENT_READ_ADDR 1
#define CY_I2C_SLAVE_EVENT_GENERAL_CALL 2
#define CY_I2C_SLAVE_EVENT_WRITE_ADDR 3

static inline struct i2c_s *cy_get_i2c(i2c_t *obj)
{
#if DEVICE_I2C_ASYNCH
    return &(obj->i2c);
#else
    return obj;
#endif
}

#ifdef __cplusplus
extern "C" {
#endif

static uint32_t cy_i2c_convert_event(i2c_t *obj, cyhal_i2c_event_t event)
{
    if (CYHAL_I2C_EVENT_NONE != (event & (CYHAL_I2C_SLAVE_ERR_EVENT | CYHAL_I2C_MASTER_ERR_EVENT))) {
        event |= I2C_EVENT_ERROR;
    }
    if (CYHAL_I2C_EVENT_NONE != (event & (CYHAL_I2C_SLAVE_RD_CMPLT_EVENT | CYHAL_I2C_SLAVE_WR_CMPLT_EVENT | CYHAL_I2C_MASTER_RD_CMPLT_EVENT | CYHAL_I2C_MASTER_WR_CMPLT_EVENT)) && !i2c_active(obj)) {
        event |= I2C_EVENT_TRANSFER_COMPLETE;
    }
    return event;
}

static void cy_i2c_event_handler(void *handler_arg, cyhal_i2c_event_t event)
{
    struct i2c_s *i2c = cy_get_i2c((i2c_t *)handler_arg);
    switch (event) {
        case CYHAL_I2C_SLAVE_READ_EVENT:
            i2c->slave_event = CY_I2C_SLAVE_EVENT_READ_ADDR;
            break;
        case CYHAL_I2C_SLAVE_WRITE_EVENT:
            i2c->slave_event = CY_I2C_SLAVE_EVENT_WRITE_ADDR;
            break;
        case CYHAL_I2C_SLAVE_ERR_EVENT: // fallthrough
        case CYHAL_I2C_SLAVE_RD_CMPLT_EVENT:    // fallthrough
        case CYHAL_I2C_SLAVE_WR_CMPLT_EVENT:
            i2c->slave_event = CY_I2C_SLAVE_EVENT_IDLE;
            break;
        default:
            break;
    }
#ifdef DEVICE_I2C_ASYNCH
    i2c->async_event = cy_i2c_convert_event((i2c_t *)handler_arg, event);
    void (*async_handler)(void) = i2c->async_handler;
    if (NULL != async_handler && i2c->async_event != 0) {
        (*async_handler)();
    }
#endif
}

void i2c_init(i2c_t *obj, PinName sda, PinName scl)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    cy_rslt_t result = cyhal_i2c_init(&(i2c->hal_i2c), sda, scl, NULL);
    if (result == CYHAL_HWMGR_RSLT_ERR_INUSE) {
        // MBED I2C driver currently does not support free, so we will allow I2C to be reallocated.
        // TODO: once the the I2C driver properly supports free, this need to be fixed so that clocks and pins are no longer leaked.
        cyhal_hwmgr_free(&(i2c->hal_i2c.resource));
        cyhal_resource_inst_t pin_rsc = cyhal_utils_get_gpio_resource(sda);
        cyhal_hwmgr_free(&pin_rsc);
        pin_rsc = cyhal_utils_get_gpio_resource(scl);
        cyhal_hwmgr_free(&pin_rsc);
        result = cyhal_i2c_init(&(i2c->hal_i2c), sda, scl, NULL);
    }
    if (CY_RSLT_SUCCESS != result) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "cyhal_i2c_init");
    }
    i2c->cfg.is_slave = false;
    i2c->cfg.address = 0;
    i2c->cfg.frequencyhal_hz = 400000;
    i2c->async_handler = NULL;
    cyhal_i2c_register_callback(&(i2c->hal_i2c), &cy_i2c_event_handler, obj);
    cyhal_i2c_enable_event(&(i2c->hal_i2c), (cyhal_i2c_event_t)(CYHAL_I2C_SLAVE_READ_EVENT | CYHAL_I2C_SLAVE_WRITE_EVENT | CYHAL_I2C_SLAVE_ERR_EVENT | CYHAL_I2C_SLAVE_RD_CMPLT_EVENT | CYHAL_I2C_SLAVE_WR_CMPLT_EVENT | CYHAL_I2C_MASTER_ERR_EVENT | CYHAL_I2C_MASTER_RD_CMPLT_EVENT | CYHAL_I2C_MASTER_WR_CMPLT_EVENT), CYHAL_ISR_PRIORITY_DEFAULT, true);
}

void i2c_frequency(i2c_t *obj, int hz)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    i2c->cfg.frequencyhal_hz = (uint32_t)hz;
    if (CY_RSLT_SUCCESS != cyhal_i2c_configure(&(i2c->hal_i2c), &(i2c->cfg))) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "cyhal_i2c_configure");
    }
}

int i2c_start(i2c_t *obj)
{
    /* start/stop is generated by i2c_read/i2c_write */
    struct i2c_s *i2c = cy_get_i2c(obj);
    /* Clear state of address */
    i2c->address_set = false;
    return 0;
}

int i2c_stop(i2c_t *obj)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    /* Clear state of address */
    i2c->address_set = false;
    if (i2c->hal_i2c.context.state != CY_SCB_I2C_IDLE) {
        return Cy_SCB_I2C_MasterSendStop(i2c->hal_i2c.base, CY_I2C_DEFAULT_TIMEOUT, &(i2c->hal_i2c.context));
    }
    return 0;
}

int i2c_read(i2c_t *obj, int address, char *data, int length, int stop)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    if (CY_RSLT_SUCCESS != cyhal_i2c_master_read(&(i2c->hal_i2c), address >> 1, (uint8_t *)data, (uint16_t)length, CY_I2C_DEFAULT_TIMEOUT, (bool)stop)) {
        return (int)I2C_ERROR_NO_SLAVE;
    }
    return length;
}

int i2c_write(i2c_t *obj, int address, const char *data, int length, int stop)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    if (CY_RSLT_SUCCESS != cyhal_i2c_master_write(&(i2c->hal_i2c), address >> 1, (const uint8_t *)data, (uint16_t)length, CY_I2C_DEFAULT_TIMEOUT, (bool)stop)) {
        return (int)I2C_ERROR_NO_SLAVE;
    }
    // NOTE: HAL does not report how many bytes were actually sent in case of early NAK
    return length;
}

void i2c_reset(i2c_t *obj)
{
    i2c_stop(obj);
}

int i2c_byte_read(i2c_t *obj, int last)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    uint8_t value;

    /* Slave device address is stored in object by first write operation */
    if (i2c->address_set) {
        /* Send slave device address */
        /* Make sure if I2C transaction direction is 'Read' */
        if (CY_SCB_I2C_SUCCESS != Cy_SCB_I2C_MasterSendStart(i2c->hal_i2c.base, i2c->address, CY_SCB_I2C_READ_XFER, CY_I2C_DEFAULT_TIMEOUT, &(i2c->hal_i2c.context))) {
            MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "i2c_send_start");
        }
        /* Clear state of address. It is not needed for next operation. */
        i2c->address_set = false;
    }

    /* Read next byte */
    if (CY_SCB_I2C_SUCCESS != Cy_SCB_I2C_MasterReadByte(i2c->hal_i2c.base, last != 0 ? CY_SCB_I2C_NAK : CY_SCB_I2C_ACK, &value, CY_I2C_DEFAULT_TIMEOUT, &(i2c->hal_i2c.context))) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "i2c_read_byte");
    }

    return value;
}

int i2c_byte_write(i2c_t *obj, int data)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    cy_en_scb_i2c_status_t status = CY_SCB_I2C_SUCCESS;

    /* First byte should be address */
    if (i2c->address_set) {
        /* Verify if Master is ready for send slave address and send first data byte */
        /* Make sure that I2C transaction direction is 'Write' */
        if (i2c->hal_i2c.context.state == CY_SCB_I2C_IDLE) {
            status = Cy_SCB_I2C_MasterSendStart(i2c->hal_i2c.base, i2c->address, CY_SCB_I2C_WRITE_XFER, CY_I2C_DEFAULT_TIMEOUT, &(i2c->hal_i2c.context));
        }

        if (status == CY_SCB_I2C_SUCCESS) {
            status = Cy_SCB_I2C_MasterWriteByte(i2c->hal_i2c.base, (uint8_t)data, CY_I2C_DEFAULT_TIMEOUT, &(i2c->hal_i2c.context));
        }
    } else {
        /* Store slave address and remember status for next byte read or write operation */
        i2c->address = data >> 1;
        i2c->address_set = true;
    }


    switch (status) {
        case CY_SCB_I2C_MASTER_MANUAL_TIMEOUT:
            return 2;
        case CY_SCB_I2C_SUCCESS:
            return 1;
        default:
            return 0;
    }
}

const PinMap *i2c_master_sda_pinmap(void)
{
    return PinMap_I2C_SDA;
}

const PinMap *i2c_master_scl_pinmap(void)
{
    return PinMap_I2C_SCL;
}

const PinMap *i2c_slave_sda_pinmap(void)
{
    return PinMap_I2C_SDA;
}

const PinMap *i2c_slave_scl_pinmap(void)
{
    return PinMap_I2C_SCL;
}

#if DEVICE_I2CSLAVE

void i2c_slave_mode(i2c_t *obj, int enable_slave)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    i2c->cfg.is_slave = (0 != enable_slave);
    if (CY_RSLT_SUCCESS != cyhal_i2c_configure(&(i2c->hal_i2c), &(i2c->cfg))) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "cyhal_i2c_configure");
    }
}

int  i2c_slave_receive(i2c_t *obj)
{
    return cy_get_i2c(obj)->slave_event;
}

int  i2c_slave_read(i2c_t *obj, char *data, int length)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    if (CY_RSLT_SUCCESS != cyhal_i2c_slave_config_read_buff(&(i2c->hal_i2c), (uint8_t *)data, (uint16_t)length)) {
        return 0;
    }
    return 1;
}

int  i2c_slave_write(i2c_t *obj, const char *data, int length)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    if (CY_RSLT_SUCCESS != cyhal_i2c_slave_config_write_buff(&(i2c->hal_i2c), (const uint8_t *)data, (uint16_t)length)) {
        return 0;
    }
    return 1;
}

void i2c_slave_address(i2c_t *obj, int idx, uint32_t address, uint32_t mask)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    i2c->cfg.address = address;
    if (CY_RSLT_SUCCESS != cyhal_i2c_configure(&(i2c->hal_i2c), &(i2c->cfg))) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "cyhal_i2c_configure");
    }
    Cy_SCB_I2C_SlaveSetAddressMask(i2c->hal_i2c.base, (uint8_t)mask);
}

#endif

#if DEVICE_I2C_ASYNCH

void i2c_transfer_asynch(i2c_t *obj, const void *tx, size_t tx_length, void *rx, size_t rx_length, uint32_t address, uint32_t stop, uint32_t handler, uint32_t event, DMAUsage hint)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    core_util_critical_section_enter();
    i2c->async_rx_size = rx_length;
    i2c->async_handler = (void (*)(void))handler;
    core_util_critical_section_exit();
    if (CY_RSLT_SUCCESS != cyhal_i2c_master_transfer_async(&(i2c->hal_i2c), address, tx, tx_length, rx, rx_length)) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "cyhal_i2c_master_transfer_async");
    }
}

uint32_t i2c_irq_handler_asynch(i2c_t *obj)
{
    return cy_get_i2c(obj)->async_event;
}

uint8_t i2c_active(i2c_t *obj)
{
    return cy_get_i2c(obj)->hal_i2c.pending != 0 ? 1 : 0;
}

void i2c_abort_asynch(i2c_t *obj)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    if (CY_RSLT_SUCCESS != cyhal_i2c_abort_async(&(i2c->hal_i2c))) {
        MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_I2C, MBED_ERROR_CODE_FAILED_OPERATION), "cyhal_i2c_abort_async");
    }
}

void i2c_free(i2c_t *obj)
{
    struct i2c_s *i2c = cy_get_i2c(obj);
    cyhal_i2c_free(&i2c->hal_i2c);
}

#endif

#ifdef __cplusplus
}
#endif

#endif
