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