Implements low-level communication layer over I2C and Aux lines using GPIO handles. Signed-off-by: Harry Wentland <harry.wentland@xxxxxxx> Reviewed-by: Alex Deucher <alexander.deucher@xxxxxxx> --- drivers/gpu/drm/amd/dal/dc/i2caux/Makefile | 33 + drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.c | 567 ++++++++++++ drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.h | 119 +++ .../amd/dal/dc/i2caux/dce110/aux_engine_dce110.c | 788 +++++++++++++++++ .../amd/dal/dc/i2caux/dce110/aux_engine_dce110.h | 56 ++ .../i2caux/dce110/i2c_generic_hw_engine_dce110.h | 25 + .../dal/dc/i2caux/dce110/i2c_hw_engine_dce110.c | 954 +++++++++++++++++++++ .../dal/dc/i2caux/dce110/i2c_hw_engine_dce110.h | 58 ++ .../dal/dc/i2caux/dce110/i2c_sw_engine_dce110.c | 172 ++++ .../dal/dc/i2caux/dce110/i2c_sw_engine_dce110.h | 43 + .../drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.c | 266 ++++++ .../drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.h | 39 + .../amd/dal/dc/i2caux/diagnostics/i2caux_diag.c | 112 +++ .../amd/dal/dc/i2caux/diagnostics/i2caux_diag.h | 33 + drivers/gpu/drm/amd/dal/dc/i2caux/engine.h | 129 +++ drivers/gpu/drm/amd/dal/dc/i2caux/engine_base.c | 67 ++ drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.c | 121 +++ drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.h | 113 +++ .../drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.c | 286 ++++++ .../drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.h | 77 ++ drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.c | 246 ++++++ drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.h | 80 ++ drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.c | 614 +++++++++++++ drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.h | 81 ++ drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.c | 529 ++++++++++++ drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.h | 123 +++ 26 files changed, 5731 insertions(+) create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/Makefile create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_generic_hw_engine_dce110.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/engine.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/engine_base.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.h create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.c create mode 100644 drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.h diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/Makefile b/drivers/gpu/drm/amd/dal/dc/i2caux/Makefile new file mode 100644 index 000000000000..390d83d649f5 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for the 'i2c' sub-component of DAL. +# It provides the control and status of HW i2c engine of the adapter. + +I2CAUX = aux_engine.o engine_base.o i2caux.o i2c_engine.o \ + i2c_generic_hw_engine.o i2c_hw_engine.o i2c_sw_engine.o + +AMD_DAL_I2CAUX = $(addprefix $(AMDDALPATH)/dc/i2caux/,$(I2CAUX)) + +AMD_DAL_FILES += $(AMD_DAL_I2CAUX) + + +############################################################################### +# DCE 11x family +############################################################################### +ifdef CONFIG_DRM_AMD_DAL_DCE11_0 +I2CAUX_DCE110 = i2caux_dce110.o i2c_sw_engine_dce110.o i2c_hw_engine_dce110.o \ + aux_engine_dce110.o + +AMD_DAL_I2CAUX_DCE110 = $(addprefix $(AMDDALPATH)/dc/i2caux/dce110/,$(I2CAUX_DCE110)) + +AMD_DAL_FILES += $(AMD_DAL_I2CAUX_DCE110) +endif + +############################################################################### +# Diagnostics on FPGA +############################################################################### +I2CAUX_DIAG = i2caux_diag.o + +AMD_DAL_I2CAUX_DIAG = $(addprefix $(AMDDALPATH)/dc/i2caux/diagnostics/,$(I2CAUX_DIAG)) + +AMD_DAL_FILES += $(AMD_DAL_I2CAUX_DIAG) + diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.c b/drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.c new file mode 100644 index 000000000000..7042d10ee7dc --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.c @@ -0,0 +1,567 @@ +/* + * 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; + + 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; + uint8_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 + dm_delay_in_microseconds(engine->base.ctx, 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 { + dm_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) + dm_sleep_in_milliseconds(engine->base.ctx, engine->delay); + } while (ctx.operation_succeeded && !ctx.transaction_complete); + + return ctx.operation_succeeded; +} + +struct write_command_context { + bool mot; + + uint8_t *buffer; + uint8_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 + dm_delay_in_microseconds(engine->base.ctx, 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 + dm_delay_in_microseconds(engine->base.ctx, 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) + dm_sleep_in_milliseconds(engine->base.ctx, 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; +} + +bool dal_aux_engine_construct( + struct aux_engine *engine, + struct dc_context *ctx) +{ + if (!dal_i2caux_construct_engine(&engine->base, ctx)) + return false; + engine->delay = 0; + engine->max_defer_write_retry = 0; + return true; +} + +void dal_aux_engine_destruct( + struct aux_engine *engine) +{ + dal_i2caux_destruct_engine(&engine->base); +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.h b/drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.h new file mode 100644 index 000000000000..474f5e911f49 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/aux_engine.h @@ -0,0 +1,119 @@ +/* + * 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 + * + */ + +#ifndef __DAL_AUX_ENGINE_H__ +#define __DAL_AUX_ENGINE_H__ + +enum aux_transaction_type { + AUX_TRANSACTION_TYPE_DP, + AUX_TRANSACTION_TYPE_I2C +}; + +struct aux_request_transaction_data { + enum aux_transaction_type type; + enum i2caux_transaction_action action; + /* 20-bit AUX channel transaction address */ + uint32_t address; + /* delay, in 100-microsecond units */ + uint8_t delay; + uint8_t length; + uint8_t *data; +}; + +enum aux_transaction_reply { + AUX_TRANSACTION_REPLY_AUX_ACK = 0x00, + AUX_TRANSACTION_REPLY_AUX_NACK = 0x01, + AUX_TRANSACTION_REPLY_AUX_DEFER = 0x02, + + AUX_TRANSACTION_REPLY_I2C_ACK = 0x00, + AUX_TRANSACTION_REPLY_I2C_NACK = 0x10, + AUX_TRANSACTION_REPLY_I2C_DEFER = 0x20, + + AUX_TRANSACTION_REPLY_INVALID = 0xFF +}; + +struct aux_reply_transaction_data { + enum aux_transaction_reply status; + uint8_t length; + uint8_t *data; +}; + +enum aux_channel_operation_result { + AUX_CHANNEL_OPERATION_SUCCEEDED, + AUX_CHANNEL_OPERATION_FAILED_REASON_UNKNOWN, + AUX_CHANNEL_OPERATION_FAILED_INVALID_REPLY, + AUX_CHANNEL_OPERATION_FAILED_TIMEOUT +}; + +struct aux_engine; + +struct aux_engine_funcs { + void (*destroy)( + struct aux_engine **ptr); + bool (*acquire_engine)( + struct aux_engine *engine); + void (*configure)( + struct aux_engine *engine, + union aux_config cfg); + bool (*start_gtc_sync)( + struct aux_engine *engine); + void (*stop_gtc_sync)( + struct aux_engine *engine); + void (*submit_channel_request)( + struct aux_engine *engine, + struct aux_request_transaction_data *request); + void (*process_channel_reply)( + struct aux_engine *engine, + struct aux_reply_transaction_data *reply); + enum aux_channel_operation_result (*get_channel_status)( + struct aux_engine *engine, + uint8_t *returned_bytes); +}; + +struct aux_engine { + struct engine base; + const struct aux_engine_funcs *funcs; + /* following values are expressed in milliseconds */ + uint32_t delay; + uint32_t max_defer_write_retry; +}; + +bool dal_aux_engine_construct( + struct aux_engine *engine, + struct dc_context *ctx); + +void dal_aux_engine_destruct( + struct aux_engine *engine); +bool dal_aux_engine_submit_request( + struct engine *ptr, + struct i2caux_transaction_request *request, + bool middle_of_transaction); +bool dal_aux_engine_acquire( + struct engine *ptr, + struct ddc *ddc); +enum i2caux_engine_type dal_aux_engine_get_engine_type( + const struct engine *engine); + +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.c b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.c new file mode 100644 index 000000000000..f9c55435b5a0 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.c @@ -0,0 +1,788 @@ +/* + * 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 "../aux_engine.h" + +/* + * Header of this unit + */ + +#include "aux_engine_dce110.h" + +/* + * Post-requisites: headers required by this unit + */ +#include "dce/dce_11_0_d.h" +#include "dce/dce_11_0_sh_mask.h" + +/* + * This unit + */ + +/* + * @brief + * Cast 'struct aux_engine *' + * to 'struct aux_engine_dce110 *' + */ +#define FROM_AUX_ENGINE(ptr) \ + container_of((ptr), struct aux_engine_dce110, base) + +/* + * @brief + * Cast 'struct engine *' + * to 'struct aux_engine_dce110 *' + */ +#define FROM_ENGINE(ptr) \ + FROM_AUX_ENGINE(container_of((ptr), struct aux_engine, base)) + +static void release_engine( + struct engine *engine) +{ + struct aux_engine_dce110 *aux_engine = FROM_ENGINE(engine); + + const uint32_t addr = aux_engine->addr.aux_arb_control; + + uint32_t value = dm_read_reg(engine->ctx, addr); + + set_reg_field_value( + value, + 1, + AUX_ARB_CONTROL, + AUX_SW_DONE_USING_AUX_REG); + + dm_write_reg(engine->ctx, addr, value); +} + +static void destruct( + struct aux_engine_dce110 *engine); + +static void destroy( + struct aux_engine **aux_engine) +{ + struct aux_engine_dce110 *engine = FROM_AUX_ENGINE(*aux_engine); + + destruct(engine); + + dm_free((*aux_engine)->base.ctx, engine); + + *aux_engine = NULL; +} + +#define SW_CAN_ACCESS_AUX 1 + +static bool acquire_engine( + struct aux_engine *engine) +{ + struct aux_engine_dce110 *aux_engine = FROM_AUX_ENGINE(engine); + uint32_t value; + uint32_t field; + + /* enable AUX before request SW to access AUX */ + { + const uint32_t addr = aux_engine->addr.aux_control; + + value = dm_read_reg(engine->base.ctx, addr); + + field = get_reg_field_value( + value, + AUX_CONTROL, + AUX_EN); + + if (field == 0) { + uint8_t counter = 0; + + set_reg_field_value( + value, + 1, + AUX_CONTROL, + AUX_EN); + + /*DP_AUX block as part of the enable sequence*/ + set_reg_field_value( + value, + 1, + AUX_CONTROL, + AUX_RESET); + + dm_write_reg(engine->base.ctx, addr, value); + + /*poll HW to make sure reset it done*/ + do { + dm_delay_in_microseconds(engine->base.ctx, 1); + + value = dm_read_reg(engine->base.ctx, addr); + + field = get_reg_field_value( + value, + AUX_CONTROL, + AUX_RESET_DONE); + + counter++; + + } while ((field == 0) && (counter < 11)); + + set_reg_field_value( + value, + 0, + AUX_CONTROL, + AUX_RESET); + + dm_write_reg(engine->base.ctx, addr, value); + + counter = 0; + + do { + dm_delay_in_microseconds(engine->base.ctx, 1); + + value = dm_read_reg(engine->base.ctx, addr); + + field = get_reg_field_value( + value, + AUX_CONTROL, + AUX_RESET_DONE); + + counter++; + + } while ((field == 1) && (counter < 11)); + } /*if (field)*/ + } + + /* request SW to access AUX */ + { + const uint32_t addr = aux_engine->addr.aux_arb_control; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 1, + AUX_ARB_CONTROL, + AUX_SW_USE_AUX_REG_REQ); + + dm_write_reg(engine->base.ctx, addr, value); + + value = dm_read_reg(engine->base.ctx, addr); + + field = get_reg_field_value( + value, + AUX_ARB_CONTROL, + AUX_REG_RW_CNTL_STATUS); + + return field == SW_CAN_ACCESS_AUX; + } +} + +static void configure( + struct aux_engine *engine, + union aux_config cfg) +{ + struct aux_engine_dce110 *aux_engine = FROM_AUX_ENGINE(engine); + + const uint32_t addr = aux_engine->addr.aux_control; + + uint32_t value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + (0 != cfg.bits.ALLOW_AUX_WHEN_HPD_LOW), + AUX_CONTROL, + AUX_IGNORE_HPD_DISCON); + + dm_write_reg(engine->base.ctx, addr, value); +} + +static bool start_gtc_sync( + struct aux_engine *engine) +{ + /*TODO*/ + return false; +} + +static void stop_gtc_sync( + struct aux_engine *engine) +{ + /*TODO*/ +} + +#define COMPOSE_AUX_SW_DATA_16_20(command, address) \ + ((command) | ((0xF0000 & (address)) >> 16)) + +#define COMPOSE_AUX_SW_DATA_8_15(address) \ + ((0xFF00 & (address)) >> 8) + +#define COMPOSE_AUX_SW_DATA_0_7(address) \ + (0xFF & (address)) + +static void submit_channel_request( + struct aux_engine *engine, + struct aux_request_transaction_data *request) +{ + struct aux_engine_dce110 *aux_engine = FROM_AUX_ENGINE(engine); + uint32_t value; + uint32_t length; + + bool is_write = + ((request->type == AUX_TRANSACTION_TYPE_DP) && + (request->action == I2CAUX_TRANSACTION_ACTION_DP_WRITE)) || + ((request->type == AUX_TRANSACTION_TYPE_I2C) && + ((request->action == I2CAUX_TRANSACTION_ACTION_I2C_WRITE) || + (request->action == I2CAUX_TRANSACTION_ACTION_I2C_WRITE_MOT))); + + /* clear_aux_error */ + { + const uint32_t addr = mmAUXN_IMPCAL; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 1, + AUXN_IMPCAL, + AUXN_CALOUT_ERROR_AK); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + 0, + AUXN_IMPCAL, + AUXN_CALOUT_ERROR_AK); + + dm_write_reg(engine->base.ctx, addr, value); + } + { + const uint32_t addr = mmAUXP_IMPCAL; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 1, + AUXP_IMPCAL, + AUXP_CALOUT_ERROR_AK); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + 0, + AUXP_IMPCAL, + AUXP_CALOUT_ERROR_AK); + + dm_write_reg(engine->base.ctx, addr, value); + } + + /* force_default_calibrate */ + { + const uint32_t addr = mmAUXN_IMPCAL; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 1, + AUXN_IMPCAL, + AUXN_IMPCAL_ENABLE); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + 0, + AUXN_IMPCAL, + AUXN_IMPCAL_OVERRIDE_ENABLE); + + dm_write_reg(engine->base.ctx, addr, value); + } + { + const uint32_t addr = mmAUXP_IMPCAL; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 1, + AUXP_IMPCAL, + AUXP_IMPCAL_OVERRIDE_ENABLE); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + 0, + AUXP_IMPCAL, + AUXP_IMPCAL_OVERRIDE_ENABLE); + + dm_write_reg(engine->base.ctx, addr, value); + } + + /* set the delay and the number of bytes to write */ + { + const uint32_t addr = aux_engine->addr.aux_sw_control; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + request->delay, + AUX_SW_CONTROL, + AUX_SW_START_DELAY); + + /* The length include + * the 4 bit header and the 20 bit address + * (that is 3 byte). + * If the requested length is non zero this means + * an addition byte specifying the length is required. */ + + length = request->length ? 4 : 3; + if (is_write) + length += request->length; + + set_reg_field_value( + value, + length, + AUX_SW_CONTROL, + AUX_SW_WR_BYTES); + + dm_write_reg(engine->base.ctx, addr, value); + } + + /* program action and address and payload data (if 'is_write') */ + { + const uint32_t addr = aux_engine->addr.aux_sw_data; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 0, + AUX_SW_DATA, + AUX_SW_INDEX); + + set_reg_field_value( + value, + 0, + AUX_SW_DATA, + AUX_SW_DATA_RW); + + set_reg_field_value( + value, + 1, + AUX_SW_DATA, + AUX_SW_AUTOINCREMENT_DISABLE); + + set_reg_field_value( + value, + COMPOSE_AUX_SW_DATA_16_20( + request->action, request->address), + AUX_SW_DATA, + AUX_SW_DATA); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + 0, + AUX_SW_DATA, + AUX_SW_AUTOINCREMENT_DISABLE); + + set_reg_field_value( + value, + COMPOSE_AUX_SW_DATA_8_15(request->address), + AUX_SW_DATA, + AUX_SW_DATA); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + COMPOSE_AUX_SW_DATA_0_7(request->address), + AUX_SW_DATA, + AUX_SW_DATA); + + dm_write_reg(engine->base.ctx, addr, value); + + if (request->length) { + set_reg_field_value( + value, + request->length - 1, + AUX_SW_DATA, + AUX_SW_DATA); + + dm_write_reg(engine->base.ctx, addr, value); + } + + if (is_write) { + /* Load the HW buffer with the Data to be sent. + * This is relevant for write operation. + * For read, the data recived data will be + * processed in process_channel_reply(). */ + uint32_t i = 0; + + while (i < request->length) { + + set_reg_field_value( + value, + request->data[i], + AUX_SW_DATA, + AUX_SW_DATA); + + dm_write_reg( + engine->base.ctx, addr, value); + + ++i; + } + } + } + + { + const uint32_t addr = aux_engine->addr.aux_interrupt_control; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 1, + AUX_INTERRUPT_CONTROL, + AUX_SW_DONE_ACK); + + dm_write_reg(engine->base.ctx, addr, value); + } + + { + const uint32_t addr = aux_engine->addr.aux_sw_control; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 1, + AUX_SW_CONTROL, + AUX_SW_GO); + + dm_write_reg(engine->base.ctx, addr, value); + } +} + +static void process_channel_reply( + struct aux_engine *engine, + struct aux_reply_transaction_data *reply) +{ + struct aux_engine_dce110 *aux_engine = FROM_AUX_ENGINE(engine); + + /* Need to do a read to get the number of bytes to process + * Alternatively, this information can be passed - + * but that causes coupling which isn't good either. */ + + uint32_t bytes_replied; + uint32_t value; + + { + const uint32_t addr = aux_engine->addr.aux_sw_status; + + value = dm_read_reg(engine->base.ctx, addr); + + bytes_replied = get_reg_field_value( + value, + AUX_SW_STATUS, + AUX_SW_REPLY_BYTE_COUNT); + } + + if (bytes_replied) { + uint32_t reply_result; + + const uint32_t addr = aux_engine->addr.aux_sw_data; + + value = dm_read_reg(engine->base.ctx, addr); + + set_reg_field_value( + value, + 0, + AUX_SW_DATA, + AUX_SW_INDEX); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + 1, + AUX_SW_DATA, + AUX_SW_AUTOINCREMENT_DISABLE); + + dm_write_reg(engine->base.ctx, addr, value); + + set_reg_field_value( + value, + 1, + AUX_SW_DATA, + AUX_SW_DATA_RW); + + dm_write_reg(engine->base.ctx, addr, value); + + value = dm_read_reg(engine->base.ctx, addr); + + reply_result = get_reg_field_value( + value, + AUX_SW_DATA, + AUX_SW_DATA); + + reply_result = reply_result >> 4; + + switch (reply_result) { + case 0: /* ACK */ { + uint32_t i = 0; + + /* first byte was already used + * to get the command status */ + --bytes_replied; + + while (i < bytes_replied) { + value = dm_read_reg( + engine->base.ctx, addr); + + reply->data[i] = get_reg_field_value( + value, + AUX_SW_DATA, + AUX_SW_DATA); + + ++i; + } + + reply->status = AUX_TRANSACTION_REPLY_AUX_ACK; + } + break; + case 1: /* NACK */ + reply->status = AUX_TRANSACTION_REPLY_AUX_NACK; + break; + case 2: /* DEFER */ + reply->status = AUX_TRANSACTION_REPLY_AUX_DEFER; + break; + case 4: /* AUX ACK / I2C NACK */ + reply->status = AUX_TRANSACTION_REPLY_I2C_NACK; + break; + case 8: /* AUX ACK / I2C DEFER */ + reply->status = AUX_TRANSACTION_REPLY_I2C_DEFER; + break; + default: + reply->status = AUX_TRANSACTION_REPLY_INVALID; + } + } else { + /* Need to handle an error case... + * hopefully, upper layer function won't call this function + * if the number of bytes in the reply was 0 + * because there was surely an error that was asserted + * that should have been handled + * for hot plug case, this could happens*/ + if (!(value & AUX_SW_STATUS__AUX_SW_HPD_DISCON_MASK)) + ASSERT_CRITICAL(false); + } +} + +static enum aux_channel_operation_result get_channel_status( + struct aux_engine *engine, + uint8_t *returned_bytes) +{ + struct aux_engine_dce110 *aux_engine = FROM_AUX_ENGINE(engine); + + const uint32_t addr = aux_engine->addr.aux_sw_status; + + uint32_t value; + uint32_t aux_sw_done; + + if (returned_bytes == NULL) { + /*caller pass NULL pointer*/ + ASSERT_CRITICAL(false); + return AUX_CHANNEL_OPERATION_FAILED_REASON_UNKNOWN; + } + *returned_bytes = 0; + + /* poll to make sure that SW_DONE is asserted */ + { + uint32_t time_elapsed = 0; + + do { + value = dm_read_reg(engine->base.ctx, addr); + + aux_sw_done = get_reg_field_value( + value, + AUX_SW_STATUS, + AUX_SW_DONE); + + if (aux_sw_done) + break; + + dm_delay_in_microseconds(engine->base.ctx, 10); + + time_elapsed += 10; + } while (time_elapsed < aux_engine->timeout_period); + + + } + + /* Note that the following bits are set in 'status.bits' + * during CTS 4.2.1.2: + * AUX_SW_RX_MIN_COUNT_VIOL, AUX_SW_RX_INVALID_STOP, + * AUX_SW_RX_RECV_NO_DET, AUX_SW_RX_RECV_INVALID_H. + * + * AUX_SW_RX_MIN_COUNT_VIOL is an internal, + * HW debugging bit and should be ignored. */ + if (aux_sw_done) { + if (get_reg_field_value( + value, + AUX_SW_STATUS, + AUX_SW_RX_TIMEOUT_STATE) || + get_reg_field_value( + value, + AUX_SW_STATUS, + AUX_SW_RX_TIMEOUT)) + return AUX_CHANNEL_OPERATION_FAILED_TIMEOUT; + else if (get_reg_field_value( + value, + AUX_SW_STATUS, + AUX_SW_RX_INVALID_STOP)) + return AUX_CHANNEL_OPERATION_FAILED_INVALID_REPLY; + + *returned_bytes = get_reg_field_value( + value, + AUX_SW_STATUS, + AUX_SW_REPLY_BYTE_COUNT); + if (*returned_bytes == 0) + return + AUX_CHANNEL_OPERATION_FAILED_INVALID_REPLY; + else { + *returned_bytes -= 1; + return AUX_CHANNEL_OPERATION_SUCCEEDED; + } + } else { + /*time_elapsed >= aux_engine->timeout_period */ + if (!(value & AUX_SW_STATUS__AUX_SW_HPD_DISCON_MASK)) + ASSERT_CRITICAL(false); + return AUX_CHANNEL_OPERATION_FAILED_TIMEOUT; + } +} + +static const int32_t aux_channel_offset[] = { + mmDP_AUX0_AUX_CONTROL - mmDP_AUX0_AUX_CONTROL, + mmDP_AUX1_AUX_CONTROL - mmDP_AUX0_AUX_CONTROL, + mmDP_AUX2_AUX_CONTROL - mmDP_AUX0_AUX_CONTROL, + mmDP_AUX3_AUX_CONTROL - mmDP_AUX0_AUX_CONTROL, + mmDP_AUX4_AUX_CONTROL - mmDP_AUX0_AUX_CONTROL, + mmDP_AUX5_AUX_CONTROL - mmDP_AUX0_AUX_CONTROL +}; + +static const struct aux_engine_funcs aux_engine_funcs = { + .destroy = destroy, + .acquire_engine = acquire_engine, + .configure = configure, + .start_gtc_sync = start_gtc_sync, + .stop_gtc_sync = stop_gtc_sync, + .submit_channel_request = submit_channel_request, + .process_channel_reply = process_channel_reply, + .get_channel_status = get_channel_status, +}; + +static const struct engine_funcs engine_funcs = { + .release_engine = release_engine, + .submit_request = dal_aux_engine_submit_request, + .keep_power_up_count = dal_i2caux_keep_power_up_count, + .get_engine_type = dal_aux_engine_get_engine_type, + .acquire = dal_aux_engine_acquire, +}; + +static bool construct( + struct aux_engine_dce110 *engine, + const struct aux_engine_dce110_init_data *aux_init_data) +{ + int32_t offset; + + if (aux_init_data->engine_id >= + sizeof(aux_channel_offset) / sizeof(int32_t)) { + ASSERT_CRITICAL(false); + return false; + } + + if (!dal_aux_engine_construct( + &engine->base, aux_init_data->ctx)) { + ASSERT_CRITICAL(false); + return false; + } + engine->base.base.funcs = &engine_funcs; + engine->base.funcs = &aux_engine_funcs; + offset = aux_channel_offset[aux_init_data->engine_id]; + engine->addr.aux_control = mmAUX_CONTROL + offset; + engine->addr.aux_arb_control = mmAUX_ARB_CONTROL + offset; + engine->addr.aux_sw_data = mmAUX_SW_DATA + offset; + engine->addr.aux_sw_control = mmAUX_SW_CONTROL + offset; + engine->addr.aux_interrupt_control = mmAUX_INTERRUPT_CONTROL + offset; + engine->addr.aux_sw_status = mmAUX_SW_STATUS + offset; + engine->addr.aux_gtc_sync_control = mmAUX_GTC_SYNC_CONTROL + offset; + engine->addr.aux_gtc_sync_status = mmAUX_GTC_SYNC_STATUS + offset; + engine->addr.aux_gtc_sync_controller_status = + mmAUX_GTC_SYNC_CONTROLLER_STATUS + offset; + + engine->timeout_period = aux_init_data->timeout_period; + + return true; +} + +static void destruct( + struct aux_engine_dce110 *engine) +{ + dal_aux_engine_destruct(&engine->base); +} + +struct aux_engine *dal_aux_engine_dce110_create( + const struct aux_engine_dce110_init_data *aux_init_data) +{ + struct aux_engine_dce110 *engine; + + if (!aux_init_data) { + ASSERT_CRITICAL(false); + return NULL; + } + + engine = dm_alloc(aux_init_data->ctx, sizeof(*engine)); + + if (!engine) { + ASSERT_CRITICAL(false); + return NULL; + } + + if (construct(engine, aux_init_data)) + return &engine->base; + + ASSERT_CRITICAL(false); + + dm_free(aux_init_data->ctx, engine); + + return NULL; +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.h b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.h new file mode 100644 index 000000000000..ec6899ed5b40 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/aux_engine_dce110.h @@ -0,0 +1,56 @@ +/* + * 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 + * + */ + +#ifndef __DAL_AUX_ENGINE_DCE110_H__ +#define __DAL_AUX_ENGINE_DCE110_H__ + +#include "../aux_engine.h" + +struct aux_engine_dce110 { + struct aux_engine base; + struct { + uint32_t aux_control; + uint32_t aux_arb_control; + uint32_t aux_sw_data; + uint32_t aux_sw_control; + uint32_t aux_interrupt_control; + uint32_t aux_sw_status; + uint32_t aux_gtc_sync_control; + uint32_t aux_gtc_sync_status; + uint32_t aux_gtc_sync_controller_status; + } addr; + uint32_t timeout_period; +}; + +struct aux_engine_dce110_init_data { + uint32_t engine_id; + uint32_t timeout_period; + struct dc_context *ctx; +}; + +struct aux_engine *dal_aux_engine_dce110_create( + const struct aux_engine_dce110_init_data *aux_init_data); + +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_generic_hw_engine_dce110.h b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_generic_hw_engine_dce110.h new file mode 100644 index 000000000000..e6b6a972a0e5 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_generic_hw_engine_dce110.h @@ -0,0 +1,25 @@ +/* + * 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 + * + */ + diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.c b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.c new file mode 100644 index 000000000000..2517f44f7297 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.c @@ -0,0 +1,954 @@ +/* + * 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" +#include "include/logger_interface.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" +#include "../i2c_generic_hw_engine.h" +/* + * Header of this unit + */ + +#include "i2c_hw_engine_dce110.h" + +/* + * Post-requisites: headers required by this unit + */ +#include "dce/dce_11_0_d.h" +#include "dce/dce_11_0_sh_mask.h" + +/* + * This unit + */ + +enum dc_i2c_status { + DC_I2C_STATUS__DC_I2C_STATUS_IDLE, + DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_SW, + DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_HW +}; + +enum dc_i2c_arbitration { + DC_I2C_ARBITRATION__DC_I2C_SW_PRIORITY_NORMAL, + DC_I2C_ARBITRATION__DC_I2C_SW_PRIORITY_HIGH +}; + +enum { + /* No timeout in HW + * (timeout implemented in SW by querying status) */ + I2C_SETUP_TIME_LIMIT = 255, + I2C_HW_BUFFER_SIZE = 144 +}; + +/* + * @brief + * Cast pointer to 'struct i2c_hw_engine *' + * to pointer 'struct i2c_hw_engine_dce110 *' + */ +#define FROM_I2C_HW_ENGINE(ptr) \ + container_of((ptr), struct i2c_hw_engine_dce110, base) +/* + * @brief + * Cast pointer to 'struct i2c_engine *' + * to pointer to 'struct i2c_hw_engine_dce110 *' + */ +#define FROM_I2C_ENGINE(ptr) \ + FROM_I2C_HW_ENGINE(container_of((ptr), struct i2c_hw_engine, base)) + +/* + * @brief + * Cast pointer to 'struct engine *' + * to 'pointer to struct i2c_hw_engine_dce110 *' + */ +#define FROM_ENGINE(ptr) \ + FROM_I2C_ENGINE(container_of((ptr), struct i2c_engine, base)) + + +static void disable_i2c_hw_engine( + struct i2c_hw_engine_dce110 *engine) +{ + const uint32_t addr = engine->addr.DC_I2C_DDCX_SETUP; + uint32_t value = 0; + + struct dc_context *ctx = NULL; + + ctx = engine->base.base.base.ctx; + + value = dm_read_reg(ctx, addr); + + set_reg_field_value( + value, + 0, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_ENABLE); + + dm_write_reg(ctx, addr, value); +} + +static void release_engine( + struct engine *engine) +{ + struct i2c_hw_engine_dce110 *hw_engine = FROM_ENGINE(engine); + + struct i2c_engine *base = NULL; + bool safe_to_reset; + uint32_t value = 0; + + base = &hw_engine->base.base; + + /* Restore original HW engine speed */ + + base->funcs->set_speed(base, hw_engine->base.original_speed); + + /* Release I2C */ + { + value = dm_read_reg(engine->ctx, mmDC_I2C_ARBITRATION); + + set_reg_field_value( + value, + 1, + DC_I2C_ARBITRATION, + DC_I2C_SW_DONE_USING_I2C_REG); + + dm_write_reg(engine->ctx, mmDC_I2C_ARBITRATION, value); + } + + /* Reset HW engine */ + { + uint32_t i2c_sw_status = 0; + + value = dm_read_reg(engine->ctx, mmDC_I2C_SW_STATUS); + + i2c_sw_status = get_reg_field_value( + value, + DC_I2C_SW_STATUS, + DC_I2C_SW_STATUS); + /* if used by SW, safe to reset */ + safe_to_reset = (i2c_sw_status == 1); + } + { + value = dm_read_reg(engine->ctx, mmDC_I2C_CONTROL); + + if (safe_to_reset) + set_reg_field_value( + value, + 1, + DC_I2C_CONTROL, + DC_I2C_SOFT_RESET); + + set_reg_field_value( + value, + 1, + DC_I2C_CONTROL, + DC_I2C_SW_STATUS_RESET); + + dm_write_reg(engine->ctx, mmDC_I2C_CONTROL, value); + } + + /* HW I2c engine - clock gating feature */ + if (!hw_engine->engine_keep_power_up_count) + disable_i2c_hw_engine(hw_engine); +} + +static void keep_power_up_count( + struct engine *engine, + bool keep_power_up) +{ + struct i2c_hw_engine_dce110 *hw_engine = FROM_ENGINE(engine); + + if (keep_power_up) + ++hw_engine->engine_keep_power_up_count; + else { + --hw_engine->engine_keep_power_up_count; + + if (!hw_engine->engine_keep_power_up_count) + disable_i2c_hw_engine(hw_engine); + } +} + +static bool setup_engine( + struct i2c_engine *i2c_engine) +{ + uint32_t value = 0; + struct i2c_hw_engine_dce110 *engine = FROM_I2C_ENGINE(i2c_engine); + + /* Program pin select */ + { + const uint32_t addr = mmDC_I2C_CONTROL; + + value = dm_read_reg(i2c_engine->base.ctx, addr); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_GO); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_SOFT_RESET); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_SEND_RESET); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_SW_STATUS_RESET); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_TRANSACTION_COUNT); + + set_reg_field_value( + value, + engine->engine_id, + DC_I2C_CONTROL, + DC_I2C_DDC_SELECT); + + + dm_write_reg(i2c_engine->base.ctx, addr, value); + } + + /* Program time limit */ + { + const uint32_t addr = engine->addr.DC_I2C_DDCX_SETUP; + + value = dm_read_reg(i2c_engine->base.ctx, addr); + + set_reg_field_value( + value, + I2C_SETUP_TIME_LIMIT, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_TIME_LIMIT); + + set_reg_field_value( + value, + 1, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_ENABLE); + + dm_write_reg(i2c_engine->base.ctx, addr, value); + } + + /* Program HW priority + * set to High - interrupt software I2C at any time + * Enable restart of SW I2C that was interrupted by HW + * disable queuing of software while I2C is in use by HW */ + { + value = dm_read_reg(i2c_engine->base.ctx, + mmDC_I2C_ARBITRATION); + + set_reg_field_value( + value, + 0, + DC_I2C_ARBITRATION, + DC_I2C_NO_QUEUED_SW_GO); + + set_reg_field_value( + value, + DC_I2C_ARBITRATION__DC_I2C_SW_PRIORITY_NORMAL, + DC_I2C_ARBITRATION, + DC_I2C_SW_PRIORITY); + + dm_write_reg(i2c_engine->base.ctx, + mmDC_I2C_ARBITRATION, value); + } + + return true; +} + +static uint32_t get_speed( + const struct i2c_engine *i2c_engine) +{ + const struct i2c_hw_engine_dce110 *engine = FROM_I2C_ENGINE(i2c_engine); + + const uint32_t addr = engine->addr.DC_I2C_DDCX_SPEED; + + uint32_t pre_scale = 0; + + uint32_t value = dm_read_reg(i2c_engine->base.ctx, addr); + + pre_scale = get_reg_field_value( + value, + DC_I2C_DDC1_SPEED, + DC_I2C_DDC1_PRESCALE); + + /* [anaumov] it seems following is unnecessary */ + /*ASSERT(value.bits.DC_I2C_DDC1_PRESCALE);*/ + + return pre_scale ? + engine->reference_frequency / pre_scale : + engine->base.default_speed; +} + +static void set_speed( + struct i2c_engine *i2c_engine, + uint32_t speed) +{ + struct i2c_hw_engine_dce110 *engine = FROM_I2C_ENGINE(i2c_engine); + + if (speed) { + const uint32_t addr = engine->addr.DC_I2C_DDCX_SPEED; + + uint32_t value = dm_read_reg(i2c_engine->base.ctx, addr); + + set_reg_field_value( + value, + engine->reference_frequency / speed, + DC_I2C_DDC1_SPEED, + DC_I2C_DDC1_PRESCALE); + + set_reg_field_value( + value, + 2, + DC_I2C_DDC1_SPEED, + DC_I2C_DDC1_THRESHOLD); + + /*DCE11, HW add 100Khz support for I2c*/ + if (speed > 50) { + set_reg_field_value( + value, + 2, + DC_I2C_DDC1_SPEED, + DC_I2C_DDC1_START_STOP_TIMING_CNTL); + } else { + set_reg_field_value( + value, + 1, + DC_I2C_DDC1_SPEED, + DC_I2C_DDC1_START_STOP_TIMING_CNTL); + } + + dm_write_reg(i2c_engine->base.ctx, addr, value); + } +} + +static inline void reset_hw_engine(struct engine *engine) +{ + uint32_t value = dm_read_reg(engine->ctx, mmDC_I2C_CONTROL); + + set_reg_field_value( + value, + 1, + DC_I2C_CONTROL, + DC_I2C_SOFT_RESET); + + set_reg_field_value( + value, + 1, + DC_I2C_CONTROL, + DC_I2C_SW_STATUS_RESET); + + dm_write_reg(engine->ctx, mmDC_I2C_CONTROL, value); +} + +static bool is_hw_busy(struct engine *engine) +{ + uint32_t i2c_sw_status = 0; + + uint32_t value = dm_read_reg(engine->ctx, mmDC_I2C_SW_STATUS); + + i2c_sw_status = get_reg_field_value( + value, + DC_I2C_SW_STATUS, + DC_I2C_SW_STATUS); + + if (i2c_sw_status == DC_I2C_STATUS__DC_I2C_STATUS_IDLE) + return false; + + reset_hw_engine(engine); + + value = dm_read_reg(engine->ctx, mmDC_I2C_SW_STATUS); + + i2c_sw_status = get_reg_field_value( + value, + DC_I2C_SW_STATUS, + DC_I2C_SW_STATUS); + + return i2c_sw_status != DC_I2C_STATUS__DC_I2C_STATUS_IDLE; +} + +/* + * @brief + * DC_GPIO_DDC MM register offsets + */ +static const uint32_t transaction_addr[] = { + mmDC_I2C_TRANSACTION0, + mmDC_I2C_TRANSACTION1, + mmDC_I2C_TRANSACTION2, + mmDC_I2C_TRANSACTION3 +}; + +static bool process_transaction( + struct i2c_hw_engine_dce110 *engine, + struct i2c_request_transaction_data *request) +{ + uint8_t length = request->length; + uint8_t *buffer = request->data; + + bool last_transaction = false; + uint32_t value = 0; + + struct dc_context *ctx = NULL; + + ctx = engine->base.base.base.ctx; + + { + const uint32_t addr = + transaction_addr[engine->transaction_count]; + + value = dm_read_reg(ctx, addr); + + set_reg_field_value( + value, + 1, + DC_I2C_TRANSACTION0, + DC_I2C_STOP_ON_NACK0); + + set_reg_field_value( + value, + 1, + DC_I2C_TRANSACTION0, + DC_I2C_START0); + + + if ((engine->transaction_count == 3) || + (request->action == I2CAUX_TRANSACTION_ACTION_I2C_WRITE) || + (request->action & I2CAUX_TRANSACTION_ACTION_I2C_READ)) { + + set_reg_field_value( + value, + 1, + DC_I2C_TRANSACTION0, + DC_I2C_STOP0); + + last_transaction = true; + } else + set_reg_field_value( + value, + 0, + DC_I2C_TRANSACTION0, + DC_I2C_STOP0); + + set_reg_field_value( + value, + (0 != (request->action & + I2CAUX_TRANSACTION_ACTION_I2C_READ)), + DC_I2C_TRANSACTION0, + DC_I2C_RW0); + + set_reg_field_value( + value, + length, + DC_I2C_TRANSACTION0, + DC_I2C_COUNT0); + + dm_write_reg(ctx, addr, value); + } + + /* Write the I2C address and I2C data + * into the hardware circular buffer, one byte per entry. + * As an example, the 7-bit I2C slave address for CRT monitor + * for reading DDC/EDID information is 0b1010001. + * For an I2C send operation, the LSB must be programmed to 0; + * for I2C receive operation, the LSB must be programmed to 1. */ + + { + value = 0; + + set_reg_field_value( + value, + false, + DC_I2C_DATA, + DC_I2C_DATA_RW); + + set_reg_field_value( + value, + request->address, + DC_I2C_DATA, + DC_I2C_DATA); + + if (engine->transaction_count == 0) { + set_reg_field_value( + value, + 0, + DC_I2C_DATA, + DC_I2C_INDEX); + + /*enable index write*/ + set_reg_field_value( + value, + 1, + DC_I2C_DATA, + DC_I2C_INDEX_WRITE); + + engine->buffer_used_write = 0; + } + + dm_write_reg(ctx, mmDC_I2C_DATA, value); + + engine->buffer_used_write++; + + if (!(request->action & I2CAUX_TRANSACTION_ACTION_I2C_READ)) { + + set_reg_field_value( + value, + 0, + DC_I2C_DATA, + DC_I2C_INDEX_WRITE); + + while (length) { + + set_reg_field_value( + value, + *buffer++, + DC_I2C_DATA, + DC_I2C_DATA); + + dm_write_reg(ctx, mmDC_I2C_DATA, value); + + engine->buffer_used_write++; + --length; + } + } + } + + ++engine->transaction_count; + engine->buffer_used_bytes += length + 1; + + return last_transaction; +} + +static void execute_transaction( + struct i2c_hw_engine_dce110 *engine) +{ + uint32_t value = 0; + struct dc_context *ctx = NULL; + + ctx = engine->base.base.base.ctx; + + { + const uint32_t addr = engine->addr.DC_I2C_DDCX_SETUP; + + value = dm_read_reg(ctx, addr); + + set_reg_field_value( + value, + 0, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_DATA_DRIVE_EN); + + set_reg_field_value( + value, + 0, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_CLK_DRIVE_EN); + + set_reg_field_value( + value, + 0, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_DATA_DRIVE_SEL); + + set_reg_field_value( + value, + 0, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_INTRA_TRANSACTION_DELAY); + + set_reg_field_value( + value, + 0, + DC_I2C_DDC1_SETUP, + DC_I2C_DDC1_INTRA_BYTE_DELAY); + + dm_write_reg(ctx, addr, value); + } + + { + const uint32_t addr = mmDC_I2C_CONTROL; + + value = dm_read_reg(ctx, addr); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_SOFT_RESET); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_SW_STATUS_RESET); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_SEND_RESET); + + set_reg_field_value( + value, + 0, + DC_I2C_CONTROL, + DC_I2C_GO); + + set_reg_field_value( + value, + engine->transaction_count - 1, + DC_I2C_CONTROL, + DC_I2C_TRANSACTION_COUNT); + + dm_write_reg(ctx, addr, value); + } + + /* start I2C transfer */ + { + const uint32_t addr = mmDC_I2C_CONTROL; + + value = dm_read_reg(ctx, addr); + + set_reg_field_value( + value, + 1, + DC_I2C_CONTROL, + DC_I2C_GO); + + dm_write_reg(ctx, addr, value); + } + + /* all transactions were executed and HW buffer became empty + * (even though it actually happens when status becomes DONE) */ + engine->transaction_count = 0; + engine->buffer_used_bytes = 0; +} + +static void submit_channel_request( + struct i2c_engine *engine, + struct i2c_request_transaction_data *request) +{ + request->status = I2C_CHANNEL_OPERATION_SUCCEEDED; + + if (!process_transaction(FROM_I2C_ENGINE(engine), request)) + return; + + if (is_hw_busy(&engine->base)) { + request->status = I2C_CHANNEL_OPERATION_ENGINE_BUSY; + return; + } + + execute_transaction(FROM_I2C_ENGINE(engine)); +} + +static void process_channel_reply( + struct i2c_engine *engine, + struct i2c_reply_transaction_data *reply) +{ + uint8_t length = reply->length; + uint8_t *buffer = reply->data; + + struct i2c_hw_engine_dce110 *i2c_hw_engine_dce110 = + FROM_I2C_ENGINE(engine); + + uint32_t value = 0; + + /*set index*/ + set_reg_field_value( + value, + i2c_hw_engine_dce110->buffer_used_write, + DC_I2C_DATA, + DC_I2C_INDEX); + + set_reg_field_value( + value, + 1, + DC_I2C_DATA, + DC_I2C_DATA_RW); + + set_reg_field_value( + value, + 1, + DC_I2C_DATA, + DC_I2C_INDEX_WRITE); + + dm_write_reg(engine->base.ctx, mmDC_I2C_DATA, value); + + while (length) { + /* after reading the status, + * if the I2C operation executed successfully + * (i.e. DC_I2C_STATUS_DONE = 1) then the I2C controller + * should read data bytes from I2C circular data buffer */ + + value = dm_read_reg(engine->base.ctx, mmDC_I2C_DATA); + + *buffer++ = get_reg_field_value( + value, + DC_I2C_DATA, + DC_I2C_DATA); + + --length; + } +} + +static enum i2c_channel_operation_result get_channel_status( + struct i2c_engine *engine, + uint8_t *returned_bytes) +{ + uint32_t i2c_sw_status = 0; + uint32_t value = dm_read_reg(engine->base.ctx, mmDC_I2C_SW_STATUS); + + i2c_sw_status = get_reg_field_value( + value, + DC_I2C_SW_STATUS, + DC_I2C_SW_STATUS); + + if (i2c_sw_status == DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_SW) + return I2C_CHANNEL_OPERATION_ENGINE_BUSY; + else if (value & DC_I2C_SW_STATUS__DC_I2C_SW_STOPPED_ON_NACK_MASK) + return I2C_CHANNEL_OPERATION_NO_RESPONSE; + else if (value & DC_I2C_SW_STATUS__DC_I2C_SW_TIMEOUT_MASK) + return I2C_CHANNEL_OPERATION_TIMEOUT; + else if (value & DC_I2C_SW_STATUS__DC_I2C_SW_ABORTED_MASK) + return I2C_CHANNEL_OPERATION_FAILED; + else if (value & DC_I2C_SW_STATUS__DC_I2C_SW_DONE_MASK) + return I2C_CHANNEL_OPERATION_SUCCEEDED; + + /* in DAL2, I2C_RESULT_OK was returned */ + return I2C_CHANNEL_OPERATION_NOT_STARTED; +} + +static uint8_t get_hw_buffer_available_size( + const struct i2c_hw_engine *engine) +{ + return I2C_HW_BUFFER_SIZE - + FROM_I2C_HW_ENGINE(engine)->buffer_used_bytes; +} + +static uint32_t get_transaction_timeout( + const struct i2c_hw_engine *engine, + uint32_t length) +{ + uint32_t speed = engine->base.funcs->get_speed(&engine->base); + + uint32_t period_timeout; + uint32_t num_of_clock_stretches; + + if (!speed) + return 0; + + period_timeout = (1000 * TRANSACTION_TIMEOUT_IN_I2C_CLOCKS) / speed; + + num_of_clock_stretches = 1 + (length << 3) + 1; + num_of_clock_stretches += + (FROM_I2C_HW_ENGINE(engine)->buffer_used_bytes << 3) + + (FROM_I2C_HW_ENGINE(engine)->transaction_count << 1); + + return period_timeout * num_of_clock_stretches; +} + +static void destroy( + struct i2c_engine **i2c_engine) +{ + struct i2c_hw_engine_dce110 *engine_dce110 = + FROM_I2C_ENGINE(*i2c_engine); + + dal_i2c_hw_engine_destruct(&engine_dce110->base); + + dm_free((*i2c_engine)->base.ctx, engine_dce110); + + *i2c_engine = NULL; +} +/* + * @brief + * DC_I2C_DDC1_SETUP MM register offsets + * + * @note + * The indices of this offset array are DDC engine IDs + */ +static const int32_t ddc_setup_offset[] = { + + mmDC_I2C_DDC1_SETUP - mmDC_I2C_DDC1_SETUP, /* DDC Engine 1 */ + mmDC_I2C_DDC2_SETUP - mmDC_I2C_DDC1_SETUP, /* DDC Engine 2 */ + mmDC_I2C_DDC3_SETUP - mmDC_I2C_DDC1_SETUP, /* DDC Engine 3 */ + mmDC_I2C_DDC4_SETUP - mmDC_I2C_DDC1_SETUP, /* DDC Engine 4 */ + mmDC_I2C_DDC5_SETUP - mmDC_I2C_DDC1_SETUP, /* DDC Engine 5 */ + mmDC_I2C_DDC6_SETUP - mmDC_I2C_DDC1_SETUP, /* DDC Engine 6 */ + mmDC_I2C_DDCVGA_SETUP - mmDC_I2C_DDC1_SETUP /* DDC Engine 7 */ +}; + +/* + * @brief + * DC_I2C_DDC1_SPEED MM register offsets + * + * @note + * The indices of this offset array are DDC engine IDs + */ +static const int32_t ddc_speed_offset[] = { + mmDC_I2C_DDC1_SPEED - mmDC_I2C_DDC1_SPEED, /* DDC Engine 1 */ + mmDC_I2C_DDC2_SPEED - mmDC_I2C_DDC1_SPEED, /* DDC Engine 2 */ + mmDC_I2C_DDC3_SPEED - mmDC_I2C_DDC1_SPEED, /* DDC Engine 3 */ + mmDC_I2C_DDC4_SPEED - mmDC_I2C_DDC1_SPEED, /* DDC Engine 4 */ + mmDC_I2C_DDC5_SPEED - mmDC_I2C_DDC1_SPEED, /* DDC Engine 5 */ + mmDC_I2C_DDC6_SPEED - mmDC_I2C_DDC1_SPEED, /* DDC Engine 6 */ + mmDC_I2C_DDCVGA_SPEED - mmDC_I2C_DDC1_SPEED /* DDC Engine 7 */ +}; + +static const struct i2c_engine_funcs i2c_engine_funcs = { + .destroy = destroy, + .get_speed = get_speed, + .set_speed = set_speed, + .setup_engine = setup_engine, + .submit_channel_request = submit_channel_request, + .process_channel_reply = process_channel_reply, + .get_channel_status = get_channel_status, + .acquire_engine = dal_i2c_hw_engine_acquire_engine, +}; + +static const struct engine_funcs engine_funcs = { + .release_engine = release_engine, + .keep_power_up_count = keep_power_up_count, + .get_engine_type = dal_i2c_hw_engine_get_engine_type, + .acquire = dal_i2c_engine_acquire, + .submit_request = dal_i2c_hw_engine_submit_request, +}; + +static const struct i2c_hw_engine_funcs i2c_hw_engine_funcs = { + .get_hw_buffer_available_size = get_hw_buffer_available_size, + .get_transaction_timeout = get_transaction_timeout, + .wait_on_operation_result = dal_i2c_hw_engine_wait_on_operation_result, +}; + +static bool construct( + struct i2c_hw_engine_dce110 *engine_dce110, + const struct i2c_hw_engine_dce110_create_arg *arg) +{ + uint32_t xtal_ref_div = 0; + uint32_t value = 0; + + /*ddc_setup_offset of dce80 and dce110 have the same register name + * but different offset. Do not need different array*/ + if (arg->engine_id >= sizeof(ddc_setup_offset) / sizeof(int32_t)) + return false; + if (arg->engine_id >= sizeof(ddc_speed_offset) / sizeof(int32_t)) + return false; + if (!arg->reference_frequency) + return false; + + if (!dal_i2c_hw_engine_construct(&engine_dce110->base, arg->ctx)) + return false; + + engine_dce110->base.base.base.funcs = &engine_funcs; + engine_dce110->base.base.funcs = &i2c_engine_funcs; + engine_dce110->base.funcs = &i2c_hw_engine_funcs; + engine_dce110->base.default_speed = arg->default_speed; + + engine_dce110->engine_id = arg->engine_id; + + engine_dce110->buffer_used_bytes = 0; + engine_dce110->transaction_count = 0; + engine_dce110->engine_keep_power_up_count = 1; + + /*values which are not included by arg*/ + engine_dce110->addr.DC_I2C_DDCX_SETUP = + mmDC_I2C_DDC1_SETUP + ddc_setup_offset[arg->engine_id]; + engine_dce110->addr.DC_I2C_DDCX_SPEED = + mmDC_I2C_DDC1_SPEED + ddc_speed_offset[arg->engine_id]; + + + value = dm_read_reg( + engine_dce110->base.base.base.ctx, + mmMICROSECOND_TIME_BASE_DIV); + + xtal_ref_div = get_reg_field_value( + value, + MICROSECOND_TIME_BASE_DIV, + XTAL_REF_DIV); + + if (xtal_ref_div == 0) { + dal_logger_write( + engine_dce110->base.base.base.ctx->logger, + LOG_MAJOR_WARNING, + LOG_MINOR_COMPONENT_I2C_AUX, + "Invalid base timer divider\n", + __func__); + xtal_ref_div = 2; + } + + /*Calculating Reference Clock by divding original frequency by + * XTAL_REF_DIV. + * At upper level, uint32_t reference_frequency = + * dal_i2caux_get_reference_clock(as) >> 1 + * which already divided by 2. So we need x2 to get original + * reference clock from ppll_info + */ + engine_dce110->reference_frequency = + (arg->reference_frequency * 2) / xtal_ref_div; + + + return true; +} + +struct i2c_engine *dal_i2c_hw_engine_dce110_create( + const struct i2c_hw_engine_dce110_create_arg *arg) +{ + struct i2c_hw_engine_dce110 *engine_dce10; + + if (!arg) { + ASSERT_CRITICAL(false); + return NULL; + } + + engine_dce10 = dm_alloc(arg->ctx, sizeof(struct i2c_hw_engine_dce110)); + + if (!engine_dce10) { + ASSERT_CRITICAL(false); + return NULL; + } + + if (construct(engine_dce10, arg)) + return &engine_dce10->base.base; + + ASSERT_CRITICAL(false); + + dm_free(arg->ctx, engine_dce10); + + return NULL; +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.h b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.h new file mode 100644 index 000000000000..fc2ae365f8ed --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_hw_engine_dce110.h @@ -0,0 +1,58 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_HW_ENGINE_DCE110_H__ +#define __DAL_I2C_HW_ENGINE_DCE110_H__ + +struct i2c_hw_engine_dce110 { + struct i2c_hw_engine base; + struct { + uint32_t DC_I2C_DDCX_SETUP; + uint32_t DC_I2C_DDCX_SPEED; + } addr; + uint32_t engine_id; + /* expressed in kilohertz */ + uint32_t reference_frequency; + /* number of bytes currently used in HW buffer */ + uint32_t buffer_used_bytes; + /* number of bytes used for write transaction in HW buffer + * - this will be used as the index to read from*/ + uint32_t buffer_used_write; + /* number of pending transactions (before GO) */ + uint32_t transaction_count; + uint32_t engine_keep_power_up_count; +}; + +struct i2c_hw_engine_dce110_create_arg { + uint32_t engine_id; + uint32_t reference_frequency; + uint32_t default_speed; + struct dc_context *ctx; +}; + +struct i2c_engine *dal_i2c_hw_engine_dce110_create( + const struct i2c_hw_engine_dce110_create_arg *arg); + +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.c b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.c new file mode 100644 index 000000000000..f060b25d1618 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.c @@ -0,0 +1,172 @@ +/* + * 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_sw_engine.h" + +/* + * Header of this unit + */ + +#include "i2c_sw_engine_dce110.h" + +/* + * Post-requisites: headers required by this unit + */ + +/* + * This unit + */ + +/* + * @brief + * Cast 'struct i2c_sw_engine *' + * to 'struct i2c_sw_engine_dce110 *' + */ +#define FROM_I2C_SW_ENGINE(ptr) \ + container_of((ptr), struct i2c_sw_engine_dce110, base) +/* + * @brief + * Cast 'struct i2c_engine *' + * to 'struct i2c_sw_engine_dce80 *' + */ +#define FROM_I2C_ENGINE(ptr) \ + FROM_I2C_SW_ENGINE(container_of((ptr), struct i2c_sw_engine, base)) + +/* + * @brief + * Cast 'struct engine *' + * to 'struct i2c_sw_engine_dce80 *' + */ +#define FROM_ENGINE(ptr) \ + FROM_I2C_ENGINE(container_of((ptr), struct i2c_engine, base)) + +static void release_engine( + struct engine *engine) +{ +} + +static void destruct( + struct i2c_sw_engine_dce110 *engine) +{ + dal_i2c_sw_engine_destruct(&engine->base); +} + +static void destroy( + struct i2c_engine **engine) +{ + struct i2c_sw_engine_dce110 *sw_engine = FROM_I2C_ENGINE(*engine); + + destruct(sw_engine); + + dm_free((*engine)->base.ctx, sw_engine); + + *engine = NULL; +} + +static bool acquire_engine( + struct i2c_engine *engine, + struct ddc *ddc_handle) +{ + return dal_i2caux_i2c_sw_engine_acquire_engine(engine, ddc_handle); +} + +static const struct i2c_engine_funcs i2c_engine_funcs = { + .acquire_engine = acquire_engine, + .destroy = destroy, + .get_speed = dal_i2c_sw_engine_get_speed, + .set_speed = dal_i2c_sw_engine_set_speed, + .setup_engine = dal_i2c_engine_setup_i2c_engine, + .submit_channel_request = dal_i2c_sw_engine_submit_channel_request, + .process_channel_reply = dal_i2c_engine_process_channel_reply, + .get_channel_status = dal_i2c_sw_engine_get_channel_status, +}; + +static const struct engine_funcs engine_funcs = { + .release_engine = release_engine, + .get_engine_type = dal_i2c_sw_engine_get_engine_type, + .acquire = dal_i2c_engine_acquire, + .submit_request = dal_i2c_sw_engine_submit_request, + .keep_power_up_count = dal_i2caux_keep_power_up_count, +}; + +static bool construct( + struct i2c_sw_engine_dce110 *engine_dce110, + const struct i2c_sw_engine_dce110_create_arg *arg_dce110) +{ + struct i2c_sw_engine_create_arg arg_base; + + arg_base.ctx = arg_dce110->ctx; + arg_base.default_speed = arg_dce110->default_speed; + + if (!dal_i2c_sw_engine_construct( + &engine_dce110->base, &arg_base)) { + ASSERT_CRITICAL(false); + return false; + } + + /*struct engine struct engine_funcs*/ + engine_dce110->base.base.base.funcs = &engine_funcs; + /*struct i2c_engine struct i2c_engine_funcs*/ + engine_dce110->base.base.funcs = &i2c_engine_funcs; + engine_dce110->base.default_speed = arg_dce110->default_speed; + engine_dce110->engine_id = arg_dce110->engine_id; + + return true; +} + +struct i2c_engine *dal_i2c_sw_engine_dce110_create( + const struct i2c_sw_engine_dce110_create_arg *arg) +{ + struct i2c_sw_engine_dce110 *engine_dce110; + + if (!arg) { + ASSERT_CRITICAL(false); + return NULL; + } + + engine_dce110 = dm_alloc(arg->ctx, sizeof(struct i2c_sw_engine_dce110)); + + if (!engine_dce110) { + ASSERT_CRITICAL(false); + return NULL; + } + + if (construct(engine_dce110, arg)) + return &engine_dce110->base.base; + + ASSERT_CRITICAL(false); + + dm_free(arg->ctx, engine_dce110); + + return NULL; +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.h b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.h new file mode 100644 index 000000000000..c48c61f540a8 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2c_sw_engine_dce110.h @@ -0,0 +1,43 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_SW_ENGINE_DCE110_H__ +#define __DAL_I2C_SW_ENGINE_DCE110_H__ + +struct i2c_sw_engine_dce110 { + struct i2c_sw_engine base; + uint32_t engine_id; +}; + +struct i2c_sw_engine_dce110_create_arg { + uint32_t engine_id; + uint32_t default_speed; + struct dc_context *ctx; +}; + +struct i2c_engine *dal_i2c_sw_engine_dce110_create( + const struct i2c_sw_engine_dce110_create_arg *arg); + +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.c b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.c new file mode 100644 index 000000000000..9c88762dbd0c --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.c @@ -0,0 +1,266 @@ +/* + * 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 "../i2caux.h" +#include "../engine.h" +#include "../i2c_engine.h" +#include "../i2c_sw_engine.h" +#include "../i2c_hw_engine.h" + +/* + * Header of this unit + */ +#include "i2caux_dce110.h" + +#include "i2c_sw_engine_dce110.h" +#include "i2c_hw_engine_dce110.h" +#include "aux_engine_dce110.h" + +/* + * Post-requisites: headers required by this unit + */ + +/* + * This unit + */ +/*cast pointer to struct i2caux TO pointer to struct i2caux_dce110*/ +#define FROM_I2C_AUX(ptr) \ + container_of((ptr), struct i2caux_dce110, base) + +static void destruct( + struct i2caux_dce110 *i2caux_dce110) +{ + dal_i2caux_destruct(&i2caux_dce110->base); +} + +static void destroy( + struct i2caux **i2c_engine) +{ + struct i2caux_dce110 *i2caux_dce110 = FROM_I2C_AUX(*i2c_engine); + + destruct(i2caux_dce110); + + dm_free((*i2c_engine)->ctx, i2caux_dce110); + + *i2c_engine = NULL; +} + +static struct i2c_engine *acquire_i2c_hw_engine( + struct i2caux *i2caux, + struct ddc *ddc) +{ + struct i2caux_dce110 *i2caux_dce110 = FROM_I2C_AUX(i2caux); + + struct i2c_engine *engine = NULL; + /* generic hw engine is not used for EDID read + * It may be needed for external i2c device, like thermal chip, + * TODO will be implemented when needed. + * check dce80 bool non_generic for generic hw engine; + */ + + if (!ddc) + return NULL; + + if (dal_ddc_is_hw_supported(ddc)) { + enum gpio_ddc_line line = dal_ddc_get_line(ddc); + + if (line < GPIO_DDC_LINE_COUNT) + engine = i2caux->i2c_hw_engines[line]; + } + + if (!engine) + return NULL; + + if (!i2caux_dce110->i2c_hw_buffer_in_use && + engine->base.funcs->acquire(&engine->base, ddc)) { + i2caux_dce110->i2c_hw_buffer_in_use = true; + return engine; + } + + return NULL; +} + +static void release_engine( + struct i2caux *i2caux, + struct engine *engine) +{ + struct i2caux_dce110 *i2caux_dce110 = FROM_I2C_AUX(i2caux); + + if (engine->funcs->get_engine_type(engine) == + I2CAUX_ENGINE_TYPE_I2C_DDC_HW) + i2caux_dce110->i2c_hw_buffer_in_use = false; + + dal_i2caux_release_engine(i2caux, engine); +} + +static const enum gpio_ddc_line hw_ddc_lines[] = { + GPIO_DDC_LINE_DDC1, + GPIO_DDC_LINE_DDC2, + GPIO_DDC_LINE_DDC3, + GPIO_DDC_LINE_DDC4, + GPIO_DDC_LINE_DDC5, + GPIO_DDC_LINE_DDC6, +}; + +static const enum gpio_ddc_line hw_aux_lines[] = { + GPIO_DDC_LINE_DDC1, + GPIO_DDC_LINE_DDC2, + GPIO_DDC_LINE_DDC3, + GPIO_DDC_LINE_DDC4, + GPIO_DDC_LINE_DDC5, + GPIO_DDC_LINE_DDC6, +}; + +/* function table */ +static const struct i2caux_funcs i2caux_funcs = { + .destroy = destroy, + .acquire_i2c_hw_engine = acquire_i2c_hw_engine, + .release_engine = release_engine, + .acquire_i2c_sw_engine = dal_i2caux_acquire_i2c_sw_engine, + .acquire_aux_engine = dal_i2caux_acquire_aux_engine, +}; + +static bool construct( + struct i2caux_dce110 *i2caux_dce110, + struct adapter_service *as, + struct dc_context *ctx) +{ + uint32_t i = 0; + uint32_t reference_frequency = 0; + bool use_i2c_sw_engine = false; + struct i2caux *base = NULL; + /*TODO: For CZ bring up, if dal_i2caux_get_reference_clock + * does not return 48KHz, we need hard coded for 48Khz. + * Some BIOS setting incorrect cause this + * For production, we always get value from BIOS*/ + reference_frequency = + dal_i2caux_get_reference_clock(as) >> 1; + + use_i2c_sw_engine = dal_adapter_service_is_feature_supported( + FEATURE_RESTORE_USAGE_I2C_SW_ENGINE); + + base = &i2caux_dce110->base; + + if (!dal_i2caux_construct(base, as, ctx)) { + ASSERT_CRITICAL(false); + return false; + } + + i2caux_dce110->base.funcs = &i2caux_funcs; + i2caux_dce110->i2c_hw_buffer_in_use = false; + /* Create I2C engines (DDC lines per connector) + * different I2C/AUX usage cases, DDC, Generic GPIO, AUX. + */ + do { + enum gpio_ddc_line line_id = hw_ddc_lines[i]; + + struct i2c_hw_engine_dce110_create_arg hw_arg_dce110; + + if (use_i2c_sw_engine) { + struct i2c_sw_engine_dce110_create_arg sw_arg; + + sw_arg.engine_id = i; + sw_arg.default_speed = base->default_i2c_sw_speed; + sw_arg.ctx = ctx; + base->i2c_sw_engines[line_id] = + dal_i2c_sw_engine_dce110_create(&sw_arg); + } + + hw_arg_dce110.engine_id = i; + hw_arg_dce110.reference_frequency = reference_frequency; + hw_arg_dce110.default_speed = base->default_i2c_hw_speed; + hw_arg_dce110.ctx = ctx; + + base->i2c_hw_engines[line_id] = + dal_i2c_hw_engine_dce110_create(&hw_arg_dce110); + + ++i; + } while (i < ARRAY_SIZE(hw_ddc_lines)); + + /* Create AUX engines for all lines which has assisted HW AUX + * 'i' (loop counter) used as DDC/AUX engine_id */ + + i = 0; + + do { + enum gpio_ddc_line line_id = hw_aux_lines[i]; + + struct aux_engine_dce110_init_data aux_init_data; + + aux_init_data.engine_id = i; + aux_init_data.timeout_period = base->aux_timeout_period; + aux_init_data.ctx = ctx; + + base->aux_engines[line_id] = + dal_aux_engine_dce110_create(&aux_init_data); + + ++i; + } while (i < ARRAY_SIZE(hw_aux_lines)); + + /*TODO Generic I2C SW and HW*/ + + return true; +} + +/* + * dal_i2caux_dce110_create + * + * @brief + * public interface to allocate memory for DCE11 I2CAUX + * + * @param + * struct adapter_service *as - [in] + * struct dc_context *ctx - [in] + * + * @return + * pointer to the base struct of DCE11 I2CAUX + */ +struct i2caux *dal_i2caux_dce110_create( + struct adapter_service *as, + struct dc_context *ctx) +{ + struct i2caux_dce110 *i2caux_dce110 = + dm_alloc(ctx, sizeof(struct i2caux_dce110)); + + if (!i2caux_dce110) { + ASSERT_CRITICAL(false); + return NULL; + } + + if (construct(i2caux_dce110, as, ctx)) + return &i2caux_dce110->base; + + ASSERT_CRITICAL(false); + + dm_free(ctx, i2caux_dce110); + + return NULL; +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.h b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.h new file mode 100644 index 000000000000..1a7ba1baf3d0 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/dce110/i2caux_dce110.h @@ -0,0 +1,39 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_AUX_DCE110_H__ +#define __DAL_I2C_AUX_DCE110_H__ + +struct i2caux_dce110 { + struct i2caux base; + /* indicate the I2C HW circular buffer is in use */ + bool i2c_hw_buffer_in_use; +}; + +struct i2caux *dal_i2caux_dce110_create( + struct adapter_service *as, + struct dc_context *ctx); + +#endif /* __DAL_I2C_AUX_DCE110_H__ */ diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.c b/drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.c new file mode 100644 index 000000000000..f4bc39d786c8 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.c @@ -0,0 +1,112 @@ +/* + * Copyright 2012-16 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 "../i2caux.h" +#include "../engine.h" +#include "../i2c_engine.h" +#include "../i2c_sw_engine.h" +#include "../i2c_hw_engine.h" + +/* + * Header of this unit + */ +#include "i2caux_diag.h" + +/* + * Post-requisites: headers required by this unit + */ + +/* + * This unit + */ + +static void destruct( + struct i2caux *i2caux) +{ + dal_i2caux_destruct(i2caux); +} + +static void destroy( + struct i2caux **i2c_engine) +{ + destruct(*i2c_engine); + + dm_free((*i2c_engine)->ctx, *i2c_engine); + + *i2c_engine = NULL; +} + + + +/* function table */ +static const struct i2caux_funcs i2caux_funcs = { + .destroy = destroy, + .acquire_i2c_hw_engine = NULL, + .release_engine = NULL, + .acquire_i2c_sw_engine = NULL, + .acquire_aux_engine = NULL, +}; + +static bool construct( + struct i2caux *i2caux, + struct adapter_service *as, + struct dc_context *ctx) +{ + if (!dal_i2caux_construct(i2caux, as, ctx)) { + ASSERT_CRITICAL(false); + return false; + } + + i2caux->funcs = &i2caux_funcs; + + return true; +} + +struct i2caux *dal_i2caux_diag_fpga_create( + struct adapter_service *as, + struct dc_context *ctx) +{ + struct i2caux *i2caux = dm_alloc(ctx, sizeof(struct i2caux)); + + if (!i2caux) { + ASSERT_CRITICAL(false); + return NULL; + } + + if (construct(i2caux, as, ctx)) + return i2caux; + + ASSERT_CRITICAL(false); + + dm_free(ctx, i2caux); + + return NULL; +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.h b/drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.h new file mode 100644 index 000000000000..3de250bd7660 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/diagnostics/i2caux_diag.h @@ -0,0 +1,33 @@ +/* + * Copyright 2012-16 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 + * + */ + +#ifndef __DAL_I2C_AUX_DIAG_FPGA_H__ +#define __DAL_I2C_AUX_DIAG_FPGA_H__ + +struct i2caux *dal_i2caux_diag_fpga_create( + struct adapter_service *as, + struct dc_context *ctx); + +#endif /* __DAL_I2C_AUX_DIAG_FPGA_H__ */ diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/engine.h b/drivers/gpu/drm/amd/dal/dc/i2caux/engine.h new file mode 100644 index 000000000000..d3635f884579 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/engine.h @@ -0,0 +1,129 @@ +/* + * 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 + * + */ + +#ifndef __DAL_ENGINE_H__ +#define __DAL_ENGINE_H__ + +enum i2caux_transaction_operation { + I2CAUX_TRANSACTION_READ, + I2CAUX_TRANSACTION_WRITE +}; + +enum i2caux_transaction_address_space { + I2CAUX_TRANSACTION_ADDRESS_SPACE_I2C = 1, + I2CAUX_TRANSACTION_ADDRESS_SPACE_DPCD +}; + +struct i2caux_transaction_payload { + enum i2caux_transaction_address_space address_space; + uint32_t address; + uint8_t length; + uint8_t *data; +}; + +enum i2caux_transaction_status { + I2CAUX_TRANSACTION_STATUS_UNKNOWN = (-1L), + I2CAUX_TRANSACTION_STATUS_SUCCEEDED, + I2CAUX_TRANSACTION_STATUS_FAILED_CHANNEL_BUSY, + I2CAUX_TRANSACTION_STATUS_FAILED_TIMEOUT, + I2CAUX_TRANSACTION_STATUS_FAILED_PROTOCOL_ERROR, + I2CAUX_TRANSACTION_STATUS_FAILED_NACK, + I2CAUX_TRANSACTION_STATUS_FAILED_INCOMPLETE, + I2CAUX_TRANSACTION_STATUS_FAILED_OPERATION, + I2CAUX_TRANSACTION_STATUS_FAILED_INVALID_OPERATION, + I2CAUX_TRANSACTION_STATUS_FAILED_BUFFER_OVERFLOW +}; + +struct i2caux_transaction_request { + enum i2caux_transaction_operation operation; + struct i2caux_transaction_payload payload; + enum i2caux_transaction_status status; +}; + +enum i2caux_engine_type { + I2CAUX_ENGINE_TYPE_UNKNOWN = (-1L), + I2CAUX_ENGINE_TYPE_AUX, + I2CAUX_ENGINE_TYPE_I2C_DDC_HW, + I2CAUX_ENGINE_TYPE_I2C_GENERIC_HW, + I2CAUX_ENGINE_TYPE_I2C_SW +}; + +enum i2c_default_speed { + I2CAUX_DEFAULT_I2C_HW_SPEED = 50, + I2CAUX_DEFAULT_I2C_SW_SPEED = 50 +}; + +enum i2caux_transaction_action { + I2CAUX_TRANSACTION_ACTION_I2C_WRITE = 0x00, + I2CAUX_TRANSACTION_ACTION_I2C_READ = 0x10, + I2CAUX_TRANSACTION_ACTION_I2C_STATUS_REQUEST = 0x20, + + I2CAUX_TRANSACTION_ACTION_I2C_WRITE_MOT = 0x40, + I2CAUX_TRANSACTION_ACTION_I2C_READ_MOT = 0x50, + I2CAUX_TRANSACTION_ACTION_I2C_STATUS_REQUEST_MOT = 0x60, + + I2CAUX_TRANSACTION_ACTION_DP_WRITE = 0x80, + I2CAUX_TRANSACTION_ACTION_DP_READ = 0x90 +}; + +struct engine; + +struct engine_funcs { + enum i2caux_engine_type (*get_engine_type)( + const struct engine *engine); + bool (*acquire)( + struct engine *engine, + struct ddc *ddc); + bool (*submit_request)( + struct engine *engine, + struct i2caux_transaction_request *request, + bool middle_of_transaction); + /* [anaumov] Actually, following method is meaningful + * only in I2C HW engines */ + void (*keep_power_up_count)( + struct engine *engine, + bool keep_power_up); + void (*release_engine)( + struct engine *engine); +}; + +struct engine { + const struct engine_funcs *funcs; + struct ddc *ddc; + struct dc_context *ctx; +}; + +bool dal_i2caux_construct_engine( + struct engine *engine, + struct dc_context *ctx); + +void dal_i2caux_destruct_engine( + struct engine *engine); + +void dal_i2caux_keep_power_up_count( + struct engine *engine, + bool keep_power_up); + +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/engine_base.c b/drivers/gpu/drm/amd/dal/dc/i2caux/engine_base.c new file mode 100644 index 000000000000..9c472b9db5ba --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/engine_base.c @@ -0,0 +1,67 @@ +/* + * 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" + +/* + * Header of this unit + */ + +#include "engine.h" + +/* + * Post-requisites: headers required by this unit + */ + +/* + * This unit + */ +void dal_i2caux_keep_power_up_count( + struct engine *engine, + bool keep_power_up) +{ + +} + +bool dal_i2caux_construct_engine( + struct engine *engine, + struct dc_context *ctx) +{ + engine->ddc = NULL; + engine->ctx = ctx; + return true; +} + +void dal_i2caux_destruct_engine( + struct engine *engine) +{ + /* nothing to do */ +} + diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.c b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.c new file mode 100644 index 000000000000..dccb1c51a3be --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.c @@ -0,0 +1,121 @@ +/* + * 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 "i2c_engine.h" + +/* + * Post-requisites: headers required by this unit + */ + +/* + * This unit + */ + +#define FROM_ENGINE(ptr) \ + container_of((ptr), struct i2c_engine, base) + +bool dal_i2c_engine_acquire( + struct engine *engine, + struct ddc *ddc_handle) +{ + struct i2c_engine *i2c_engine = FROM_ENGINE(engine); + + uint32_t counter = 0; + bool result; + + do { + result = i2c_engine->funcs->acquire_engine( + i2c_engine, ddc_handle); + + if (result) + break; + + /* i2c_engine is busy by VBios, lets wait and retry */ + + dm_delay_in_microseconds(engine->ctx, 10); + + ++counter; + } while (counter < 2); + + if (result) { + if (!i2c_engine->funcs->setup_engine(i2c_engine)) { + engine->funcs->release_engine(engine); + result = false; + } + } + + return result; +} + +bool dal_i2c_engine_setup_i2c_engine( + struct i2c_engine *engine) +{ + /* Derivative classes do not have to override this */ + + return true; +} + +void dal_i2c_engine_submit_channel_request( + struct i2c_engine *engine, + struct i2c_request_transaction_data *request) +{ + +} + +void dal_i2c_engine_process_channel_reply( + struct i2c_engine *engine, + struct i2c_reply_transaction_data *reply) +{ + +} + +bool dal_i2c_engine_construct( + struct i2c_engine *engine, + struct dc_context *ctx) +{ + if (!dal_i2caux_construct_engine(&engine->base, ctx)) + return false; + + engine->timeout_delay = 0; + return true; +} + +void dal_i2c_engine_destruct( + struct i2c_engine *engine) +{ + dal_i2caux_destruct_engine(&engine->base); +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.h b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.h new file mode 100644 index 000000000000..20299fd08e9c --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_engine.h @@ -0,0 +1,113 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_ENGINE_H__ +#define __DAL_I2C_ENGINE_H__ + +enum i2c_channel_operation_result { + I2C_CHANNEL_OPERATION_SUCCEEDED, + I2C_CHANNEL_OPERATION_FAILED, + I2C_CHANNEL_OPERATION_NOT_GRANTED, + I2C_CHANNEL_OPERATION_IS_BUSY, + I2C_CHANNEL_OPERATION_NO_HANDLE_PROVIDED, + I2C_CHANNEL_OPERATION_CHANNEL_IN_USE, + I2C_CHANNEL_OPERATION_CHANNEL_CLIENT_MAX_ALLOWED, + I2C_CHANNEL_OPERATION_ENGINE_BUSY, + I2C_CHANNEL_OPERATION_TIMEOUT, + I2C_CHANNEL_OPERATION_NO_RESPONSE, + I2C_CHANNEL_OPERATION_HW_REQUEST_I2C_BUS, + I2C_CHANNEL_OPERATION_WRONG_PARAMETER, + I2C_CHANNEL_OPERATION_OUT_NB_OF_RETRIES, + I2C_CHANNEL_OPERATION_NOT_STARTED +}; + +struct i2c_request_transaction_data { + enum i2caux_transaction_action action; + enum i2c_channel_operation_result status; + uint8_t address; + uint8_t length; + uint8_t *data; +}; + +struct i2c_reply_transaction_data { + uint8_t length; + uint8_t *data; +}; + +struct i2c_engine; + +struct i2c_engine_funcs { + void (*destroy)( + struct i2c_engine **ptr); + uint32_t (*get_speed)( + const struct i2c_engine *engine); + void (*set_speed)( + struct i2c_engine *engine, + uint32_t speed); + bool (*acquire_engine)( + struct i2c_engine *engine, + struct ddc *ddc); + bool (*setup_engine)( + struct i2c_engine *engine); + void (*submit_channel_request)( + struct i2c_engine *engine, + struct i2c_request_transaction_data *request); + void (*process_channel_reply)( + struct i2c_engine *engine, + struct i2c_reply_transaction_data *reply); + enum i2c_channel_operation_result (*get_channel_status)( + struct i2c_engine *engine, + uint8_t *returned_bytes); +}; + +struct i2c_engine { + struct engine base; + const struct i2c_engine_funcs *funcs; + uint32_t timeout_delay; +}; + +bool dal_i2c_engine_construct( + struct i2c_engine *engine, + struct dc_context *ctx); + +void dal_i2c_engine_destruct( + struct i2c_engine *engine); + +bool dal_i2c_engine_setup_i2c_engine( + struct i2c_engine *engine); + +void dal_i2c_engine_submit_channel_request( + struct i2c_engine *engine, + struct i2c_request_transaction_data *request); + +void dal_i2c_engine_process_channel_reply( + struct i2c_engine *engine, + struct i2c_reply_transaction_data *reply); + +bool dal_i2c_engine_acquire( + struct engine *ptr, + struct ddc *ddc_handle); + +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.c b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.c new file mode 100644 index 000000000000..6429b55dd697 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.c @@ -0,0 +1,286 @@ +/* + * 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; + + uint8_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; + uint8_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) { + uint8_t current_transaction_size; + uint8_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); +} + +bool dal_i2c_generic_hw_engine_construct( + struct i2c_generic_hw_engine *engine, + struct dc_context *ctx) +{ + if (!dal_i2c_hw_engine_construct(&engine->base, ctx)) + return false; + return true; +} + +void dal_i2c_generic_hw_engine_destruct( + struct i2c_generic_hw_engine *engine) +{ + dal_i2c_hw_engine_destruct(&engine->base); +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.h b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.h new file mode 100644 index 000000000000..52f2aa248d95 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_generic_hw_engine.h @@ -0,0 +1,77 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_GENERIC_HW_ENGINE_H__ +#define __DAL_I2C_GENERIC_HW_ENGINE_H__ + +struct i2c_generic_transaction_attributes { + enum i2caux_transaction_action action; + uint8_t transaction_size; + bool start_bit; + bool stop_bit; + bool last_read; +}; + +struct i2c_generic_hw_engine; + +struct i2c_generic_hw_engine_funcs { + void (*write_address)( + struct i2c_generic_hw_engine *engine, + uint8_t address); + void (*write_data)( + struct i2c_generic_hw_engine *engine, + const uint8_t *buffer, + uint8_t length); + void (*read_data)( + struct i2c_generic_hw_engine *engine, + uint8_t *buffer, + uint8_t length, + uint32_t offset); + void (*execute_transaction)( + struct i2c_generic_hw_engine *engine, + struct i2c_generic_transaction_attributes *attributes); +}; + +struct i2c_generic_hw_engine { + struct i2c_hw_engine base; + const struct i2c_generic_hw_engine_funcs *funcs; +}; + +bool dal_i2c_generic_hw_engine_construct( + struct i2c_generic_hw_engine *engine, + struct dc_context *ctx); + +void dal_i2c_generic_hw_engine_destruct( + struct i2c_generic_hw_engine *engine); +enum i2caux_engine_type dal_i2c_generic_hw_engine_get_engine_type( + const struct engine *engine); +bool dal_i2c_generic_hw_engine_submit_request( + struct engine *ptr, + struct i2caux_transaction_request *i2caux_request, + bool middle_of_transaction); +uint32_t dal_i2c_generic_hw_engine_get_transaction_timeout( + const struct i2c_hw_engine *engine, + uint32_t length); +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.c b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.c new file mode 100644 index 000000000000..b02ba7927d5b --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.c @@ -0,0 +1,246 @@ +/* + * 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" + +/* + * Header of this unit + */ + +#include "i2c_hw_engine.h" + +/* + * Post-requisites: headers required by this unit + */ + +/* + * This unit + */ + +/* + * @brief + * Cast 'struct i2c_engine *' + * to 'struct i2c_hw_engine *' + */ +#define FROM_I2C_ENGINE(ptr) \ + container_of((ptr), struct i2c_hw_engine, base) + +/* + * @brief + * Cast 'struct engine *' + * to 'struct i2c_hw_engine *' + */ +#define FROM_ENGINE(ptr) \ + FROM_I2C_ENGINE(container_of((ptr), struct i2c_engine, base)) + +enum i2caux_engine_type dal_i2c_hw_engine_get_engine_type( + const struct engine *engine) +{ + return I2CAUX_ENGINE_TYPE_I2C_DDC_HW; +} + +bool dal_i2c_hw_engine_submit_request( + struct engine *engine, + struct i2caux_transaction_request *i2caux_request, + bool middle_of_transaction) +{ + struct i2c_hw_engine *hw_engine = FROM_ENGINE(engine); + + struct i2c_request_transaction_data request; + + uint32_t transaction_timeout; + + enum i2c_channel_operation_result operation_result; + + bool result = false; + + /* We need following: + * transaction length will not exceed + * the number of free bytes in HW buffer (minus one for address)*/ + + if (i2caux_request->payload.length >= + hw_engine->funcs->get_hw_buffer_available_size(hw_engine)) { + i2caux_request->status = + I2CAUX_TRANSACTION_STATUS_FAILED_BUFFER_OVERFLOW; + return false; + } + + if (i2caux_request->operation == I2CAUX_TRANSACTION_READ) + request.action = middle_of_transaction ? + I2CAUX_TRANSACTION_ACTION_I2C_READ_MOT : + I2CAUX_TRANSACTION_ACTION_I2C_READ; + else if (i2caux_request->operation == I2CAUX_TRANSACTION_WRITE) + request.action = middle_of_transaction ? + I2CAUX_TRANSACTION_ACTION_I2C_WRITE_MOT : + I2CAUX_TRANSACTION_ACTION_I2C_WRITE; + else { + i2caux_request->status = + I2CAUX_TRANSACTION_STATUS_FAILED_INVALID_OPERATION; + /* [anaumov] in DAL2, there was no "return false" */ + return false; + } + + request.address = (uint8_t)i2caux_request->payload.address; + request.length = i2caux_request->payload.length; + request.data = i2caux_request->payload.data; + + /* obtain timeout value before submitting request */ + + transaction_timeout = hw_engine->funcs->get_transaction_timeout( + hw_engine, i2caux_request->payload.length + 1); + + hw_engine->base.funcs->submit_channel_request( + &hw_engine->base, &request); + + if ((request.status == I2C_CHANNEL_OPERATION_FAILED) || + (request.status == I2C_CHANNEL_OPERATION_ENGINE_BUSY)) { + i2caux_request->status = + I2CAUX_TRANSACTION_STATUS_FAILED_CHANNEL_BUSY; + return false; + } + + /* wait until transaction proceed */ + + operation_result = hw_engine->funcs->wait_on_operation_result( + hw_engine, + transaction_timeout, + I2C_CHANNEL_OPERATION_ENGINE_BUSY); + + /* 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; + } + + if (result && (i2caux_request->operation == I2CAUX_TRANSACTION_READ)) { + struct i2c_reply_transaction_data reply; + + reply.data = i2caux_request->payload.data; + reply.length = i2caux_request->payload.length; + + hw_engine->base.funcs-> + process_channel_reply(&hw_engine->base, &reply); + } + + return result; +} + +bool dal_i2c_hw_engine_acquire_engine( + struct i2c_engine *engine, + struct ddc *ddc) +{ + enum gpio_result result; + uint32_t current_speed; + + result = dal_ddc_open(ddc, GPIO_MODE_HARDWARE, + GPIO_DDC_CONFIG_TYPE_MODE_I2C); + + if (result != GPIO_RESULT_OK) + return false; + + engine->base.ddc = ddc; + + current_speed = engine->funcs->get_speed(engine); + + if (current_speed) + FROM_I2C_ENGINE(engine)->original_speed = current_speed; + + return true; +} +/* + * @brief + * Queries in a loop for current engine status + * until retrieved status matches 'expected_result', or timeout occurs. + * Timeout given in microseconds + * and the status query frequency is also one per microsecond. + */ +enum i2c_channel_operation_result dal_i2c_hw_engine_wait_on_operation_result( + struct i2c_hw_engine *engine, + uint32_t timeout, + enum i2c_channel_operation_result expected_result) +{ + enum i2c_channel_operation_result result; + uint32_t i = 0; + + if (!timeout) + return I2C_CHANNEL_OPERATION_SUCCEEDED; + + do { + result = engine->base.funcs->get_channel_status( + &engine->base, NULL); + + if (result != expected_result) + break; + + dm_delay_in_microseconds(engine->base.base.ctx, 1); + + ++i; + } while (i < timeout); + + return result; +} + +bool dal_i2c_hw_engine_construct( + struct i2c_hw_engine *engine, + struct dc_context *ctx) +{ + if (!dal_i2c_engine_construct(&engine->base, ctx)) + return false; + engine->original_speed = I2CAUX_DEFAULT_I2C_HW_SPEED; + engine->default_speed = I2CAUX_DEFAULT_I2C_HW_SPEED; + return true; +} + +void dal_i2c_hw_engine_destruct( + struct i2c_hw_engine *engine) +{ + dal_i2c_engine_destruct(&engine->base); +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.h b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.h new file mode 100644 index 000000000000..5afbd7067a1f --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_hw_engine.h @@ -0,0 +1,80 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_HW_ENGINE_H__ +#define __DAL_I2C_HW_ENGINE_H__ + +enum { + TRANSACTION_TIMEOUT_IN_I2C_CLOCKS = 32 +}; + +struct i2c_hw_engine; + +struct i2c_hw_engine_funcs { + uint8_t (*get_hw_buffer_available_size)( + const struct i2c_hw_engine *engine); + enum i2c_channel_operation_result (*wait_on_operation_result)( + struct i2c_hw_engine *engine, + uint32_t timeout, + enum i2c_channel_operation_result expected_result); + uint32_t (*get_transaction_timeout)( + const struct i2c_hw_engine *engine, + uint32_t length); +}; + +struct i2c_hw_engine { + struct i2c_engine base; + const struct i2c_hw_engine_funcs *funcs; + + /* Values below are in kilohertz */ + uint32_t original_speed; + uint32_t default_speed; +}; + +bool dal_i2c_hw_engine_construct( + struct i2c_hw_engine *engine, + struct dc_context *ctx); + +void dal_i2c_hw_engine_destruct( + struct i2c_hw_engine *engine); + +enum i2c_channel_operation_result dal_i2c_hw_engine_wait_on_operation_result( + struct i2c_hw_engine *engine, + uint32_t timeout, + enum i2c_channel_operation_result expected_result); + +bool dal_i2c_hw_engine_acquire_engine( + struct i2c_engine *engine, + struct ddc *ddc); + +bool dal_i2c_hw_engine_submit_request( + struct engine *ptr, + struct i2caux_transaction_request *i2caux_request, + bool middle_of_transaction); + +enum i2caux_engine_type dal_i2c_hw_engine_get_engine_type( + const struct engine *engine); + +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.c b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.c new file mode 100644 index 000000000000..21e8fa2c08ee --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.c @@ -0,0 +1,614 @@ +/* + * 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" + +/* + * Header of this unit + */ + +#include "i2c_sw_engine.h" + +/* + * Post-requisites: headers required by this unit + */ + +/* + * This unit + */ + +#define SCL false +#define SDA true + +static inline bool read_bit_from_ddc( + struct ddc *ddc, + bool data_nor_clock) +{ + uint32_t value = 0; + + if (data_nor_clock) + dal_ddc_get_data(ddc, &value); + else + dal_ddc_get_clock(ddc, &value); + + return (value != 0); +} + +static inline void write_bit_to_ddc( + struct ddc *ddc, + bool data_nor_clock, + bool bit) +{ + uint32_t value = bit ? 1 : 0; + + if (data_nor_clock) + dal_ddc_set_data(ddc, value); + else + dal_ddc_set_clock(ddc, value); +} + +static bool wait_for_scl_high( + struct dc_context *ctx, + struct ddc *ddc, + uint16_t clock_delay_div_4) +{ + uint32_t scl_retry = 0; + uint32_t scl_retry_max = I2C_SW_TIMEOUT_DELAY / clock_delay_div_4; + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + /* 3 milliseconds delay + * to wake up some displays from "low power" state. + */ + + do { + if (read_bit_from_ddc(ddc, SCL)) + return true; + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + ++scl_retry; + } while (scl_retry <= scl_retry_max); + + return false; +} + +static bool start_sync( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4) +{ + uint32_t retry = 0; + + /* The I2C communications start signal is: + * the SDA going low from high, while the SCL is high. */ + + write_bit_to_ddc(ddc_handle, SCL, true); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + do { + write_bit_to_ddc(ddc_handle, SDA, true); + + if (!read_bit_from_ddc(ddc_handle, SDA)) { + ++retry; + continue; + } + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high(ctx, ddc_handle, clock_delay_div_4)) + break; + + write_bit_to_ddc(ddc_handle, SDA, false); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, false); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + return true; + } while (retry <= I2C_SW_RETRIES); + + return false; +} + +static bool stop_sync( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4) +{ + uint32_t retry = 0; + + /* The I2C communications stop signal is: + * the SDA going high from low, while the SCL is high. */ + + write_bit_to_ddc(ddc_handle, SCL, false); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, false); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high(ctx, ddc_handle, clock_delay_div_4)) + return false; + + write_bit_to_ddc(ddc_handle, SDA, true); + + do { + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + if (read_bit_from_ddc(ddc_handle, SDA)) + return true; + + ++retry; + } while (retry <= 2); + + return false; +} + +static bool write_byte( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4, + uint8_t byte) +{ + int32_t shift = 7; + bool ack; + + /* bits are transmitted serially, starting from MSB */ + + do { + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, (byte >> shift) & 1); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high(ctx, ddc_handle, clock_delay_div_4)) + return false; + + write_bit_to_ddc(ddc_handle, SCL, false); + + --shift; + } while (shift >= 0); + + /* The display sends ACK by preventing the SDA from going high + * after the SCL pulse we use to send our last data bit. + * If the SDA goes high after that bit, it's a NACK */ + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, true); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high(ctx, ddc_handle, clock_delay_div_4)) + return false; + + /* read ACK bit */ + + ack = !read_bit_from_ddc(ddc_handle, SDA); + + dm_delay_in_microseconds(ctx, clock_delay_div_4 << 1); + + write_bit_to_ddc(ddc_handle, SCL, false); + + dm_delay_in_microseconds(ctx, clock_delay_div_4 << 1); + + return ack; +} + +static bool read_byte( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4, + uint8_t *byte, + bool more) +{ + int32_t shift = 7; + + uint8_t data = 0; + + /* The data bits are read from MSB to LSB; + * bit is read while SCL is high */ + + do { + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high(ctx, ddc_handle, clock_delay_div_4)) + return false; + + if (read_bit_from_ddc(ddc_handle, SDA)) + data |= (1 << shift); + + write_bit_to_ddc(ddc_handle, SCL, false); + + dm_delay_in_microseconds(ctx, clock_delay_div_4 << 1); + + --shift; + } while (shift >= 0); + + /* read only whole byte */ + + *byte = data; + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + /* send the acknowledge bit: + * SDA low means ACK, SDA high means NACK */ + + write_bit_to_ddc(ddc_handle, SDA, !more); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high(ctx, ddc_handle, clock_delay_div_4)) + return false; + + write_bit_to_ddc(ddc_handle, SCL, false); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, true); + + dm_delay_in_microseconds(ctx, clock_delay_div_4); + + return true; +} + +static bool i2c_write( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4, + uint8_t address, + uint8_t length, + const uint8_t *data) +{ + uint32_t i = 0; + + if (!write_byte(ctx, ddc_handle, clock_delay_div_4, address)) + return false; + + while (i < length) { + if (!write_byte(ctx, ddc_handle, clock_delay_div_4, data[i])) + return false; + ++i; + } + + return true; +} + +static bool i2c_read( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4, + uint8_t address, + uint8_t length, + uint8_t *data) +{ + uint32_t i = 0; + + if (!write_byte(ctx, ddc_handle, clock_delay_div_4, address)) + return false; + + while (i < length) { + if (!read_byte(ctx, ddc_handle, clock_delay_div_4, data + i, + i < length - 1)) + return false; + ++i; + } + + return true; +} + +/* + * @brief + * Cast 'struct i2c_engine *' + * to 'struct i2c_sw_engine *' + */ +#define FROM_I2C_ENGINE(ptr) \ + container_of((ptr), struct i2c_sw_engine, base) + +/* + * @brief + * Cast 'struct engine *' + * to 'struct i2c_sw_engine *' + */ +#define FROM_ENGINE(ptr) \ + FROM_I2C_ENGINE(container_of((ptr), struct i2c_engine, base)) + +enum i2caux_engine_type dal_i2c_sw_engine_get_engine_type( + const struct engine *engine) +{ + return I2CAUX_ENGINE_TYPE_I2C_SW; +} + +bool dal_i2c_sw_engine_submit_request( + struct engine *engine, + struct i2caux_transaction_request *i2caux_request, + bool middle_of_transaction) +{ + struct i2c_sw_engine *sw_engine = FROM_ENGINE(engine); + + struct i2c_engine *base = &sw_engine->base; + + struct i2c_request_transaction_data request; + bool operation_succeeded = false; + + if (i2caux_request->operation == I2CAUX_TRANSACTION_READ) + request.action = middle_of_transaction ? + I2CAUX_TRANSACTION_ACTION_I2C_READ_MOT : + I2CAUX_TRANSACTION_ACTION_I2C_READ; + else if (i2caux_request->operation == I2CAUX_TRANSACTION_WRITE) + request.action = middle_of_transaction ? + I2CAUX_TRANSACTION_ACTION_I2C_WRITE_MOT : + I2CAUX_TRANSACTION_ACTION_I2C_WRITE; + else { + i2caux_request->status = + I2CAUX_TRANSACTION_STATUS_FAILED_INVALID_OPERATION; + /* in DAL2, there was no "return false" */ + return false; + } + + request.address = (uint8_t)i2caux_request->payload.address; + request.length = i2caux_request->payload.length; + request.data = i2caux_request->payload.data; + + base->funcs->submit_channel_request(base, &request); + + if ((request.status == I2C_CHANNEL_OPERATION_ENGINE_BUSY) || + (request.status == I2C_CHANNEL_OPERATION_FAILED)) + i2caux_request->status = + I2CAUX_TRANSACTION_STATUS_FAILED_CHANNEL_BUSY; + else { + enum i2c_channel_operation_result operation_result; + + do { + operation_result = + base->funcs->get_channel_status(base, NULL); + + switch (operation_result) { + case I2C_CHANNEL_OPERATION_SUCCEEDED: + i2caux_request->status = + I2CAUX_TRANSACTION_STATUS_SUCCEEDED; + operation_succeeded = 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; + break; + } + } while (operation_result == I2C_CHANNEL_OPERATION_ENGINE_BUSY); + } + + return operation_succeeded; +} + +uint32_t dal_i2c_sw_engine_get_speed( + const struct i2c_engine *engine) +{ + return FROM_I2C_ENGINE(engine)->speed; +} + +void dal_i2c_sw_engine_set_speed( + struct i2c_engine *engine, + uint32_t speed) +{ + struct i2c_sw_engine *sw_engine = FROM_I2C_ENGINE(engine); + + ASSERT(speed); + + sw_engine->speed = speed ? speed : I2CAUX_DEFAULT_I2C_SW_SPEED; + + sw_engine->clock_delay = 1000 / sw_engine->speed; + + if (sw_engine->clock_delay < 12) + sw_engine->clock_delay = 12; +} + +bool dal_i2caux_i2c_sw_engine_acquire_engine( + struct i2c_engine *engine, + struct ddc *ddc) +{ + enum gpio_result result; + + result = dal_ddc_open(ddc, GPIO_MODE_FAST_OUTPUT, + GPIO_DDC_CONFIG_TYPE_MODE_I2C); + + if (result != GPIO_RESULT_OK) + return false; + + engine->base.ddc = ddc; + + return true; +} + +void dal_i2c_sw_engine_submit_channel_request( + struct i2c_engine *engine, + struct i2c_request_transaction_data *req) +{ + struct i2c_sw_engine *sw_engine = FROM_I2C_ENGINE(engine); + + struct ddc *ddc = engine->base.ddc; + uint16_t clock_delay_div_4 = sw_engine->clock_delay >> 2; + + /* send sync (start / repeated start) */ + + bool result = start_sync(engine->base.ctx, ddc, clock_delay_div_4); + + /* process payload */ + + if (result) { + switch (req->action) { + case I2CAUX_TRANSACTION_ACTION_I2C_WRITE: + case I2CAUX_TRANSACTION_ACTION_I2C_WRITE_MOT: + result = i2c_write(engine->base.ctx, ddc, clock_delay_div_4, + req->address, req->length, req->data); + break; + case I2CAUX_TRANSACTION_ACTION_I2C_READ: + case I2CAUX_TRANSACTION_ACTION_I2C_READ_MOT: + result = i2c_read(engine->base.ctx, ddc, clock_delay_div_4, + req->address, req->length, req->data); + break; + default: + result = false; + break; + } + } + + /* send stop if not 'mot' or operation failed */ + + if (!result || + (req->action == I2CAUX_TRANSACTION_ACTION_I2C_WRITE) || + (req->action == I2CAUX_TRANSACTION_ACTION_I2C_READ)) + if (!stop_sync(engine->base.ctx, ddc, clock_delay_div_4)) + result = false; + + req->status = result ? + I2C_CHANNEL_OPERATION_SUCCEEDED : + I2C_CHANNEL_OPERATION_FAILED; +} + +enum i2c_channel_operation_result dal_i2c_sw_engine_get_channel_status( + struct i2c_engine *engine, + uint8_t *returned_bytes) +{ + return dal_ddc_check_line_aborted(engine->base.ddc) ? + I2C_CHANNEL_OPERATION_FAILED : + I2C_CHANNEL_OPERATION_SUCCEEDED; +} + +void dal_i2c_sw_engine_destruct( + struct i2c_sw_engine *engine) +{ + dal_i2c_engine_destruct(&engine->base); +} + +static void destroy( + struct i2c_engine **ptr) +{ + dal_i2c_sw_engine_destruct(FROM_I2C_ENGINE(*ptr)); + + dm_free((*ptr)->base.ctx, *ptr); + *ptr = NULL; +} + +static const struct i2c_engine_funcs i2c_engine_funcs = { + .acquire_engine = dal_i2caux_i2c_sw_engine_acquire_engine, + .destroy = destroy, + .get_speed = dal_i2c_sw_engine_get_speed, + .set_speed = dal_i2c_sw_engine_set_speed, + .setup_engine = dal_i2c_engine_setup_i2c_engine, + .submit_channel_request = dal_i2c_sw_engine_submit_channel_request, + .process_channel_reply = dal_i2c_engine_process_channel_reply, + .get_channel_status = dal_i2c_sw_engine_get_channel_status, +}; + +static void release_engine( + struct engine *engine) +{ + +} + +static const struct engine_funcs engine_funcs = { + .release_engine = release_engine, + .get_engine_type = dal_i2c_sw_engine_get_engine_type, + .acquire = dal_i2c_engine_acquire, + .submit_request = dal_i2c_sw_engine_submit_request, + .keep_power_up_count = dal_i2caux_keep_power_up_count, +}; + +bool dal_i2c_sw_engine_construct( + struct i2c_sw_engine *engine, + const struct i2c_sw_engine_create_arg *arg) +{ + if (!dal_i2c_engine_construct(&engine->base, arg->ctx)) + return false; + + dal_i2c_sw_engine_set_speed(&engine->base, arg->default_speed); + engine->base.funcs = &i2c_engine_funcs; + engine->base.base.funcs = &engine_funcs; + return true; +} + + + +struct i2c_engine *dal_i2c_sw_engine_create( + const struct i2c_sw_engine_create_arg *arg) +{ + struct i2c_sw_engine *engine; + + if (!arg) { + BREAK_TO_DEBUGGER(); + return NULL; + } + + engine = dm_alloc(arg->ctx, sizeof(struct i2c_sw_engine)); + + if (!engine) { + BREAK_TO_DEBUGGER(); + return NULL; + } + + if (dal_i2c_sw_engine_construct(engine, arg)) + return &engine->base; + + BREAK_TO_DEBUGGER(); + + dm_free(arg->ctx, engine); + + return NULL; +} diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.h b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.h new file mode 100644 index 000000000000..e0cb4c3d483d --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2c_sw_engine.h @@ -0,0 +1,81 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_SW_ENGINE_H__ +#define __DAL_I2C_SW_ENGINE_H__ + +enum { + I2C_SW_RETRIES = 10, + I2C_SW_SCL_READ_RETRIES = 128, + /* following value is in microseconds */ + I2C_SW_TIMEOUT_DELAY = 3000 +}; + +struct i2c_sw_engine; + +struct i2c_sw_engine { + struct i2c_engine base; + uint32_t clock_delay; + /* Values below are in KHz */ + uint32_t speed; + uint32_t default_speed; +}; + +struct i2c_sw_engine_create_arg { + uint32_t default_speed; + struct dc_context *ctx; +}; + +bool dal_i2c_sw_engine_construct( + struct i2c_sw_engine *engine, + const struct i2c_sw_engine_create_arg *arg); + +bool dal_i2caux_i2c_sw_engine_acquire_engine( + struct i2c_engine *engine, + struct ddc *ddc_handle); + +void dal_i2c_sw_engine_destruct( + struct i2c_sw_engine *engine); + +struct i2c_engine *dal_i2c_sw_engine_create( + const struct i2c_sw_engine_create_arg *arg); +enum i2caux_engine_type dal_i2c_sw_engine_get_engine_type( + const struct engine *engine); +bool dal_i2c_sw_engine_submit_request( + struct engine *ptr, + struct i2caux_transaction_request *i2caux_request, + bool middle_of_transaction); +uint32_t dal_i2c_sw_engine_get_speed( + const struct i2c_engine *engine); +void dal_i2c_sw_engine_set_speed( + struct i2c_engine *ptr, + uint32_t speed); +void dal_i2c_sw_engine_submit_channel_request( + struct i2c_engine *ptr, + struct i2c_request_transaction_data *req); +enum i2c_channel_operation_result dal_i2c_sw_engine_get_channel_status( + struct i2c_engine *engine, + uint8_t *returned_bytes); +#endif diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.c b/drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.c new file mode 100644 index 000000000000..4c2f2cb64315 --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.c @@ -0,0 +1,529 @@ +/* + * 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" + +/* + * Header of this unit + */ + +#include "i2caux.h" + +/* + * Post-requisites: headers required by this unit + */ + +#include "engine.h" +#include "i2c_engine.h" +#include "aux_engine.h" + +/* + * This unit + */ + +#if defined(CONFIG_DRM_AMD_DAL_DCE11_0) || defined(CONFIG_DRM_AMD_DAL_DCE10_0) +#include "dce110/i2caux_dce110.h" +#endif + +#include "diagnostics/i2caux_diag.h" + +/* + * @brief + * Plain API, available publicly + */ + +struct i2caux *dal_i2caux_create( + struct adapter_service *as, + struct dc_context *ctx) +{ + enum dce_version dce_version; + enum dce_environment dce_environment; + + if (!as) { + BREAK_TO_DEBUGGER(); + return NULL; + } + + dce_version = dal_adapter_service_get_dce_version(as); + dce_environment = dal_adapter_service_get_dce_environment(as); + + if (IS_FPGA_MAXIMUS_DC(dce_environment)) { + return dal_i2caux_diag_fpga_create(as, ctx); + } + + switch (dce_version) { +#if defined(CONFIG_DRM_AMD_DAL_DCE11_0) +#if defined(CONFIG_DRM_AMD_DAL_DCE10_0) + case DCE_VERSION_10_0: +#endif + case DCE_VERSION_11_0: + return dal_i2caux_dce110_create(as, ctx); +#endif + default: + BREAK_TO_DEBUGGER(); + return NULL; + } +} + +bool dal_i2caux_submit_i2c_command( + struct i2caux *i2caux, + struct ddc *ddc, + struct i2c_command *cmd) +{ + struct i2c_engine *engine; + uint8_t index_of_payload = 0; + bool result; + + if (!ddc) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (!cmd) { + BREAK_TO_DEBUGGER(); + return false; + } + + switch (cmd->engine) { + case I2C_COMMAND_ENGINE_SW: + /* try to acquire SW engine first, + * acquire HW engine if SW engine not available */ + engine = i2caux->funcs->acquire_i2c_sw_engine(i2caux, ddc); + + if (!engine) + engine = i2caux->funcs->acquire_i2c_hw_engine( + i2caux, ddc); + break; + case I2C_COMMAND_ENGINE_HW: + case I2C_COMMAND_ENGINE_DEFAULT: + default: + /* try to acquire HW engine first, + * acquire SW engine if HW engine not available */ + engine = i2caux->funcs->acquire_i2c_hw_engine(i2caux, ddc); + + if (!engine) + engine = i2caux->funcs->acquire_i2c_sw_engine( + i2caux, ddc); + } + + if (!engine) + return false; + + engine->funcs->set_speed(engine, cmd->speed); + + result = true; + + while (index_of_payload < cmd->number_of_payloads) { + bool mot = (index_of_payload != cmd->number_of_payloads - 1); + + struct i2c_payload *payload = cmd->payloads + index_of_payload; + + struct i2caux_transaction_request request = { 0 }; + + request.operation = payload->write ? + I2CAUX_TRANSACTION_WRITE : + I2CAUX_TRANSACTION_READ; + + request.payload.address_space = + I2CAUX_TRANSACTION_ADDRESS_SPACE_I2C; + request.payload.address = (payload->address << 1) | + !payload->write; + request.payload.length = payload->length; + request.payload.data = payload->data; + + if (!engine->base.funcs->submit_request( + &engine->base, &request, mot)) { + result = false; + break; + } + + ++index_of_payload; + } + + i2caux->funcs->release_engine(i2caux, &engine->base); + + return result; +} + +bool dal_i2caux_submit_aux_command( + struct i2caux *i2caux, + struct ddc *ddc, + struct aux_command *cmd) +{ + struct aux_engine *engine; + uint8_t index_of_payload = 0; + bool result; + + if (!ddc) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (!cmd) { + BREAK_TO_DEBUGGER(); + return false; + } + + engine = i2caux->funcs->acquire_aux_engine(i2caux, ddc); + + if (!engine) + return false; + + engine->delay = cmd->defer_delay; + engine->max_defer_write_retry = cmd->max_defer_write_retry; + + result = true; + + while (index_of_payload < cmd->number_of_payloads) { + bool mot = (index_of_payload != cmd->number_of_payloads - 1); + + struct aux_payload *payload = cmd->payloads + index_of_payload; + + struct i2caux_transaction_request request = { 0 }; + + request.operation = payload->write ? + I2CAUX_TRANSACTION_WRITE : + I2CAUX_TRANSACTION_READ; + + if (payload->i2c_over_aux) { + request.payload.address_space = + I2CAUX_TRANSACTION_ADDRESS_SPACE_I2C; + + request.payload.address = (payload->address << 1) | + !payload->write; + } else { + request.payload.address_space = + I2CAUX_TRANSACTION_ADDRESS_SPACE_DPCD; + + request.payload.address = payload->address; + } + + request.payload.length = payload->length; + request.payload.data = payload->data; + + if (!engine->base.funcs->submit_request( + &engine->base, &request, mot)) { + result = false; + break; + } + + ++index_of_payload; + } + + i2caux->funcs->release_engine(i2caux, &engine->base); + + return result; +} + +static bool get_hw_supported_ddc_line( + struct ddc *ddc, + enum gpio_ddc_line *line) +{ + enum gpio_ddc_line line_found; + + if (!ddc) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (!dal_ddc_is_hw_supported(ddc)) + return false; + + line_found = dal_ddc_get_line(ddc); + + if (line_found >= GPIO_DDC_LINE_COUNT) + return false; + + *line = line_found; + + return true; +} + +void dal_i2caux_keep_engine_power_up( + struct i2caux *i2caux, + struct ddc *ddc, + bool keep_power_up) +{ + enum gpio_ddc_line line; + struct i2c_engine *engine; + + if (!get_hw_supported_ddc_line(ddc, &line)) + return; + + engine = i2caux->i2c_hw_engines[line]; + + engine->base.funcs->keep_power_up_count(&engine->base, keep_power_up); +} + +bool dal_i2caux_start_gtc_sync( + struct i2caux *i2caux, + struct ddc *ddc) +{ + enum gpio_ddc_line line; + + struct aux_engine *engine; + + bool result; + + if (!get_hw_supported_ddc_line(ddc, &line)) + return false; + + engine = i2caux->aux_engines[line]; + + if (!engine) + return false; + + if (!engine->base.funcs->acquire(&engine->base, ddc)) + return false; + + result = engine->funcs->start_gtc_sync(engine); + + i2caux->funcs->release_engine(i2caux, &engine->base); + + return result; +} + +bool dal_i2caux_stop_gtc_sync( + struct i2caux *i2caux, + struct ddc *ddc) +{ + enum gpio_ddc_line line; + + struct aux_engine *engine; + + if (!get_hw_supported_ddc_line(ddc, &line)) + return false; + + engine = i2caux->aux_engines[line]; + + if (!engine) + return false; + + if (!engine->base.funcs->acquire(&engine->base, ddc)) + return false; + + engine->funcs->stop_gtc_sync(engine); + + i2caux->funcs->release_engine(i2caux, &engine->base); + + return true; +} + +void dal_i2caux_configure_aux( + struct i2caux *i2caux, + struct ddc *ddc, + union aux_config cfg) +{ + struct aux_engine *engine = + i2caux->funcs->acquire_aux_engine(i2caux, ddc); + + if (!engine) + return; + + engine->funcs->configure(engine, cfg); + + i2caux->funcs->release_engine(i2caux, &engine->base); +} + +void dal_i2caux_destroy( + struct i2caux **i2caux) +{ + if (!i2caux || !*i2caux) { + BREAK_TO_DEBUGGER(); + return; + } + + (*i2caux)->funcs->destroy(i2caux); + + *i2caux = NULL; +} + +/* + * @brief + * An utility function used by 'struct i2caux' and its descendants + */ + +uint32_t dal_i2caux_get_reference_clock( + struct adapter_service *as) +{ + struct firmware_info info = { { 0 } }; + + if (!dal_adapter_service_get_firmware_info(as, &info)) + return 0; + + return info.pll_info.crystal_frequency; +} + +/* + * @brief + * i2caux + */ + +enum { + /* following are expressed in KHz */ + DEFAULT_I2C_SW_SPEED = 50, + DEFAULT_I2C_HW_SPEED = 50, + + /* This is the timeout as defined in DP 1.2a, + * 2.3.4 "Detailed uPacket TX AUX CH State Description". */ + AUX_TIMEOUT_PERIOD = 400, + + /* Ideally, the SW timeout should be just above 550usec + * which is programmed in HW. + * But the SW timeout of 600usec is not reliable, + * because on some systems, delay_in_microseconds() + * returns faster than it should. + * EPR #379763: by trial-and-error on different systems, + * 700usec is the minimum reliable SW timeout for polling + * the AUX_SW_STATUS.AUX_SW_DONE bit. + * This timeout expires *only* when there is + * AUX Error or AUX Timeout conditions - not during normal operation. + * During normal operation, AUX_SW_STATUS.AUX_SW_DONE bit is set + * at most within ~240usec. That means, + * increasing this timeout will not affect normal operation, + * and we'll timeout after + * SW_AUX_TIMEOUT_PERIOD_MULTIPLIER * AUX_TIMEOUT_PERIOD = 1600usec. + * This timeout is especially important for + * resume from S3 and CTS. */ + SW_AUX_TIMEOUT_PERIOD_MULTIPLIER = 4 +}; + +struct i2c_engine *dal_i2caux_acquire_i2c_sw_engine( + struct i2caux *i2caux, + struct ddc *ddc) +{ + enum gpio_ddc_line line; + struct i2c_engine *engine = NULL; + + if (get_hw_supported_ddc_line(ddc, &line)) + engine = i2caux->i2c_sw_engines[line]; + + if (!engine) + engine = i2caux->i2c_generic_sw_engine; + + if (!engine) + return NULL; + + if (!engine->base.funcs->acquire(&engine->base, ddc)) + return NULL; + + return engine; +} + +struct aux_engine *dal_i2caux_acquire_aux_engine( + struct i2caux *i2caux, + struct ddc *ddc) +{ + enum gpio_ddc_line line; + struct aux_engine *engine; + + if (!get_hw_supported_ddc_line(ddc, &line)) + return NULL; + + engine = i2caux->aux_engines[line]; + + if (!engine) + return NULL; + + if (!engine->base.funcs->acquire(&engine->base, ddc)) + return NULL; + + return engine; +} + +void dal_i2caux_release_engine( + struct i2caux *i2caux, + struct engine *engine) +{ + engine->funcs->release_engine(engine); + + dal_ddc_close(engine->ddc); + + engine->ddc = NULL; +} + +bool dal_i2caux_construct( + struct i2caux *i2caux, + struct adapter_service *as, + struct dc_context *ctx) +{ + uint32_t i = 0; + + i2caux->ctx = ctx; + do { + i2caux->i2c_sw_engines[i] = NULL; + i2caux->i2c_hw_engines[i] = NULL; + i2caux->aux_engines[i] = NULL; + + ++i; + } while (i < GPIO_DDC_LINE_COUNT); + + i2caux->i2c_generic_sw_engine = NULL; + i2caux->i2c_generic_hw_engine = NULL; + + i2caux->aux_timeout_period = + SW_AUX_TIMEOUT_PERIOD_MULTIPLIER * AUX_TIMEOUT_PERIOD; + + i2caux->default_i2c_sw_speed = DEFAULT_I2C_SW_SPEED; + i2caux->default_i2c_hw_speed = DEFAULT_I2C_HW_SPEED; + + return true; +} + +void dal_i2caux_destruct( + struct i2caux *i2caux) +{ + uint32_t i = 0; + + if (i2caux->i2c_generic_hw_engine) + i2caux->i2c_generic_hw_engine->funcs->destroy( + &i2caux->i2c_generic_hw_engine); + + if (i2caux->i2c_generic_sw_engine) + i2caux->i2c_generic_sw_engine->funcs->destroy( + &i2caux->i2c_generic_sw_engine); + + do { + if (i2caux->aux_engines[i]) + i2caux->aux_engines[i]->funcs->destroy( + &i2caux->aux_engines[i]); + + if (i2caux->i2c_hw_engines[i]) + i2caux->i2c_hw_engines[i]->funcs->destroy( + &i2caux->i2c_hw_engines[i]); + + if (i2caux->i2c_sw_engines[i]) + i2caux->i2c_sw_engines[i]->funcs->destroy( + &i2caux->i2c_sw_engines[i]); + + ++i; + } while (i < GPIO_DDC_LINE_COUNT); +} + diff --git a/drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.h b/drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.h new file mode 100644 index 000000000000..76f5b637ec3f --- /dev/null +++ b/drivers/gpu/drm/amd/dal/dc/i2caux/i2caux.h @@ -0,0 +1,123 @@ +/* + * 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 + * + */ + +#ifndef __DAL_I2C_AUX_H__ +#define __DAL_I2C_AUX_H__ + +uint32_t dal_i2caux_get_reference_clock( + struct adapter_service *as); + +struct i2caux; + +struct engine; + +struct i2caux_funcs { + void (*destroy)(struct i2caux **ptr); + struct i2c_engine * (*acquire_i2c_sw_engine)( + struct i2caux *i2caux, + struct ddc *ddc); + struct i2c_engine * (*acquire_i2c_hw_engine)( + struct i2caux *i2caux, + struct ddc *ddc); + struct aux_engine * (*acquire_aux_engine)( + struct i2caux *i2caux, + struct ddc *ddc); + void (*release_engine)( + struct i2caux *i2caux, + struct engine *engine); +}; + +struct i2c_engine; +struct aux_engine; + +struct i2caux { + struct dc_context *ctx; + const struct i2caux_funcs *funcs; + /* On ASIC we have certain amount of lines with HW DDC engine + * (4, 6, or maybe more in the future). + * For every such line, we create separate HW DDC engine + * (since we have these engines in HW) and separate SW DDC engine + * (to allow concurrent use of few lines). + * In similar way we have AUX engines. */ + + /* I2C SW engines, per DDC line. + * Only lines with HW DDC support will be initialized */ + struct i2c_engine *i2c_sw_engines[GPIO_DDC_LINE_COUNT]; + + /* I2C HW engines, per DDC line. + * Only lines with HW DDC support will be initialized */ + struct i2c_engine *i2c_hw_engines[GPIO_DDC_LINE_COUNT]; + + /* AUX engines, per DDC line. + * Only lines with HW AUX support will be initialized */ + struct aux_engine *aux_engines[GPIO_DDC_LINE_COUNT]; + + /* For all other lines, we can use + * single instance of generic I2C HW engine + * (since in HW, there is single instance of it) + * or single instance of generic I2C SW engine. + * AUX is not supported for other lines. */ + + /* General-purpose I2C SW engine. + * Can be assigned dynamically to any line per transaction */ + struct i2c_engine *i2c_generic_sw_engine; + + /* General-purpose I2C generic HW engine. + * Can be assigned dynamically to almost any line per transaction */ + struct i2c_engine *i2c_generic_hw_engine; + + /* [anaumov] in DAL2, there is a Mutex */ + + uint32_t aux_timeout_period; + + /* expressed in KHz */ + uint32_t default_i2c_sw_speed; + uint32_t default_i2c_hw_speed; +}; + +bool dal_i2caux_construct( + struct i2caux *i2caux, + struct adapter_service *as, + struct dc_context *ctx); + +void dal_i2caux_release_engine( + struct i2caux *i2caux, + struct engine *engine); + +void dal_i2caux_destruct( + struct i2caux *i2caux); + +void dal_i2caux_destroy( + struct i2caux **ptr); + +struct i2c_engine *dal_i2caux_acquire_i2c_sw_engine( + struct i2caux *i2caux, + struct ddc *ddc); + +struct aux_engine *dal_i2caux_acquire_aux_engine( + struct i2caux *i2caux, + struct ddc *ddc); + +#endif -- 2.1.4 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel