mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-27 15:50:48 +00:00
drm/amd/display: Workaround for stuck I2C arbitrage
[Why] When booting without an HDMI display connected, the I2C registers are not initialized correctly, leading to DC_I2C_ARBITRATION register getting stuck with DC_I2C_REG_RW_CNTL_STATUS == USED_BY_SW. [How] * Correct TOCTOU race condition in engine acquire logic which did not check against DMUB trying to acquire it at the same time. * Deassert SOFT_RESET before acquire, as it can block access to other I2C registers. * Add a workaround in release, checking that after triggerring DC_I2C_SW_DONE_USING_I2C_REG, DC_I2C_REG_RW_CNTL_STATUS != USED_BY_SW. If necessary, trigger DC_I2C_SW_DONE_USING_I2C_REG again. * Remove unnecessary clear of DC_I2C_SW_USE_I2C_REG_REQ, which engine ignores according to specification. Reviewed-by: Alvin Lee <alvin.lee2@amd.com> Signed-off-by: Dominik Kaszewski <dominik.kaszewski@amd.com> Signed-off-by: Ivan Lipski <ivan.lipski@amd.com> Tested-by: Daniel Wheeler <daniel.wheeler@amd.com> Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
This commit is contained in:
parent
48cb9c3b21
commit
04d57f4462
@ -292,9 +292,35 @@ static void set_speed(
|
||||
FN(DC_I2C_DDC1_SPEED, DC_I2C_DDC1_THRESHOLD), 2);
|
||||
}
|
||||
|
||||
static bool acquire_engine(struct dce_i2c_hw *dce_i2c_hw)
|
||||
{
|
||||
uint32_t arbitrate = 0;
|
||||
|
||||
REG_GET(DC_I2C_ARBITRATION, DC_I2C_REG_RW_CNTL_STATUS, &arbitrate);
|
||||
switch (arbitrate) {
|
||||
case DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_SW:
|
||||
return true;
|
||||
case DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_HW:
|
||||
return false;
|
||||
case DC_I2C_STATUS__DC_I2C_STATUS_IDLE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
REG_UPDATE(DC_I2C_ARBITRATION, DC_I2C_SW_USE_I2C_REG_REQ, true);
|
||||
REG_GET(DC_I2C_ARBITRATION, DC_I2C_REG_RW_CNTL_STATUS, &arbitrate);
|
||||
if (arbitrate != DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_SW)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_engine(
|
||||
struct dce_i2c_hw *dce_i2c_hw)
|
||||
{
|
||||
// Deassert soft reset to unblock I2C engine registers
|
||||
REG_UPDATE(DC_I2C_CONTROL, DC_I2C_SOFT_RESET, false);
|
||||
|
||||
uint32_t i2c_setup_limit = I2C_SETUP_TIME_LIMIT_DCE;
|
||||
uint32_t reset_length = 0;
|
||||
|
||||
@ -309,8 +335,8 @@ static bool setup_engine(
|
||||
REG_UPDATE_N(SETUP, 1,
|
||||
FN(DC_I2C_DDC1_SETUP, DC_I2C_DDC1_CLK_EN), 1);
|
||||
|
||||
/* we have checked I2c not used by DMCU, set SW use I2C REQ to 1 to indicate SW using it*/
|
||||
REG_UPDATE(DC_I2C_ARBITRATION, DC_I2C_SW_USE_I2C_REG_REQ, 1);
|
||||
if (!acquire_engine(dce_i2c_hw))
|
||||
return false;
|
||||
|
||||
/*set SW requested I2c speed to default, if API calls in it will be override later*/
|
||||
set_speed(dce_i2c_hw, dce_i2c_hw->ctx->dc->caps.i2c_speed_in_khz);
|
||||
@ -319,9 +345,8 @@ static bool setup_engine(
|
||||
i2c_setup_limit = dce_i2c_hw->setup_limit;
|
||||
|
||||
/* Program pin select */
|
||||
REG_UPDATE_6(DC_I2C_CONTROL,
|
||||
REG_UPDATE_5(DC_I2C_CONTROL,
|
||||
DC_I2C_GO, 0,
|
||||
DC_I2C_SOFT_RESET, 0,
|
||||
DC_I2C_SEND_RESET, 0,
|
||||
DC_I2C_SW_STATUS_RESET, 1,
|
||||
DC_I2C_TRANSACTION_COUNT, 0,
|
||||
@ -351,6 +376,26 @@ static bool setup_engine(
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we boot without an HDMI display, the I2C engine does not get initialized
|
||||
* correctly. One of its symptoms is that SW_USE_I2C does not get cleared after
|
||||
* acquire, so that after setting SW_DONE_USING_I2C on release, the engine gets
|
||||
* immediately reacquired by SW, preventing DMUB from using it.
|
||||
*/
|
||||
static void cntl_stuck_hw_workaround(struct dce_i2c_hw *dce_i2c_hw)
|
||||
{
|
||||
uint32_t arbitrate = 0;
|
||||
|
||||
REG_GET(DC_I2C_ARBITRATION, DC_I2C_REG_RW_CNTL_STATUS, &arbitrate);
|
||||
if (arbitrate != DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_SW)
|
||||
return;
|
||||
|
||||
// Still acquired after release, release again as a workaround
|
||||
REG_UPDATE(DC_I2C_ARBITRATION, DC_I2C_SW_DONE_USING_I2C_REG, true);
|
||||
REG_GET(DC_I2C_ARBITRATION, DC_I2C_REG_RW_CNTL_STATUS, &arbitrate);
|
||||
ASSERT(arbitrate != DC_I2C_STATUS__DC_I2C_STATUS_USED_BY_SW);
|
||||
}
|
||||
|
||||
static void release_engine(
|
||||
struct dce_i2c_hw *dce_i2c_hw)
|
||||
{
|
||||
@ -378,9 +423,9 @@ static void release_engine(
|
||||
|
||||
/*for HW HDCP Ri polling failure w/a test*/
|
||||
set_speed(dce_i2c_hw, dce_i2c_hw->ctx->dc->caps.i2c_speed_in_khz_hdcp);
|
||||
/* Release I2C after reset, so HW or DMCU could use it */
|
||||
REG_UPDATE_2(DC_I2C_ARBITRATION, DC_I2C_SW_DONE_USING_I2C_REG, 1,
|
||||
DC_I2C_SW_USE_I2C_REG_REQ, 0);
|
||||
// Release I2C engine so it can be used by HW or DMCU, automatically clears SW_USE_I2C
|
||||
REG_UPDATE(DC_I2C_ARBITRATION, DC_I2C_SW_DONE_USING_I2C_REG, true);
|
||||
cntl_stuck_hw_workaround(dce_i2c_hw);
|
||||
|
||||
if (dce_i2c_hw->ctx->dc->debug.enable_mem_low_power.bits.i2c) {
|
||||
if (dce_i2c_hw->regs->DIO_MEM_PWR_CTRL)
|
||||
|
Loading…
Reference in New Issue
Block a user