/* * 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) #define DC_LOGGER \ engine->base.ctx->logger 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 { ctx->current_read_length = ctx->returned_byte; 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; case AUX_TRANSACTION_REPLY_HPD_DISCON: ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_HPD_DISCON; 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; case AUX_CHANNEL_OPERATION_FAILED_HPD_DISCON: ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_HPD_DISCON; ctx->operation_succeeded = false; 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); if (request->payload.address_space == I2CAUX_TRANSACTION_ADDRESS_SPACE_DPCD) { DC_LOG_I2C_AUX("READ: addr:0x%x value:0x%x Result:%d", request->payload.address, request->payload.data[0], ctx.operation_succeeded); } request->payload.length = ctx.reply.length; 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; case AUX_TRANSACTION_REPLY_HPD_DISCON: ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_HPD_DISCON; 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; case AUX_CHANNEL_OPERATION_FAILED_HPD_DISCON: ctx->status = I2CAUX_TRANSACTION_STATUS_FAILED_HPD_DISCON; ctx->operation_succeeded = false; 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); if (request->payload.address_space == I2CAUX_TRANSACTION_ADDRESS_SPACE_DPCD) { DC_LOG_I2C_AUX("WRITE: addr:0x%x value:0x%x Result:%d", request->payload.address, request->payload.data[0], ctx.operation_succeeded); } 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); }