root/sys/dev/drm/amd/display/dc/i2caux/i2c_generic_hw_engine.c
/*
 * Copyright 2012-15 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors: AMD
 *
 */

#include "dm_services.h"

/*
 * Pre-requisites: headers required by header of this unit
 */
#include "include/i2caux_interface.h"
#include "engine.h"
#include "i2c_engine.h"
#include "i2c_hw_engine.h"

/*
 * Header of this unit
 */

#include "i2c_generic_hw_engine.h"

/*
 * Post-requisites: headers required by this unit
 */

/*
 * This unit
 */

/*
 * @brief
 * Cast 'struct i2c_hw_engine *'
 * to 'struct i2c_generic_hw_engine *'
 */
#define FROM_I2C_HW_ENGINE(ptr) \
        container_of((ptr), struct i2c_generic_hw_engine, base)

/*
 * @brief
 * Cast 'struct i2c_engine *'
 * to 'struct i2c_generic_hw_engine *'
 */
#define FROM_I2C_ENGINE(ptr) \
        FROM_I2C_HW_ENGINE(container_of((ptr), struct i2c_hw_engine, base))

/*
 * @brief
 * Cast 'struct engine *'
 * to 'struct i2c_generic_hw_engine *'
 */
#define FROM_ENGINE(ptr) \
        FROM_I2C_ENGINE(container_of((ptr), struct i2c_engine, base))

enum i2caux_engine_type dal_i2c_generic_hw_engine_get_engine_type(
        const struct engine *engine)
{
        return I2CAUX_ENGINE_TYPE_I2C_GENERIC_HW;
}

/*
 * @brief
 * Single transaction handling.
 * Since transaction may be bigger than HW buffer size,
 * it divides transaction to sub-transactions
 * and uses batch transaction feature of the engine.
 */
