/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author        Notes
 * 2021-10-11     kyle          first version
 */

#include <spi-bit-ops.h>
#include <rtdevice.h>

#define DBG_TAG               "SPI"
#ifdef RT_SPI_BITOPS_DEBUG
#define DBG_LVL               DBG_LOG
#else
#define DBG_LVL               DBG_ERROR
#endif
#include <rtdbg.h>

#define TOG_SCLK(ops)        ops->tog_sclk(ops->data)
#define SET_SCLK(ops, val)   ops->set_sclk(ops->data, val)
#define SET_MOSI(ops, val)   ops->set_mosi(ops->data, val)
#define SET_MISO(ops, val)   ops->set_miso(ops->data, val)
#define GET_SCLK(ops)        ops->get_sclk(ops->data)
#define GET_MOSI(ops)        ops->get_mosi(ops->data)
#define GET_MISO(ops)        ops->get_miso(ops->data)
#define DIR_MOSI(ops, val)   ops->dir_mosi(ops->data, val)
#define DIR_MISO(ops, val)   ops->dir_miso(ops->data, val)

rt_inline void spi_delay(struct rt_spi_bit_ops *ops)
{
    ops->udelay((ops->delay_us + 1) >> 1);
}

rt_inline void spi_delay2(struct rt_spi_bit_ops *ops)
{
    ops->udelay(ops->delay_us);
}

#define SCLK_H(ops)          SET_SCLK(ops, 1)
#define SCLK_L(ops)          SET_SCLK(ops, 0)
#define MOSI_H(ops)          SET_MOSI(ops, 1)
#define MOSI_L(ops)          SET_MOSI(ops, 0)
#define MOSI_IN(ops)         DIR_MOSI(ops, 1)
#define MOSI_OUT(ops)        DIR_MOSI(ops, 0)
#define MISO_IN(ops)         DIR_MISO(ops, 1)
#define MISO_OUT(ops)        DIR_MISO(ops, 0)

rt_inline rt_size_t spi_xfer_4line_data8(struct rt_spi_bit_ops       *ops,
                                         struct rt_spi_configuration *config,
                                         const void                  *send_buf,
                                         void                        *recv_buf,
                                         rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint8_t *send_ptr = send_buf;
        rt_uint8_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;

        while (size--)
        {
            rt_uint8_t tx_data = 0xFF;
            rt_uint8_t rx_data = 0xFF;
            rt_uint8_t bit  = 0;

            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            for (i = 0; i < 8; i++)
            {
                if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (7 - i)); }
                else                           { bit = tx_data & (0x1 << i); }

                if (bit) MOSI_H(ops);
                else     MOSI_L(ops);

                spi_delay2(ops);

                TOG_SCLK(ops);

                if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x01; }
                else                           { rx_data >>= 1; bit = 0x80; }

                if (GET_MISO(ops)) { rx_data |=  bit; }
                else               { rx_data &= ~bit; }

                spi_delay2(ops);

                if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                {
                    TOG_SCLK(ops);
                }
            }

            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }
    }

    return length;
}

rt_inline rt_size_t spi_xfer_4line_data16(struct rt_spi_bit_ops       *ops,
                                          struct rt_spi_configuration *config,
                                          const void                  *send_buf,
                                          void                        *recv_buf,
                                          rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint16_t *send_ptr = send_buf;
        rt_uint16_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;

        while (size--)
        {
            rt_uint16_t tx_data = 0xFFFF;
            rt_uint16_t rx_data = 0xFFFF;
            rt_uint16_t bit  = 0;

            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            for (i = 0; i < 16; i++)
            {
                if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (15 - i)); }
                else                           { bit = tx_data & (0x1 << i); }

                if (bit) MOSI_H(ops);
                else     MOSI_L(ops);

                spi_delay2(ops);

                TOG_SCLK(ops);

                if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x0001; }
                else                           { rx_data >>= 1; bit = 0x8000; }

                if (GET_MISO(ops)) { rx_data |=  bit; }
                else               { rx_data &= ~bit; }

                spi_delay2(ops);

                if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 15))
                {
                    TOG_SCLK(ops);
                }
            }

            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }
    }

    return length;
}

rt_inline rt_size_t spi_xfer_3line_data8(struct rt_spi_bit_ops       *ops,
                                         struct rt_spi_configuration *config,
                                         const void                  *send_buf,
                                         void                        *recv_buf,
                                         rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint8_t *send_ptr = send_buf;
        rt_uint8_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;
        rt_uint8_t send_flg = 0;

        if ((send_buf != RT_NULL) || (recv_buf == RT_NULL))
        {
            MOSI_OUT(ops);
            send_flg = 1;
        }
        else
        {
            MOSI_IN(ops);
        }

        while (size--)
        {
            rt_uint8_t tx_data = 0xFF;
            rt_uint8_t rx_data = 0xFF;
            rt_uint8_t bit  = 0;

            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            if (send_flg)
            {
                for (i = 0; i < 8; i++)
                {
                    if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (7 - i)); }
                    else                           { bit = tx_data & (0x1 << i); }

                    if (bit) MOSI_H(ops);
                    else     MOSI_L(ops);

                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                    {
                        TOG_SCLK(ops);
                    }
                }

                rx_data = tx_data;
            }
            else
            {
                for (i = 0; i < 8; i++)
                {
                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x01; }
                    else                           { rx_data >>= 1; bit = 0x80; }

                    if (GET_MOSI(ops)) { rx_data |=  bit; }
                    else               { rx_data &= ~bit; }

                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                    {
                        TOG_SCLK(ops);
                    }
                }

            }

            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }

        if (!send_flg)
        {
            MOSI_OUT(ops);
        }
    }

    return length;
}

