| /* | |
| * 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" | |
| /* | |
| * Header of this unit | |
| */ | |
| #include "aux_engine.h" | |
| /* | |
| * Post-requisites: headers required by this unit | |
| */ | |
| #include "include/link_service_types.h" | |
| /* | |
| * This unit | |
| */ | |
| enum { | |
| AUX_INVALID_REPLY_RETRY_COUNTER = 1, | |
| AUX_TIMED_OUT_RETRY_COUNTER = 2, | |
| AUX_DEFER_RETRY_COUNTER = 6 | |
| }; | |
| #define FROM_ENGINE(ptr) \ | |
| container_of((ptr), struct aux_engine, base) | |
| enum i2caux_engine_type dal_aux_engine_get_engine_type( | |
| const struct engine *engine) | |
| { | |
| return I2CAUX_ENGINE_TYPE_AUX; | |
| } | |
| bool dal_aux_engine_acquire( | |
| struct engine *engine, | |
| struct ddc *ddc) | |
| { | |
| struct aux_engine *aux_engine = FROM_ENGINE(engine); | |
| enum gpio_result result; | |
| if (aux_engine->funcs->is_engine_available) { | |
| /*check whether SW could use the engine*/ | |
| if (!aux_engine->funcs->is_engine_available(aux_engine)) { | |
| return false; | |
| } | |
| } | |
| result = dal_ddc_open(ddc, GPIO_MODE_HARDWARE, | |
| GPIO_DDC_CONFIG_TYPE_MODE_AUX); | |
| if (result != GPIO_RESULT_OK) | |
| return false; | |
| if (!aux_engine->funcs->acquire_engine(aux_engine)) { | |
| dal_ddc_close(ddc); | |
| return false; | |
| } | |
| engine->ddc = ddc; | |
| return true; | |
| } | |
| struct read_command_context { | |
| uint8_t *buffer; | |
| uint32_t current_read_length; | |
| uint32_t offset; | |
| enum i2caux_transaction_status status; | |
| struct aux_request_transaction_data request; | |
| struct aux_reply_transaction_data reply; | |
| uint8_t returned_byte; | |
| uint32_t timed_out_retry_aux; | |
| uint32_t invalid_reply_retry_aux; | |
| uint32_t defer_retry_aux; | |
| uint32_t defer_retry_i2c; | |
| uint32_t invalid_reply_retry_aux_on_ack; | |
| bool transaction_complete; | |
| bool operation_succeeded; | |
| }; | |
| static void process_read_reply( | |
| struct aux_engine *engine, | |
| struct read_command_context *ctx) | |
| { | |
| engine->funcs->process_channel_reply(engine, &ctx->reply); | |
| switch (ctx->reply.status) { | |
| case AUX_TRANSACTION_REPLY_AUX_ACK: | |
| ctx->defer_retry_aux = 0; | |
| if (ctx->returned_byte > ctx->current_read_length) { | |
| ctx->status = | |
| I2CAUX_TRANSACTION_STATUS_FAILED_PROTOCOL_ERROR; | |
| ctx->operation_succeeded = false; | |
| } else if (ctx->returned_byte < ctx->current_read_length) { | |
| ctx->current_read_length -= ctx->returned_byte; | |
| ctx->offset += ctx->returned_byte; | |
| ++ctx->invalid_reply_retry_aux_on_ack; | |
| if (ctx->invalid_reply_retry_aux_on_ack > | |
| AUX_INVALID_REPLY_RETRY_COUNTER) { | |
| ctx->status = | |
| I2CAUX_TRANSACTION_STATUS_FAILED_PROTOCOL_ERROR; | |
| ctx->operation_succeeded = false; | |
| } | |
| } else { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_SUCCEEDED; | |
| ctx->transaction_complete = true; | |
| ctx->operation_succeeded = true; | |
| } | |
| break; | |
| case AUX_TRANSACTION_REPLY_AUX_NACK: | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_NACK; | |
| ctx->operation_succeeded = false; | |
| break; | |
| case AUX_TRANSACTION_REPLY_AUX_DEFER: | |
| ++ctx->defer_retry_aux; | |
| if (ctx->defer_retry_aux > AUX_DEFER_RETRY_COUNTER) { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT; | |
| ctx->operation_succeeded = false; | |
| } | |
| break; | |
| case AUX_TRANSACTION_REPLY_I2C_DEFER: | |
| ctx->defer_retry_aux = 0; | |
| ++ctx->defer_retry_i2c; | |
| if (ctx->defer_retry_i2c > AUX_DEFER_RETRY_COUNTER) { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT; | |
| ctx->operation_succeeded = false; | |
| } | |
| break; | |
| default: | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_UNKNOWN; | |
| ctx->operation_succeeded = false; | |
| } | |
| } | |
| static void process_read_request( | |
| struct aux_engine *engine, | |
| struct read_command_context *ctx) | |
| { | |
| enum aux_channel_operation_result operation_result; | |
| engine->funcs->submit_channel_request(engine, &ctx->request); | |
| operation_result = engine->funcs->get_channel_status( | |
| engine, &ctx->returned_byte); | |
| switch (operation_result) { | |
| case AUX_CHANNEL_OPERATION_SUCCEEDED: | |
| if (ctx->returned_byte > ctx->current_read_length) { | |
| ctx->status = | |
| I2CAUX_TRANSACTION_STATUS_FAILED_PROTOCOL_ERROR; | |
| ctx->operation_succeeded = false; | |
| } else { | |
| ctx->timed_out_retry_aux = 0; | |
| ctx->invalid_reply_retry_aux = 0; | |
| ctx->reply.length = ctx->returned_byte; | |
| ctx->reply.data = ctx->buffer; | |
| process_read_reply(engine, ctx); | |
| } | |
| break; | |
| case AUX_CHANNEL_OPERATION_FAILED_INVALID_REPLY: | |
| ++ctx->invalid_reply_retry_aux; | |
| if (ctx->invalid_reply_retry_aux > | |
| AUX_INVALID_REPLY_RETRY_COUNTER) { | |
| ctx->status = | |
| I2CAUX_TRANSACTION_STATUS_FAILED_PROTOCOL_ERROR; | |
| ctx->operation_succeeded = false; | |
| } else | |
| udelay(400); | |
| break; | |
| case AUX_CHANNEL_OPERATION_FAILED_TIMEOUT: | |
| ++ctx->timed_out_retry_aux; | |
| if (ctx->timed_out_retry_aux > AUX_TIMED_OUT_RETRY_COUNTER) { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT; | |
| ctx->operation_succeeded = false; | |
| } else { | |
| /* DP 1.2a, table 2-58: | |
| * "S3: AUX Request CMD PENDING: | |
| * retry 3 times, with 400usec wait on each" | |
| * The HW timeout is set to 550usec, | |
| * so we should not wait here */ | |
| } | |
| break; | |
| default: | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_UNKNOWN; | |
| ctx->operation_succeeded = false; | |
| } | |
| } | |
| static bool read_command( | |
| struct aux_engine *engine, | |
| struct i2caux_transaction_request *request, | |
| bool middle_of_transaction) | |
| { | |
| struct read_command_context ctx; | |
| ctx.buffer = request->payload.data; | |
| ctx.current_read_length = request->payload.length; | |
| ctx.offset = 0; | |
| ctx.timed_out_retry_aux = 0; | |
| ctx.invalid_reply_retry_aux = 0; | |
| ctx.defer_retry_aux = 0; | |
| ctx.defer_retry_i2c = 0; | |
| ctx.invalid_reply_retry_aux_on_ack = 0; | |
| ctx.transaction_complete = false; | |
| ctx.operation_succeeded = true; | |
| if (request->payload.address_space == | |
| I2CAUX_TRANSACTION_ADDRESS_SPACE_DPCD) { | |
| ctx.request.type = AUX_TRANSACTION_TYPE_DP; | |
| ctx.request.action = I2CAUX_TRANSACTION_ACTION_DP_READ; | |
| ctx.request.address = request->payload.address; | |
| } else if (request->payload.address_space == | |
| I2CAUX_TRANSACTION_ADDRESS_SPACE_I2C) { | |
| ctx.request.type = AUX_TRANSACTION_TYPE_I2C; | |
| ctx.request.action = middle_of_transaction ? | |
| I2CAUX_TRANSACTION_ACTION_I2C_READ_MOT : | |
| I2CAUX_TRANSACTION_ACTION_I2C_READ; | |
| ctx.request.address = request->payload.address >> 1; | |
| } else { | |
| /* in DAL2, there was no return in such case */ | |
| BREAK_TO_DEBUGGER(); | |
| return false; | |
| } | |
| ctx.request.delay = 0; | |
| do { | |
| memset(ctx.buffer + ctx.offset, 0, ctx.current_read_length); | |
| ctx.request.data = ctx.buffer + ctx.offset; | |
| ctx.request.length = ctx.current_read_length; | |
| process_read_request(engine, &ctx); | |
| request->status = ctx.status; | |
| if (ctx.operation_succeeded && !ctx.transaction_complete) | |
| if (ctx.request.type == AUX_TRANSACTION_TYPE_I2C) | |
| msleep(engine->delay); | |
| } while (ctx.operation_succeeded && !ctx.transaction_complete); | |
| return ctx.operation_succeeded; | |
| } | |
| struct write_command_context { | |
| bool mot; | |
| uint8_t *buffer; | |
| uint32_t current_write_length; | |
| enum i2caux_transaction_status status; | |
| struct aux_request_transaction_data request; | |
| struct aux_reply_transaction_data reply; | |
| uint8_t returned_byte; | |
| uint32_t timed_out_retry_aux; | |
| uint32_t invalid_reply_retry_aux; | |
| uint32_t defer_retry_aux; | |
| uint32_t defer_retry_i2c; | |
| uint32_t max_defer_retry; | |
| uint32_t ack_m_retry; | |
| uint8_t reply_data[DEFAULT_AUX_MAX_DATA_SIZE]; | |
| bool transaction_complete; | |
| bool operation_succeeded; | |
| }; | |
| static void process_write_reply( | |
| struct aux_engine *engine, | |
| struct write_command_context *ctx) | |
| { | |
| engine->funcs->process_channel_reply(engine, &ctx->reply); | |
| switch (ctx->reply.status) { | |
| case AUX_TRANSACTION_REPLY_AUX_ACK: | |
| ctx->operation_succeeded = true; | |
| if (ctx->returned_byte) { | |
| ctx->request.action = ctx->mot ? | |
| I2CAUX_TRANSACTION_ACTION_I2C_STATUS_REQUEST_MOT : | |
| I2CAUX_TRANSACTION_ACTION_I2C_STATUS_REQUEST; | |
| ctx->current_write_length = 0; | |
| ++ctx->ack_m_retry; | |
| if (ctx->ack_m_retry > AUX_DEFER_RETRY_COUNTER) { | |
| ctx->status = | |
| I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT; | |
| ctx->operation_succeeded = false; | |
| } else | |
| udelay(300); | |
| } else { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_SUCCEEDED; | |
| ctx->defer_retry_aux = 0; | |
| ctx->ack_m_retry = 0; | |
| ctx->transaction_complete = true; | |
| } | |
| break; | |
| case AUX_TRANSACTION_REPLY_AUX_NACK: | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_NACK; | |
| ctx->operation_succeeded = false; | |
| break; | |
| case AUX_TRANSACTION_REPLY_AUX_DEFER: | |
| ++ctx->defer_retry_aux; | |
| if (ctx->defer_retry_aux > ctx->max_defer_retry) { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT; | |
| ctx->operation_succeeded = false; | |
| } | |
| break; | |
| case AUX_TRANSACTION_REPLY_I2C_DEFER: | |
| ctx->defer_retry_aux = 0; | |
| ctx->current_write_length = 0; | |
| ctx->request.action = ctx->mot ? | |
| I2CAUX_TRANSACTION_ACTION_I2C_STATUS_REQUEST_MOT : | |
| I2CAUX_TRANSACTION_ACTION_I2C_STATUS_REQUEST; | |
| ++ctx->defer_retry_i2c; | |
| if (ctx->defer_retry_i2c > ctx->max_defer_retry) { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT; | |
| ctx->operation_succeeded = false; | |
| } | |
| break; | |
| default: | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_UNKNOWN; | |
| ctx->operation_succeeded = false; | |
| } | |
| } | |
| static void process_write_request( | |
| struct aux_engine *engine, | |
| struct write_command_context *ctx) | |
| { | |
| enum aux_channel_operation_result operation_result; | |
| engine->funcs->submit_channel_request(engine, &ctx->request); | |
| operation_result = engine->funcs->get_channel_status( | |
| engine, &ctx->returned_byte); | |
| switch (operation_result) { | |
| case AUX_CHANNEL_OPERATION_SUCCEEDED: | |
| ctx->timed_out_retry_aux = 0; | |
| ctx->invalid_reply_retry_aux = 0; | |
| ctx->reply.length = ctx->returned_byte; | |
| ctx->reply.data = ctx->reply_data; | |
| process_write_reply(engine, ctx); | |
| break; | |
| case AUX_CHANNEL_OPERATION_FAILED_INVALID_REPLY: | |
| ++ctx->invalid_reply_retry_aux; | |
| if (ctx->invalid_reply_retry_aux > | |
| AUX_INVALID_REPLY_RETRY_COUNTER) { | |
| ctx->status = | |
| I2CAUX_TRANSACTION_STATUS_FAILED_PROTOCOL_ERROR; | |
| ctx->operation_succeeded = false; | |
| } else | |
| udelay(400); | |
| break; | |
| case AUX_CHANNEL_OPERATION_FAILED_TIMEOUT: | |
| ++ctx->timed_out_retry_aux; | |
| if (ctx->timed_out_retry_aux > AUX_TIMED_OUT_RETRY_COUNTER) { | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT; | |
| ctx->operation_succeeded = false; | |
| } else { | |
| /* DP 1.2a, table 2-58: | |
| * "S3: AUX Request CMD PENDING: | |
| * retry 3 times, with 400usec wait on each" | |
| * The HW timeout is set to 550usec, | |
| * so we should not wait here */ | |
| } | |
| break; | |
| default: | |
| ctx->status = I2CAUX_TRANSACTION_STATUS_UNKNOWN; | |
| ctx->operation_succeeded = false; | |
| } | |
| } | |
| static bool write_command( | |
| struct aux_engine *engine, | |
| struct i2caux_transaction_request *request, | |
| bool middle_of_transaction) | |
| { | |
| struct write_command_context ctx; | |
| ctx.mot = middle_of_transaction; | |
| ctx.buffer = request->payload.data; | |
| ctx.current_write_length = request->payload.length; | |
| ctx.timed_out_retry_aux = 0; | |
| ctx.invalid_reply_retry_aux = 0; | |
| ctx.defer_retry_aux = 0; | |
| ctx.defer_retry_i2c = 0; | |
| ctx.ack_m_retry = 0; | |
| ctx.transaction_complete = false; | |
| ctx.operation_succeeded = true; | |
| if (request->payload.address_space == | |
| I2CAUX_TRANSACTION_ADDRESS_SPACE_DPCD) { | |
| ctx.request.type = AUX_TRANSACTION_TYPE_DP; | |
| ctx.request.action = I2CAUX_TRANSACTION_ACTION_DP_WRITE; | |
| ctx.request.address = request->payload.address; | |
| } else if (request->payload.address_space == | |
| I2CAUX_TRANSACTION_ADDRESS_SPACE_I2C) { | |
| ctx.request.type = AUX_TRANSACTION_TYPE_I2C; | |
| ctx.request.action = middle_of_transaction ? | |
| I2CAUX_TRANSACTION_ACTION_I2C_WRITE_MOT : | |
| I2CAUX_TRANSACTION_ACTION_I2C_WRITE; | |
| ctx.request.address = request->payload.address >> 1; | |
| } else { | |
| /* in DAL2, there was no return in such case */ | |
| BREAK_TO_DEBUGGER(); | |
| return false; | |
| } | |
| ctx.request.delay = 0; | |
| ctx.max_defer_retry = | |
| (engine->max_defer_write_retry > AUX_DEFER_RETRY_COUNTER) ? | |
| engine->max_defer_write_retry : AUX_DEFER_RETRY_COUNTER; | |
| do { | |
| ctx.request.data = ctx.buffer; | |
| ctx.request.length = ctx.current_write_length; | |
| process_write_request(engine, &ctx); | |
| request->status = ctx.status; | |
| if (ctx.operation_succeeded && !ctx.transaction_complete) | |
| if (ctx.request.type == AUX_TRANSACTION_TYPE_I2C) | |
| msleep(engine->delay); | |
| } while (ctx.operation_succeeded && !ctx.transaction_complete); | |
| return ctx.operation_succeeded; | |
| } | |
| static bool end_of_transaction_command( | |
| struct aux_engine *engine, | |
| struct i2caux_transaction_request *request) | |
| { | |
| struct i2caux_transaction_request dummy_request; | |
| uint8_t dummy_data; | |
| /* [tcheng] We only need to send the stop (read with MOT = 0) | |
| * for I2C-over-Aux, not native AUX */ | |
| if (request->payload.address_space != | |
| I2CAUX_TRANSACTION_ADDRESS_SPACE_I2C) | |
| return false; | |
| dummy_request.operation = request->operation; | |
| dummy_request.payload.address_space = request->payload.address_space; | |
| dummy_request.payload.address = request->payload.address; | |
| /* | |
| * Add a dummy byte due to some receiver quirk | |
| * where one byte is sent along with MOT = 0. | |
| * Ideally this should be 0. | |
| */ | |
| dummy_request.payload.length = 0; | |
| dummy_request.payload.data = &dummy_data; | |
| if (request->operation == I2CAUX_TRANSACTION_READ) | |
| return read_command(engine, &dummy_request, false); | |
| else | |
| return write_command(engine, &dummy_request, false); | |
| /* according Syed, it does not need now DoDummyMOT */ | |
| } | |
| bool dal_aux_engine_submit_request( | |
| struct engine *engine, | |
| struct i2caux_transaction_request *request, | |
| bool middle_of_transaction) | |
| { | |
| struct aux_engine *aux_engine = FROM_ENGINE(engine); | |
| bool result; | |
| bool mot_used = true; | |
| switch (request->operation) { | |
| case I2CAUX_TRANSACTION_READ: | |
| result = read_command(aux_engine, request, mot_used); | |
| break; | |
| case I2CAUX_TRANSACTION_WRITE: | |
| result = write_command(aux_engine, request, mot_used); | |
| break; | |
| default: | |
| result = false; | |
| } | |
| /* [tcheng] | |
| * need to send stop for the last transaction to free up the AUX | |
| * if the above command fails, this would be the last transaction */ | |
| if (!middle_of_transaction || !result) | |
| end_of_transaction_command(aux_engine, request); | |
| /* mask AUX interrupt */ | |
| return result; | |
| } | |
| void dal_aux_engine_construct( | |
| struct aux_engine *engine, | |
| struct dc_context *ctx) | |
| { | |
| dal_i2caux_construct_engine(&engine->base, ctx); | |
| engine->delay = 0; | |
| engine->max_defer_write_retry = 0; | |
| } | |
| void dal_aux_engine_destruct( | |
| struct aux_engine *engine) | |
| { | |
| dal_i2caux_destruct_engine(&engine->base); | |
| } |