mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-09 08:31:17 +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);
|
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(
|
static bool setup_engine(
|
||||||
struct dce_i2c_hw *dce_i2c_hw)
|
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 i2c_setup_limit = I2C_SETUP_TIME_LIMIT_DCE;
|
||||||
uint32_t reset_length = 0;
|
uint32_t reset_length = 0;
|
||||||
|
|
||||||
@ -309,8 +335,8 @@ static bool setup_engine(
|
|||||||
REG_UPDATE_N(SETUP, 1,
|
REG_UPDATE_N(SETUP, 1,
|
||||||
FN(DC_I2C_DDC1_SETUP, DC_I2C_DDC1_CLK_EN), 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*/
|
if (!acquire_engine(dce_i2c_hw))
|
||||||
REG_UPDATE(DC_I2C_ARBITRATION, DC_I2C_SW_USE_I2C_REG_REQ, 1);
|
return false;
|
||||||
|
|
||||||
/*set SW requested I2c speed to default, if API calls in it will be override later*/
|
/*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);
|
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;
|
i2c_setup_limit = dce_i2c_hw->setup_limit;
|
||||||
|
|
||||||
/* Program pin select */
|
/* Program pin select */
|
||||||
REG_UPDATE_6(DC_I2C_CONTROL,
|
REG_UPDATE_5(DC_I2C_CONTROL,
|
||||||
DC_I2C_GO, 0,
|
DC_I2C_GO, 0,
|
||||||
DC_I2C_SOFT_RESET, 0,
|
|
||||||
DC_I2C_SEND_RESET, 0,
|
DC_I2C_SEND_RESET, 0,
|
||||||
DC_I2C_SW_STATUS_RESET, 1,
|
DC_I2C_SW_STATUS_RESET, 1,
|
||||||
DC_I2C_TRANSACTION_COUNT, 0,
|
DC_I2C_TRANSACTION_COUNT, 0,
|
||||||
@ -351,6 +376,26 @@ static bool setup_engine(
|
|||||||
return true;
|
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(
|
static void release_engine(
|
||||||
struct dce_i2c_hw *dce_i2c_hw)
|
struct dce_i2c_hw *dce_i2c_hw)
|
||||||
{
|
{
|
||||||
@ -378,9 +423,9 @@ static void release_engine(
|
|||||||
|
|
||||||
/*for HW HDCP Ri polling failure w/a test*/
|
/*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);
|
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 */
|
// Release I2C engine so it can be used by HW or DMCU, automatically clears SW_USE_I2C
|
||||||
REG_UPDATE_2(DC_I2C_ARBITRATION, DC_I2C_SW_DONE_USING_I2C_REG, 1,
|
REG_UPDATE(DC_I2C_ARBITRATION, DC_I2C_SW_DONE_USING_I2C_REG, true);
|
||||||
DC_I2C_SW_USE_I2C_REG_REQ, 0);
|
cntl_stuck_hw_workaround(dce_i2c_hw);
|
||||||
|
|
||||||
if (dce_i2c_hw->ctx->dc->debug.enable_mem_low_power.bits.i2c) {
|
if (dce_i2c_hw->ctx->dc->debug.enable_mem_low_power.bits.i2c) {
|
||||||
if (dce_i2c_hw->regs->DIO_MEM_PWR_CTRL)
|
if (dce_i2c_hw->regs->DIO_MEM_PWR_CTRL)
|
||||||
|
Loading…
Reference in New Issue
Block a user