rt_inline rt_size_t spi_xfer_3line_data16(struct rt_spi_bit_ops       *ops,
                                          struct rt_spi_configuration *config,
                                          const void                  *send_buf,
                                          void                        *recv_buf,
                                          rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint16_t *send_ptr = send_buf;
        rt_uint16_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;
        rt_uint8_t send_flg = 0;

        if ((send_buf != RT_NULL) || (recv_buf == RT_NULL))
        {
            MOSI_OUT(ops);
            send_flg = 1;
        }
        else
        {
            MOSI_IN(ops);
        }

        while (size--)
        {
            rt_uint16_t tx_data = 0xFFFF;
            rt_uint16_t rx_data = 0xFFFF;
            rt_uint16_t bit  = 0;

            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            if (send_flg)
            {
                for (i = 0; i < 16; i++)
                {
                    if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (15 - i)); }
                    else                           { bit = tx_data & (0x1 << i); }

                    if (bit) MOSI_H(ops);
                    else     MOSI_L(ops);

                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 15))
                    {
                        TOG_SCLK(ops);
                    }
                }

                rx_data = tx_data;
            }
            else
            {
                for (i = 0; i < 16; i++)
                {
                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x0001; }
                    else                           { rx_data >>= 1; bit = 0x8000; }

                    if (GET_MOSI(ops)) { rx_data |=  bit; }
                    else               { rx_data &= ~bit; }

                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 15))
                    {
                        TOG_SCLK(ops);
                    }
                }

            }

            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }

        if (!send_flg)
        {
            MOSI_OUT(ops);
        }
    }

    return length;
}

rt_err_t spi_bit_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration)
{
    struct rt_spi_bit_obj *obj = rt_container_of(device->bus, struct rt_spi_bit_obj, bus);
    struct rt_spi_bit_ops *ops = obj->ops;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    if (configuration->mode & RT_SPI_SLAVE)
    {
        return -RT_EIO;
    }

    if (configuration->mode & RT_SPI_CPOL)
    {
        SCLK_H(ops);
    }
    else
    {
        SCLK_L(ops);
    }

    if (configuration->max_hz < 200000)
    {
        ops->delay_us = 1;
    }
    else
    {
        ops->delay_us = 0;
    }

    rt_memcpy(&obj->config, configuration, sizeof(struct rt_spi_configuration));

    return RT_EOK;
}

rt_uint32_t spi_bit_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    struct rt_spi_bit_obj *obj = rt_container_of(device->bus, struct rt_spi_bit_obj, bus);
    struct rt_spi_bit_ops *ops = obj->ops;
    struct rt_spi_configuration *config = &obj->config;
    rt_base_t cs_pin = (rt_base_t)device->parent.user_data;

    RT_ASSERT(device != NULL);
    RT_ASSERT(message != NULL);

#ifdef RT_SPI_BITOPS_DEBUG
    if (!ops->tog_sclk || !ops->set_sclk || !ops->get_sclk)
    {
        LOG_E("SPI bus error, SCLK line not defined");
    }
    if (!ops->set_mosi || !ops->get_mosi)
    {
        LOG_E("SPI bus error, MOSI line not defined");
    }
    if (!ops->set_miso || !ops->get_miso)
    {
        LOG_E("SPI bus error, MISO line not defined");
    }
#endif

    /* take CS */
    if (message->cs_take)
    {
        LOG_I("spi take cs\n");
        rt_pin_write(cs_pin, PIN_LOW);
        spi_delay(ops);

        /* spi phase */
        if (config->mode & RT_SPI_CPHA)
        {
            spi_delay(ops);
            TOG_SCLK(ops);
        }
    }

    if (config->mode & RT_SPI_3WIRE)
    {
        if (config->data_width <= 8)
        {
            spi_xfer_3line_data8(ops,
                                 config,
                                 message->send_buf,
                                 message->recv_buf,
                                 message->length);
        }
        else if (config->data_width <= 16)
        {
            spi_xfer_3line_data16(ops,
                                  config,
                                  message->send_buf,
                                  message->recv_buf,
                                  message->length);
        }
    }
    else
    {
        if (config->data_width <= 8)
        {
            spi_xfer_4line_data8(ops,
                                 config,
                                 message->send_buf,
                                 message->recv_buf,
                                 message->length);
        }
        else if (config->data_width <= 16)
        {
            spi_xfer_4line_data16(ops,
                                  config,
                                  message->send_buf,
                                  message->recv_buf,
                                  message->length);
        }
    }

    /* release CS */
    if (message->cs_release)
    {
        spi_delay(ops);
        rt_pin_write(cs_pin, PIN_HIGH);
        LOG_I("spi release cs\n");
    }

    return message->length;
}

static const struct rt_spi_ops spi_bit_bus_ops =
{
    .configure = spi_bit_configure,
    .xfer      = spi_bit_xfer,
};

rt_err_t rt_spi_bit_add_bus(struct rt_spi_bit_obj *obj,
                            const char            *bus_name,
                            struct rt_spi_bit_ops *ops)
{
    obj->ops = ops;
    obj->config.data_width = 8;
    obj->config.max_hz     = 1 * 1000 * 1000;
    obj->config.mode       = RT_SPI_MASTER | RT_SPI_MSB | RT_SPI_MODE_0;

    /* idle status */
    if (obj->config.mode & RT_SPI_CPOL) SCLK_H(ops);
    else                                SCLK_L(ops);

    return rt_spi_bus_register(&obj->bus, bus_name, &spi_bit_bus_ops);
}