/* I2C bus common (driver-independent) primitives implementation.
 *
 * Copyright (C) 2000 OKTET Ltd., St.-Petersburg, Russia
 * Author: Victor V. Vengerov <vvv@oktet.ru>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 *
 * http://www.rtems.org/license/LICENSE.
 */

#include <bsp.h>
#include <bsp/i2c.h>
#include <rtems/score/sysstate.h>

/* i2c_transfer_sema_done_func --
 *     This function called from I2C driver layer to signal that I2C
 *     transfer is finished. This function resumes of task execution which
 *     has invoked blocking I2C primitive.
 *
 * PARAMETERS:
 *     arg - done function argument; it is RTEMS semaphore ID.
 */
static void
i2c_transfer_sema_done_func(void * arg)
{
    rtems_id sema = *(rtems_id *)arg;
    rtems_semaphore_release(sema);
}

/* i2c_transfer_poll_done_func --
 *     This function called from I2C driver layer to signal that I2C
 *     transfer is finished. This function set the flag polled by waiting
 *     function.
 *
 * PARAMETERS:
 *     arg - done function argument; address of poll_done_flag
 */
static void
i2c_transfer_poll_done_func(void *arg)
{
    bool *poll_done_flag = (bool *)arg;
    *poll_done_flag = true;
}

/* i2c_transfer_wait_sema --
 *     Initiate I2C bus transfer and block on temporary created semaphore
 *     until this transfer will be finished.
 *
 * PARAMETERS:
 *     bus - I2C bus number
 *     msg - pointer to transfer messages array
 *     nmsg - number of messages in transfer
 *
 * RETURNS:
 *     RTEMS_SUCCESSFUL, if tranfer finished successfully,
 *     or RTEMS status code if semaphore operations has failed.
 */
