/*
 * 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);
}
