Commit 04d57f44 authored by Dominik Kaszewski's avatar Dominik Kaszewski Committed by Alex Deucher
Browse files

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: default avatarAlvin Lee <alvin.lee2@amd.com>
Signed-off-by: default avatarDominik Kaszewski <dominik.kaszewski@amd.com>
Signed-off-by: default avatarIvan Lipski <ivan.lipski@amd.com>
Tested-by: default avatarDaniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: default avatarAlex Deucher <alexander.deucher@amd.com>
parent 48cb9c3b
Loading
Loading
Loading
Loading
+52 −7
Original line number Diff line number Diff line
@@ -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)