| /* |
| * 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_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 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; |
| |
| udelay(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; |
| |
| udelay(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); |
| |
| 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(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; |
| } |
| |
| 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); |
| |
| 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(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 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 { |
| 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(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(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( |
| 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); |
| |
| 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(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 i2c_write( |
| 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(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, |
| uint32_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) |
| { |
| /* No arbitration with VBIOS is performed since DCE 6.0 */ |
| return 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)); |
| |
| kfree(*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, |
| }; |
| |
| void dal_i2c_sw_engine_construct( |
| struct i2c_sw_engine *engine, |
| const struct i2c_sw_engine_create_arg *arg) |
| { |
| dal_i2c_engine_construct(&engine->base, arg->ctx); |
| dal_i2c_sw_engine_set_speed(&engine->base, arg->default_speed); |
| engine->base.funcs = &i2c_engine_funcs; |
| engine->base.base.funcs = &engine_funcs; |
| } |
| |
| 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 = kzalloc(sizeof(struct i2c_sw_engine), GFP_KERNEL); |
| |
| if (!engine) { |
| BREAK_TO_DEBUGGER(); |
| return NULL; |
| } |
| |
| dal_i2c_sw_engine_construct(engine, arg); |
| return &engine->base; |
| } |