diff options
Diffstat (limited to 'drivers/gpu/drm/amd/display/dc/dce/dce_i2c_sw.c')
-rw-r--r-- | drivers/gpu/drm/amd/display/dc/dce/dce_i2c_sw.c | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/display/dc/dce/dce_i2c_sw.c b/drivers/gpu/drm/amd/display/dc/dce/dce_i2c_sw.c new file mode 100644 index 000000000000..f0266694cb56 --- /dev/null +++ b/drivers/gpu/drm/amd/display/dc/dce/dce_i2c_sw.c @@ -0,0 +1,541 @@ +/* + * Copyright 2018 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 "dce_i2c.h" +#include "dce_i2c_sw.h" +#include "include/gpio_service_interface.h" +#define SCL false +#define SDA true + +void dce_i2c_sw_construct( + struct dce_i2c_sw *dce_i2c_sw, + struct dc_context *ctx) +{ + dce_i2c_sw->ctx = ctx; +} + +static inline bool read_bit_from_ddc( + struct ddc *ddc, + bool data_nor_clock) +{ + uint32_t value = 0; + + if (data_nor_clock) + dal_gpio_get_value(ddc->pin_data, &value); + else + dal_gpio_get_value(ddc->pin_clock, &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_gpio_set_value(ddc->pin_data, value); + else + dal_gpio_set_value(ddc->pin_clock, value); +} + +static void release_engine_dce_sw( + struct resource_pool *pool, + struct dce_i2c_sw *dce_i2c_sw) +{ + dal_ddc_close(dce_i2c_sw->ddc); + dce_i2c_sw->ddc = NULL; +} + +static bool get_hw_supported_ddc_line( + struct ddc *ddc, + enum gpio_ddc_line *line) +{ + enum gpio_ddc_line line_found; + + *line = GPIO_DDC_LINE_UNKNOWN; + + if (!ddc) { + BREAK_TO_DEBUGGER(); + return false; + } + + if (!ddc->hw_info.hw_supported) + return false; + + line_found = dal_ddc_get_line(ddc); + + if (line_found >= GPIO_DDC_LINE_COUNT) + return false; + + *line = line_found; + + return true; +} +static bool wait_for_scl_high_sw( + 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; + + udelay(clock_delay_div_4); + + do { + if (read_bit_from_ddc(ddc, SCL)) + return true; + + udelay(clock_delay_div_4); + + ++scl_retry; + } while (scl_retry <= scl_retry_max); + + return false; +} +static bool write_byte_sw( + 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 { + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, (byte >> shift) & 1); + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high_sw(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 + */ + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, true); + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high_sw(ctx, ddc_handle, clock_delay_div_4)) + return false; + + /* read ACK bit */ + + ack = !read_bit_from_ddc(ddc_handle, SDA); + + udelay(clock_delay_div_4 << 1); + + write_bit_to_ddc(ddc_handle, SCL, false); + + udelay(clock_delay_div_4 << 1); + + return ack; +} + +static bool read_byte_sw( + 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_sw(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); + + udelay(clock_delay_div_4 << 1); + + --shift; + } while (shift >= 0); + + /* read only whole byte */ + + *byte = data; + + udelay(clock_delay_div_4); + + /* send the acknowledge bit: + * SDA low means ACK, SDA high means NACK + */ + + write_bit_to_ddc(ddc_handle, SDA, !more); + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high_sw(ctx, ddc_handle, clock_delay_div_4)) + return false; + + write_bit_to_ddc(ddc_handle, SCL, false); + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, true); + + udelay(clock_delay_div_4); + + return true; +} +static bool stop_sync_sw( + 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); + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SDA, false); + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high_sw(ctx, ddc_handle, clock_delay_div_4)) + return false; + + write_bit_to_ddc(ddc_handle, SDA, true); + + do { + udelay(clock_delay_div_4); + + if (read_bit_from_ddc(ddc_handle, SDA)) + return true; + + ++retry; + } while (retry <= 2); + + return false; +} +static bool i2c_write_sw( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4, + uint8_t address, + uint32_t length, + const uint8_t *data) +{ + uint32_t i = 0; + + if (!write_byte_sw(ctx, ddc_handle, clock_delay_div_4, address)) + return false; + + while (i < length) { + if (!write_byte_sw(ctx, ddc_handle, clock_delay_div_4, data[i])) + return false; + ++i; + } + + return true; +} + +static bool i2c_read_sw( + struct dc_context *ctx, + struct ddc *ddc_handle, + uint16_t clock_delay_div_4, + uint8_t address, + uint32_t length, + uint8_t *data) +{ + uint32_t i = 0; + + if (!write_byte_sw(ctx, ddc_handle, clock_delay_div_4, address)) + return false; + + while (i < length) { + if (!read_byte_sw(ctx, ddc_handle, clock_delay_div_4, data + i, + i < length - 1)) + return false; + ++i; + } + + return true; +} + + + +static bool start_sync_sw( + 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); + + udelay(clock_delay_div_4); + + do { + write_bit_to_ddc(ddc_handle, SDA, true); + + if (!read_bit_from_ddc(ddc_handle, SDA)) { + ++retry; + continue; + } + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, true); + + if (!wait_for_scl_high_sw(ctx, ddc_handle, clock_delay_div_4)) + break; + + write_bit_to_ddc(ddc_handle, SDA, false); + + udelay(clock_delay_div_4); + + write_bit_to_ddc(ddc_handle, SCL, false); + + udelay(clock_delay_div_4); + + return true; + } while (retry <= I2C_SW_RETRIES); + + return false; +} + +void dce_i2c_sw_engine_set_speed( + struct dce_i2c_sw *engine, + uint32_t speed) +{ + ASSERT(speed); + + engine->speed = speed ? speed : DCE_I2C_DEFAULT_I2C_SW_SPEED; + + engine->clock_delay = 1000 / engine->speed; + + if (engine->clock_delay < 12) + engine->clock_delay = 12; +} + +bool dce_i2c_sw_engine_acquire_engine( + struct dce_i2c_sw *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->ddc = ddc; + + return true; +} +bool dce_i2c_engine_acquire_sw( + struct dce_i2c_sw *dce_i2c_sw, + struct ddc *ddc_handle) +{ + uint32_t counter = 0; + bool result; + + do { + + result = dce_i2c_sw_engine_acquire_engine( + dce_i2c_sw, ddc_handle); + + if (result) + break; + + /* i2c_engine is busy by VBios, lets wait and retry */ + + udelay(10); + + ++counter; + } while (counter < 2); + + return result; +} + + + + +void dce_i2c_sw_engine_submit_channel_request( + struct dce_i2c_sw *engine, + struct i2c_request_transaction_data *req) +{ + struct ddc *ddc = engine->ddc; + uint16_t clock_delay_div_4 = engine->clock_delay >> 2; + + /* send sync (start / repeated start) */ + + bool result = start_sync_sw(engine->ctx, ddc, clock_delay_div_4); + + /* process payload */ + + if (result) { + switch (req->action) { + case DCE_I2C_TRANSACTION_ACTION_I2C_WRITE: + case DCE_I2C_TRANSACTION_ACTION_I2C_WRITE_MOT: + result = i2c_write_sw(engine->ctx, ddc, clock_delay_div_4, + req->address, req->length, req->data); + break; + case DCE_I2C_TRANSACTION_ACTION_I2C_READ: + case DCE_I2C_TRANSACTION_ACTION_I2C_READ_MOT: + result = i2c_read_sw(engine->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 == DCE_I2C_TRANSACTION_ACTION_I2C_WRITE) || + (req->action == DCE_I2C_TRANSACTION_ACTION_I2C_READ)) + if (!stop_sync_sw(engine->ctx, ddc, clock_delay_div_4)) + result = false; + + req->status = result ? + I2C_CHANNEL_OPERATION_SUCCEEDED : + I2C_CHANNEL_OPERATION_FAILED; +} +bool dce_i2c_sw_engine_submit_payload( + struct dce_i2c_sw *engine, + struct i2c_payload *payload, + bool middle_of_transaction) +{ + struct i2c_request_transaction_data request; + + if (!payload->write) + request.action = middle_of_transaction ? + DCE_I2C_TRANSACTION_ACTION_I2C_READ_MOT : + DCE_I2C_TRANSACTION_ACTION_I2C_READ; + else + request.action = middle_of_transaction ? + DCE_I2C_TRANSACTION_ACTION_I2C_WRITE_MOT : + DCE_I2C_TRANSACTION_ACTION_I2C_WRITE; + + request.address = (uint8_t) ((payload->address << 1) | !payload->write); + request.length = payload->length; + request.data = payload->data; + + dce_i2c_sw_engine_submit_channel_request(engine, &request); + + if ((request.status == I2C_CHANNEL_OPERATION_ENGINE_BUSY) || + (request.status == I2C_CHANNEL_OPERATION_FAILED)) + return false; + + return true; +} +bool dce_i2c_submit_command_sw( + struct resource_pool *pool, + struct ddc *ddc, + struct i2c_command *cmd, + struct dce_i2c_sw *dce_i2c_sw) +{ + uint8_t index_of_payload = 0; + bool result; + + dce_i2c_sw_engine_set_speed(dce_i2c_sw, 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; + + if (!dce_i2c_sw_engine_submit_payload( + dce_i2c_sw, payload, mot)) { + result = false; + break; + } + + ++index_of_payload; + } + + release_engine_dce_sw(pool, dce_i2c_sw); + + return result; +} +struct dce_i2c_sw *dce_i2c_acquire_i2c_sw_engine( + struct resource_pool *pool, + struct ddc *ddc) +{ + enum gpio_ddc_line line; + struct dce_i2c_sw *engine = NULL; + + if (get_hw_supported_ddc_line(ddc, &line)) + engine = pool->sw_i2cs[line]; + + if (!engine) + return NULL; + + if (!dce_i2c_engine_acquire_sw(engine, ddc)) + return NULL; + + return engine; +} |