static i2c_message_status
i2c_transfer_wait_sema(i2c_bus_number bus, i2c_message *msg, int nmsg)
{
    rtems_status_code sc;
    rtems_id sema;
    sc = rtems_semaphore_create(
        rtems_build_name('I', '2', 'C', 'S'),
        0,
        RTEMS_COUNTING_SEMAPHORE | RTEMS_NO_INHERIT_PRIORITY |
        RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL,
        0,
        &sema
    );
    if (sc != RTEMS_SUCCESSFUL)
        return I2C_RESOURCE_NOT_AVAILABLE;
    sc = i2c_transfer(bus, nmsg, msg,
		      i2c_transfer_sema_done_func, &sema);
    if (sc != RTEMS_SUCCESSFUL)
    {
        rtems_semaphore_delete(sema);
        return sc;
    }
    rtems_semaphore_obtain(sema, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
    sc = rtems_semaphore_delete(sema);
    return sc;
}

/* i2c_transfer_wait_poll --
 *     Initiate I2C bus transfer and wait by poll transaction done flag until
 *     this transfer will be finished.
 *
 * PARAMETERS:
 *     bus - I2C bus number
 *     msg - pointer to transfer messages array
 *     nmsg - number of messages in transfer
 *
 * RETURNS:
 *     RTEMS_SUCCESSFUL
 */
static rtems_status_code
i2c_transfer_wait_poll(i2c_bus_number bus, i2c_message *msg, int nmsg)
{
  /*
   * this looks nasty, but is correct:
   * we wait in this function, until the poll_done_flag is
   * set deep inside the i2c_poll() function
   */
    volatile bool poll_done_flag;
    rtems_status_code sc;
    poll_done_flag = false;
    sc = i2c_transfer(bus, nmsg, msg,
		      i2c_transfer_poll_done_func,(void *)&poll_done_flag);
    if (sc != RTEMS_SUCCESSFUL)
        return sc;
    while (poll_done_flag == false)
    {
        i2c_poll(bus);
    }
    return RTEMS_SUCCESSFUL;
}

/* i2c_transfer_wait --
 *     Initiate I2C bus transfer and block until this transfer will be
 *     finished. This function wait the semaphore if system in
 *     SYSTEM_STATE_UP state, or poll done flag in other states.
 *
 * PARAMETERS:
 *     bus - I2C bus number
 *     msg - pointer to transfer messages array
 *     nmsg - number of messages in transfer
 *
 * RETURNS:
 *     I2C_SUCCESSFUL, if tranfer finished successfully,
 *     I2C_RESOURCE_NOT_AVAILABLE, if semaphore operations has failed,
 *     value of status field of first error-finished message in transfer,
 *     if something wrong.
 */
i2c_message_status
i2c_transfer_wait(i2c_bus_number bus, i2c_message *msg, int nmsg)
{
    rtems_status_code sc;
    int i;
    if (_System_state_Is_up(_System_state_Get()))
    {
        sc = i2c_transfer_wait_sema(bus, msg, nmsg);
    }
    else
    {
        sc = i2c_transfer_wait_poll(bus, msg, nmsg);
    }

    if (sc != RTEMS_SUCCESSFUL)
        return I2C_RESOURCE_NOT_AVAILABLE;

    for (i = 0; i < nmsg; i++)
    {
        if (msg[i].status != I2C_SUCCESSFUL)
        {
            return msg[i].status;
        }
    }
    return I2C_SUCCESSFUL;
}

/* i2c_write --
 *     Send single message over specified I2C bus to addressed device and
 *     wait while transfer is finished.
 *
 * PARAMETERS:
 *     bus  - I2C bus number
 *     addr - address of I2C device
 *     buf  - data to be sent to device
 *     size - data buffer size
 *
 * RETURNS:
 *     transfer status
 */
i2c_message_status
i2c_write(i2c_bus_number bus, i2c_address addr, void *buf, int size)
{
    i2c_message msg;
    msg.addr = addr;
    msg.flags = I2C_MSG_WR;
    if (addr > 0xff)
        msg.flags |= I2C_MSG_ADDR_10;
    msg.status = 0;
    msg.len = size;
    msg.buf = buf;
    return i2c_transfer_wait(bus, &msg, 1);
}

/* i2c_wrbyte --
 *     Send single one-byte long message over specified I2C bus to
 *     addressed device and wait while transfer is finished.
 *
 * PARAMETERS:
 *     bus  - I2C bus number
 *     addr - address of I2C device
 *     cmd  - byte message to be sent to device
 *
 * RETURNS:
 *     transfer status
 */
i2c_message_status
i2c_wrbyte(i2c_bus_number bus, i2c_address addr, uint8_t         cmd)
{
    i2c_message msg;
    uint8_t         data = cmd;
    msg.addr = addr;
    msg.flags = I2C_MSG_WR;
    if (addr > 0xff)
        msg.flags |= I2C_MSG_ADDR_10;
    msg.status = 0;
    msg.len = sizeof(data);
    msg.buf = &data;
    return i2c_transfer_wait(bus, &msg, 1);
}

/* i2c_read --
 *     receive single message over specified I2C bus from addressed device.
 *     This call will wait while transfer is finished.
 *
 * PARAMETERS:
 *     bus  - I2C bus number
 *     addr - address of I2C device
 *     buf  - buffer for received message
 *     size - receive buffer size
 *
 * RETURNS:
 *     transfer status
 */
i2c_message_status
i2c_read(i2c_bus_number bus, i2c_address addr, void *buf, int size)
{
    i2c_message msg;
    msg.addr = addr;
    msg.flags = 0;
    if (addr > 0xff)
        msg.flags |= I2C_MSG_ADDR_10;
    msg.status = 0;
    msg.len = size;
    msg.buf = buf;
    return i2c_transfer_wait(bus, &msg, 1);
}

/* i2c_wrrd --
 *     Send message over I2C bus to specified device and receive message
 *     from the same device during single transfer.
 *
 * PARAMETERS:
 *     bus   - I2C bus number
 *     addr  - address of I2C device
 *     bufw  - data to be sent to device
 *     sizew - send data buffer size
 *     bufr  - buffer for received message
 *     sizer - receive buffer size
 *
 * RETURNS:
 *     transfer status
 */
i2c_message_status
i2c_wrrd(i2c_bus_number bus, i2c_address addr, void *bufw, int sizew,
         void *bufr, int sizer)
{
    i2c_message msg[2];
    msg[0].addr = addr;
    msg[0].flags = I2C_MSG_WR | I2C_MSG_ERRSKIP;
    if (addr > 0xff)
        msg[0].flags |= I2C_MSG_ADDR_10;
    msg[0].status = 0;
    msg[0].len = sizew;
    msg[0].buf = bufw;

    msg[1].addr = addr;
    msg[1].flags = 0;
    if (addr > 0xff)
        msg[1].flags |= I2C_MSG_ADDR_10;
    msg[1].status = 0;
    msg[1].len = sizer;
    msg[1].buf = bufr;

    return i2c_transfer_wait(bus, msg, 2);
}

/* i2c_wbrd --
 *     Send one-byte message over I2C bus to specified device and receive
 *     message from the same device during single transfer.
 *
 * PARAMETERS:
 *     bus   - I2C bus number
 *     addr  - address of I2C device
 *     cmd   - one-byte message to be sent over I2C bus
 *     bufr  - buffer for received message
 *     sizer - receive buffer size
 *
 * RETURNS:
 *     transfer status
 */
i2c_message_status
i2c_wbrd(i2c_bus_number bus, i2c_address addr, uint8_t         cmd,
         void *bufr, int sizer)
{
    i2c_message msg[2];
    uint8_t         bufw = cmd;
    msg[0].addr = addr;
    msg[0].flags = I2C_MSG_WR | I2C_MSG_ERRSKIP;
    if (addr > 0xff)
        msg[0].flags |= I2C_MSG_ADDR_10;
    msg[0].status = 0;
    msg[0].len = sizeof(bufw);
    msg[0].buf = &bufw;

    msg[1].addr = addr;
    msg[1].flags = I2C_MSG_ERRSKIP;
    if (addr > 0xff)
        msg[1].flags |= I2C_MSG_ADDR_10;
    msg[1].status = 0;
    msg[1].len = sizer;
    msg[1].buf = bufr;

    return i2c_transfer_wait(bus, msg, 2);
}