bool dal_i2c_generic_hw_engine_submit_request(
        struct engine *engine,
        struct i2caux_transaction_request *i2caux_request,
        bool middle_of_transaction)
{
        struct i2c_generic_hw_engine *hw_engine = FROM_ENGINE(engine);

        struct i2c_hw_engine *base = &hw_engine->base;

        uint32_t max_payload_size =
                base->funcs->get_hw_buffer_available_size(base);

        bool initial_stop_bit = !middle_of_transaction;

        struct i2c_generic_transaction_attributes attributes;

        enum i2c_channel_operation_result operation_result =
                I2C_CHANNEL_OPERATION_FAILED;

        bool result = false;

        /* setup transaction initial properties */

        uint8_t address = i2caux_request->payload.address;
        uint8_t *current_payload = i2caux_request->payload.data;
        uint32_t remaining_payload_size = i2caux_request->payload.length;

        bool first_iteration = true;

        if (i2caux_request->operation == I2CAUX_TRANSACTION_READ)
                attributes.action = I2CAUX_TRANSACTION_ACTION_I2C_READ;
        else if (i2caux_request->operation == I2CAUX_TRANSACTION_WRITE)
                attributes.action = I2CAUX_TRANSACTION_ACTION_I2C_WRITE;
        else {
                i2caux_request->status =
                        I2CAUX_TRANSACTION_STATUS_FAILED_INVALID_OPERATION;
                return false;
        }

        /* Do batch transaction.
         * Divide read/write data into payloads which fit HW buffer size.
         * 1. Single transaction:
         *    start_bit = 1, stop_bit depends on session state, ack_on_read = 0;
         * 2. Start of batch transaction:
         *    start_bit = 1, stop_bit = 0, ack_on_read = 1;
         * 3. Middle of batch transaction:
         *    start_bit = 0, stop_bit = 0, ack_on_read = 1;
         * 4. End of batch transaction:
         *    start_bit = 0, stop_bit depends on session state, ack_on_read = 0.
         * Session stop bit is set if 'middle_of_transaction' = 0. */

        while (remaining_payload_size) {
                uint32_t current_transaction_size;
                uint32_t current_payload_size;

                bool last_iteration;
                bool stop_bit;

                /* Calculate current transaction size and payload size.
                 * Transaction size = total number of bytes in transaction,
                 * including slave's address;
                 * Payload size = number of data bytes in transaction. */

                if (first_iteration) {
                        /* In the first sub-transaction we send slave's address
                         * thus we need to reserve one byte for it */
                        current_transaction_size =
                        (remaining_payload_size > max_payload_size - 1) ?
                                max_payload_size :
                                remaining_payload_size + 1;

                        current_payload_size = current_transaction_size - 1;
                } else {
                        /* Second and further sub-transactions will have
                         * entire buffer reserved for data */
                        current_transaction_size =
                                (remaining_payload_size > max_payload_size) ?
                                max_payload_size :
                                remaining_payload_size;

                        current_payload_size = current_transaction_size;
                }

                last_iteration =
                        (remaining_payload_size == current_payload_size);

                stop_bit = last_iteration ? initial_stop_bit : false;

                /* write slave device address */

                if (first_iteration)
                        hw_engine->funcs->write_address(hw_engine, address);

                /* write current portion of data, if requested */

                if (i2caux_request->operation == I2CAUX_TRANSACTION_WRITE)
                        hw_engine->funcs->write_data(
                                hw_engine,
                                current_payload,
                                current_payload_size);

                /* execute transaction */

                attributes.start_bit = first_iteration;
                attributes.stop_bit = stop_bit;
                attributes.last_read = last_iteration;
                attributes.transaction_size = current_transaction_size;

                hw_engine->funcs->execute_transaction(hw_engine, &attributes);

                /* wait until transaction is processed; if it fails - quit */

                operation_result = base->funcs->wait_on_operation_result(
                        base,
                        base->funcs->get_transaction_timeout(
                                base, current_transaction_size),
                        I2C_CHANNEL_OPERATION_ENGINE_BUSY);

                if (operation_result != I2C_CHANNEL_OPERATION_SUCCEEDED)
                        break;

                /* read current portion of data, if requested */

                /* the read offset should be 1 for first sub-transaction,
                 * and 0 for any next one */

                if (i2caux_request->operation == I2CAUX_TRANSACTION_READ)
                        hw_engine->funcs->read_data(hw_engine, current_payload,
                                current_payload_size, first_iteration ? 1 : 0);

                /* update loop variables */

                first_iteration = false;
                current_payload += current_payload_size;
                remaining_payload_size -= current_payload_size;
        }

        /* update transaction status */

        switch (operation_result) {
        case I2C_CHANNEL_OPERATION_SUCCEEDED:
                i2caux_request->status =
                        I2CAUX_TRANSACTION_STATUS_SUCCEEDED;
                result = true;
        break;
        case I2C_CHANNEL_OPERATION_NO_RESPONSE:
                i2caux_request->status =
                        I2CAUX_TRANSACTION_STATUS_FAILED_NACK;
        break;
        case I2C_CHANNEL_OPERATION_TIMEOUT:
                i2caux_request->status =
                        I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT;
        break;
        case I2C_CHANNEL_OPERATION_FAILED:
                i2caux_request->status =
                        I2CAUX_TRANSACTION_STATUS_FAILED_INCOMPLETE;
        break;
        default:
                i2caux_request->status =
                        I2CAUX_TRANSACTION_STATUS_FAILED_OPERATION;
        }

        return result;
}

/*
 * @brief
 * Returns number of microseconds to wait until timeout to be considered
 */
uint32_t dal_i2c_generic_hw_engine_get_transaction_timeout(
        const struct i2c_hw_engine *engine,
        uint32_t length)
{
        const struct i2c_engine *base = &engine->base;

        uint32_t speed = base->funcs->get_speed(base);

        if (!speed)
                return 0;

        /* total timeout = period_timeout * (start + data bits count + stop) */

        return ((1000 * TRANSACTION_TIMEOUT_IN_I2C_CLOCKS) / speed) *
                (1 + (length << 3) + 1);
}

void dal_i2c_generic_hw_engine_construct(
        struct i2c_generic_hw_engine *engine,
        struct dc_context *ctx)
{
        dal_i2c_hw_engine_construct(&engine->base, ctx);
}

void dal_i2c_generic_hw_engine_destruct(
        struct i2c_generic_hw_engine *engine)
{
        dal_i2c_hw_engine_destruct(&engine->base);
}