| /* |
| * 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 <linux/delay.h> |
| |
| #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; |
| } |