Skip to content
Snippets Groups Projects
sil-sii8620.c 56.9 KiB
Newer Older
			| BIT_TPI_SC_TPI_AV_MUTE;
		break;
	case SINK_HDMI:
		val = BIT_TPI_SC_REG_TMDS_OE_POWER_DOWN
			| BIT_TPI_SC_TPI_AV_MUTE
			| BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI;
		break;
	}

	sii8620_write(ctx, REG_TPI_SC, val);
}

static void sii8620_set_format(struct sii8620 *ctx)
{
	if (sii8620_is_mhl3(ctx)) {
		sii8620_setbits(ctx, REG_M3_P0CTRL,
				BIT_M3_P0CTRL_MHL3_P0_PIXEL_MODE_PACKED,
				ctx->use_packed_pixel ? ~0 : 0);
	} else {
		if (ctx->use_packed_pixel) {
			sii8620_write_seq_static(ctx,
				REG_VID_MODE, BIT_VID_MODE_M1080P,
				REG_MHL_TOP_CTL, BIT_MHL_TOP_CTL_MHL_PP_SEL | 1,
				REG_MHLTX_CTL6, 0x60
			);
		} else {
			sii8620_write_seq_static(ctx,
				REG_VID_MODE, 0,
				REG_MHL_TOP_CTL, 1,
				REG_MHLTX_CTL6, 0xa0
			);
	if (ctx->use_packed_pixel)
		out_fmt = VAL_TPI_FORMAT(YCBCR422, FULL);
	else
		out_fmt = VAL_TPI_FORMAT(RGB, FULL);

	sii8620_write_seq(ctx,
		REG_TPI_INPUT, VAL_TPI_FORMAT(RGB, FULL),
		REG_TPI_OUTPUT, out_fmt,
	);
}

static int mhl3_infoframe_init(struct mhl3_infoframe *frame)
{
	memset(frame, 0, sizeof(*frame));

	frame->version = 3;
	frame->hev_format = -1;
	return 0;
}

static ssize_t mhl3_infoframe_pack(struct mhl3_infoframe *frame,
		 void *buffer, size_t size)
{
	const int frm_len = HDMI_INFOFRAME_HEADER_SIZE + MHL3_INFOFRAME_SIZE;
	u8 *ptr = buffer;

	if (size < frm_len)
		return -ENOSPC;

	memset(buffer, 0, size);
	ptr[0] = HDMI_INFOFRAME_TYPE_VENDOR;
	ptr[1] = frame->version;
	ptr[2] = MHL3_INFOFRAME_SIZE;
	ptr[4] = MHL3_IEEE_OUI & 0xff;
	ptr[5] = (MHL3_IEEE_OUI >> 8) & 0xff;
	ptr[6] = (MHL3_IEEE_OUI >> 16) & 0xff;
	ptr[7] = frame->video_format & 0x3;
	ptr[7] |= (frame->format_type & 0x7) << 2;
	ptr[7] |= frame->sep_audio ? BIT(5) : 0;
	if (frame->hev_format >= 0) {
		ptr[9] = 1;
		ptr[10] = (frame->hev_format >> 8) & 0xff;
		ptr[11] = frame->hev_format & 0xff;
	}
	if (frame->av_delay) {
		bool sign = frame->av_delay < 0;
		int delay = sign ? -frame->av_delay : frame->av_delay;

		ptr[12] = (delay >> 16) & 0xf;
		if (sign)
			ptr[12] |= BIT(4);
		ptr[13] = (delay >> 8) & 0xff;
		ptr[14] = delay & 0xff;
	}
	ptr[3] -= sii8620_checksum(buffer, frm_len);
	return frm_len;
}

static void sii8620_set_infoframes(struct sii8620 *ctx,
				   struct drm_display_mode *mode)
{
	struct mhl3_infoframe mhl_frm;
	union hdmi_infoframe frm;
	u8 buf[31];
	int ret;

	ret = drm_hdmi_avi_infoframe_from_display_mode(&frm.avi,
	if (ctx->use_packed_pixel)
		frm.avi.colorspace = HDMI_COLORSPACE_YUV422;

	if (!ret)
		ret = hdmi_avi_infoframe_pack(&frm.avi, buf, ARRAY_SIZE(buf));
	if (ret > 0)
		sii8620_write_buf(ctx, REG_TPI_AVI_CHSUM, buf + 3, ret - 3);

	if (!sii8620_is_mhl3(ctx) || !ctx->use_packed_pixel) {
		sii8620_write(ctx, REG_TPI_SC,
			BIT_TPI_SC_TPI_OUTPUT_MODE_0_HDMI);
		sii8620_write(ctx, REG_PKT_FILTER_0,
			BIT_PKT_FILTER_0_DROP_CEA_GAMUT_PKT |
			BIT_PKT_FILTER_0_DROP_MPEG_PKT |
			BIT_PKT_FILTER_0_DROP_GCP_PKT,
			BIT_PKT_FILTER_1_DROP_GEN_PKT);
		return;
	}

	sii8620_write(ctx, REG_PKT_FILTER_0,
		BIT_PKT_FILTER_0_DROP_CEA_GAMUT_PKT |
		BIT_PKT_FILTER_0_DROP_MPEG_PKT |
		BIT_PKT_FILTER_0_DROP_AVI_PKT |
		BIT_PKT_FILTER_0_DROP_GCP_PKT,
		BIT_PKT_FILTER_1_VSI_OVERRIDE_DIS |
		BIT_PKT_FILTER_1_DROP_GEN_PKT |
		BIT_PKT_FILTER_1_DROP_VSIF_PKT);

	sii8620_write(ctx, REG_TPI_INFO_FSEL, BIT_TPI_INFO_FSEL_EN
		| BIT_TPI_INFO_FSEL_RPT | VAL_TPI_INFO_FSEL_VSI);
	ret = mhl3_infoframe_init(&mhl_frm);
	if (!ret)
		ret = mhl3_infoframe_pack(&mhl_frm, buf, ARRAY_SIZE(buf));
	sii8620_write_buf(ctx, REG_TPI_INFO_B0, buf, ret);
}

static void sii8620_start_video(struct sii8620 *ctx)
	struct drm_display_mode *mode =
		&ctx->bridge.encoder->crtc->state->adjusted_mode;

	if (!sii8620_is_mhl3(ctx))
		sii8620_stop_video(ctx);

	if (ctx->sink_type == SINK_DVI && !sii8620_is_mhl3(ctx)) {
		sii8620_write(ctx, REG_RX_HDMI_CTRL2,
			      VAL_RX_HDMI_CTRL2_DEFVAL);
		sii8620_write(ctx, REG_TPI_SC, 0);
		return;
	}

	sii8620_write_seq_static(ctx,
		REG_RX_HDMI_CTRL2, VAL_RX_HDMI_CTRL2_DEFVAL
			| BIT_RX_HDMI_CTRL2_USE_AV_MUTE,
		REG_VID_OVRRD, BIT_VID_OVRRD_PP_AUTO_DISABLE
			| BIT_VID_OVRRD_M1080P_OVRRD);
	sii8620_set_format(ctx);
	if (!sii8620_is_mhl3(ctx)) {
		u8 link_mode = MHL_DST_LM_PATH_ENABLED;

		if (ctx->use_packed_pixel)
			link_mode |= MHL_DST_LM_CLK_MODE_PACKED_PIXEL;
		else
			link_mode |= MHL_DST_LM_CLK_MODE_NORMAL;

		sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE), link_mode);
		sii8620_set_auto_zone(ctx);
	} else {
		static const struct {
			int max_clk;
			u8 zone;
			u8 link_rate;
			u8 rrp_decode;
		} clk_spec[] = {
			{ 150000, VAL_TX_ZONE_CTL3_TX_ZONE_1_5GBPS,
			  MHL_XDS_LINK_RATE_1_5_GBPS, 0x38 },
			{ 300000, VAL_TX_ZONE_CTL3_TX_ZONE_3GBPS,
			  MHL_XDS_LINK_RATE_3_0_GBPS, 0x40 },
			{ 600000, VAL_TX_ZONE_CTL3_TX_ZONE_6GBPS,
			  MHL_XDS_LINK_RATE_6_0_GBPS, 0x40 },
		};
		u8 p0_ctrl = BIT_M3_P0CTRL_MHL3_P0_PORT_EN;
		int clk = mode->clock * (ctx->use_packed_pixel ? 2 : 3);
		for (i = 0; i < ARRAY_SIZE(clk_spec) - 1; ++i)
			if (clk < clk_spec[i].max_clk)
				break;
		if (100 * clk >= 98 * clk_spec[i].max_clk)
			p0_ctrl |= BIT_M3_P0CTRL_MHL3_P0_UNLIMIT_EN;
		sii8620_burst_tx_bits_per_pixel_fmt(ctx, ctx->use_packed_pixel);
		sii8620_burst_send(ctx);
		sii8620_write_seq(ctx,
			REG_MHL_DP_CTL0, 0xf0,
			REG_MHL3_TX_ZONE_CTL, clk_spec[i].zone);
		sii8620_setbits(ctx, REG_M3_P0CTRL,
			BIT_M3_P0CTRL_MHL3_P0_PORT_EN
			| BIT_M3_P0CTRL_MHL3_P0_UNLIMIT_EN, p0_ctrl);
		sii8620_setbits(ctx, REG_M3_POSTM, MSK_M3_POSTM_RRP_DECODE,
			clk_spec[i].rrp_decode);
		sii8620_write_seq_static(ctx,
			REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE
				| BIT_M3_CTRL_H2M_SWRST,
			REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE
		);
		sii8620_mt_write_stat(ctx, MHL_XDS_REG(AVLINK_MODE_CONTROL),
			clk_spec[i].link_rate);
	}
	sii8620_set_infoframes(ctx, mode);
}

static void sii8620_disable_hpd(struct sii8620 *ctx)
{
	sii8620_setbits(ctx, REG_EDID_CTRL, BIT_EDID_CTRL_EDID_PRIME_VALID, 0);
	sii8620_write_seq_static(ctx,
		REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN,
		REG_INTR8_MASK, 0
	);
}

static void sii8620_enable_hpd(struct sii8620 *ctx)
{
	sii8620_setbits(ctx, REG_TMDS_CSTAT_P3,
			BIT_TMDS_CSTAT_P3_SCDT_CLR_AVI_DIS
			| BIT_TMDS_CSTAT_P3_CLR_AVI, ~0);
	sii8620_write_seq_static(ctx,
		REG_HPD_CTRL, BIT_HPD_CTRL_HPD_OUT_OVR_EN
			| BIT_HPD_CTRL_HPD_HIGH,
	);
}

static void sii8620_mhl_discover(struct sii8620 *ctx)
{
	sii8620_write_seq_static(ctx,
		REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT
			| BIT_DISC_CTRL9_DISC_PULSE_PROCEED,
		REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_5K, VAL_PUP_20K),
		REG_CBUS_DISC_INTR0_MASK, BIT_MHL3_EST_INT
			| BIT_MHL_EST_INT
			| BIT_NOT_MHL_EST_INT
			| BIT_CBUS_MHL3_DISCON_INT
			| BIT_CBUS_MHL12_DISCON_INT
			| BIT_RGND_READY_INT,
		REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X
			| BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL
			| BIT_MHL_PLL_CTL0_ZONE_MASK_OE,
		REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE
			| BIT_MHL_DP_CTL0_TX_OE_OVR,
		REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE,
		REG_MHL_DP_CTL1, 0xA2,
		REG_MHL_DP_CTL2, 0x03,
		REG_MHL_DP_CTL3, 0x35,
		REG_MHL_DP_CTL5, 0x02,
		REG_MHL_DP_CTL6, 0x02,
		REG_MHL_DP_CTL7, 0x03,
		REG_COC_CTLC, 0xFF,
		REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12
			| BIT_DPD_OSC_EN | BIT_DPD_PWRON_HSIC,
		REG_COC_INTR_MASK, BIT_COC_PLL_LOCK_STATUS_CHANGE
			| BIT_COC_CALIBRATION_DONE,
		REG_CBUS_INT_1_MASK, BIT_CBUS_MSC_ABORT_RCVD
			| BIT_CBUS_CMD_ABORT,
		REG_CBUS_INT_0_MASK, BIT_CBUS_MSC_MT_DONE
			| BIT_CBUS_HPD_CHG
			| BIT_CBUS_MSC_MR_WRITE_STAT
			| BIT_CBUS_MSC_MR_MSC_MSG
			| BIT_CBUS_MSC_MR_WRITE_BURST
			| BIT_CBUS_MSC_MR_SET_INT
			| BIT_CBUS_MSC_MT_DONE_NACK
	);
}

static void sii8620_peer_specific_init(struct sii8620 *ctx)
{
	if (sii8620_is_mhl3(ctx))
		sii8620_write_seq_static(ctx,
			REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD,
			REG_EMSCINTRMASK1,
				BIT_EMSCINTR1_EMSC_TRAINING_COMMA_ERR
		);
	else
		sii8620_write_seq_static(ctx,
			REG_HDCP2X_INTR0_MASK, 0x00,
			REG_EMSCINTRMASK1, 0x00,
			REG_HDCP2X_INTR0, 0xFF,
			REG_INTR1, 0xFF,
			REG_SYS_CTRL1, BIT_SYS_CTRL1_BLOCK_DDC_BY_HPD
				| BIT_SYS_CTRL1_TX_CTRL_HDMI
		);
}

#define SII8620_MHL_VERSION			0x32
#define SII8620_SCRATCHPAD_SIZE			16
#define SII8620_INT_STAT_SIZE			0x33

static void sii8620_set_dev_cap(struct sii8620 *ctx)
{
	static const u8 devcap[MHL_DCAP_SIZE] = {
		[MHL_DCAP_MHL_VERSION] = SII8620_MHL_VERSION,
		[MHL_DCAP_CAT] = MHL_DCAP_CAT_SOURCE | MHL_DCAP_CAT_POWER,
		[MHL_DCAP_ADOPTER_ID_H] = 0x01,
		[MHL_DCAP_ADOPTER_ID_L] = 0x41,
		[MHL_DCAP_VID_LINK_MODE] = MHL_DCAP_VID_LINK_RGB444
			| MHL_DCAP_VID_LINK_PPIXEL
			| MHL_DCAP_VID_LINK_16BPP,
		[MHL_DCAP_AUD_LINK_MODE] = MHL_DCAP_AUD_LINK_2CH,
		[MHL_DCAP_VIDEO_TYPE] = MHL_DCAP_VT_GRAPHICS,
		[MHL_DCAP_LOG_DEV_MAP] = MHL_DCAP_LD_GUI,
		[MHL_DCAP_BANDWIDTH] = 0x0f,
		[MHL_DCAP_FEATURE_FLAG] = MHL_DCAP_FEATURE_RCP_SUPPORT
			| MHL_DCAP_FEATURE_RAP_SUPPORT
			| MHL_DCAP_FEATURE_SP_SUPPORT,
		[MHL_DCAP_SCRATCHPAD_SIZE] = SII8620_SCRATCHPAD_SIZE,
		[MHL_DCAP_INT_STAT_SIZE] = SII8620_INT_STAT_SIZE,
	};
	static const u8 xdcap[MHL_XDC_SIZE] = {
		[MHL_XDC_ECBUS_SPEEDS] = MHL_XDC_ECBUS_S_075
			| MHL_XDC_ECBUS_S_8BIT,
		[MHL_XDC_TMDS_SPEEDS] = MHL_XDC_TMDS_150
			| MHL_XDC_TMDS_300 | MHL_XDC_TMDS_600,
		[MHL_XDC_ECBUS_ROLES] = MHL_XDC_DEV_HOST,
		[MHL_XDC_LOG_DEV_MAPX] = MHL_XDC_LD_PHONE,
	};

	sii8620_write_buf(ctx, REG_MHL_DEVCAP_0, devcap, ARRAY_SIZE(devcap));
	sii8620_write_buf(ctx, REG_MHL_EXTDEVCAP_0, xdcap, ARRAY_SIZE(xdcap));
}

static void sii8620_mhl_init(struct sii8620 *ctx)
{
	sii8620_write_seq_static(ctx,
		REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K),
		REG_CBUS_MSC_COMPAT_CTRL,
			BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN,
	);

	sii8620_peer_specific_init(ctx);

	sii8620_disable_hpd(ctx);

	sii8620_write_seq_static(ctx,
		REG_EDID_CTRL, BIT_EDID_CTRL_EDID_FIFO_ADDR_AUTO,
		REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT
			| BIT_DISC_CTRL9_WAKE_PULSE_BYPASS,
		REG_TMDS0_CCTRL1, 0x90,
		REG_TMDS_CLK_EN, 0x01,
		REG_TMDS_CH_EN, 0x11,
		REG_BGR_BIAS, 0x87,
		REG_ALICE0_ZONE_CTRL, 0xE8,
		REG_ALICE0_MODE_CTRL, 0x04,
	);
	sii8620_setbits(ctx, REG_LM_DDC, BIT_LM_DDC_SW_TPI_EN_DISABLED, 0);
	sii8620_write_seq_static(ctx,
		REG_TPI_HW_OPT3, 0x76,
		REG_TMDS_CCTRL, BIT_TMDS_CCTRL_TMDS_OE,
		REG_TPI_DTD_B2, 79,
	);
	sii8620_set_dev_cap(ctx);
	sii8620_write_seq_static(ctx,
		REG_MDT_XMIT_TIMEOUT, 100,
		REG_MDT_XMIT_CTRL, 0x03,
		REG_MDT_XFIFO_STAT, 0x00,
		REG_MDT_RCV_TIMEOUT, 100,
		REG_CBUS_LINK_CTRL_8, 0x1D,
	);

	sii8620_start_gen2_write_burst(ctx);
	sii8620_write_seq_static(ctx,
		REG_BIST_CTRL, 0x00,
		REG_COC_CTL1, 0x10,
		REG_COC_CTL2, 0x18,
		REG_COC_CTLF, 0x07,
		REG_COC_CTL11, 0xF8,
		REG_COC_CTL17, 0x61,
		REG_COC_CTL18, 0x46,
		REG_COC_CTL19, 0x15,
		REG_COC_CTL1A, 0x01,
		REG_MHL_COC_CTL3, BIT_MHL_COC_CTL3_COC_AECHO_EN,
		REG_MHL_COC_CTL4, 0x2D,
		REG_MHL_COC_CTL5, 0xF9,
		REG_MSC_HEARTBEAT_CTRL, 0x27,
	);
	sii8620_disable_gen2_write_burst(ctx);

	sii8620_mt_write_stat(ctx, MHL_DST_REG(VERSION), SII8620_MHL_VERSION);
	sii8620_mt_write_stat(ctx, MHL_DST_REG(CONNECTED_RDY),
			      MHL_DST_CONN_DCAP_RDY | MHL_DST_CONN_XDEVCAPP_SUPP
			      | MHL_DST_CONN_POW_STAT);
	sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE), MHL_INT_RC_DCAP_CHG);
}

static void sii8620_emsc_enable(struct sii8620 *ctx)
{
	u8 reg;

	sii8620_setbits(ctx, REG_GENCTL, BIT_GENCTL_EMSC_EN
					 | BIT_GENCTL_CLR_EMSC_RFIFO
					 | BIT_GENCTL_CLR_EMSC_XFIFO, ~0);
	sii8620_setbits(ctx, REG_GENCTL, BIT_GENCTL_CLR_EMSC_RFIFO
					 | BIT_GENCTL_CLR_EMSC_XFIFO, 0);
	sii8620_setbits(ctx, REG_COMMECNT, BIT_COMMECNT_I2C_TO_EMSC_EN, ~0);
	reg = sii8620_readb(ctx, REG_EMSCINTR);
	sii8620_write(ctx, REG_EMSCINTR, reg);
	sii8620_write(ctx, REG_EMSCINTRMASK, BIT_EMSCINTR_SPI_DVLD);
}

static int sii8620_wait_for_fsm_state(struct sii8620 *ctx, u8 state)
{
	int i;

	for (i = 0; i < 10; ++i) {
		u8 s = sii8620_readb(ctx, REG_COC_STAT_0);

		if ((s & MSK_COC_STAT_0_FSM_STATE) == state)
			return 0;
		if (!(s & BIT_COC_STAT_0_PLL_LOCKED))
			return -EBUSY;
		usleep_range(4000, 6000);
	}
	return -ETIMEDOUT;
}

static void sii8620_set_mode(struct sii8620 *ctx, enum sii8620_mode mode)
{
	if (ctx->mode == mode)
		return;

	switch (mode) {
	case CM_MHL1:
		sii8620_write_seq_static(ctx,
			REG_CBUS_MSC_COMPAT_CTRL, 0x02,
			REG_M3_CTRL, VAL_M3_CTRL_MHL1_2_VALUE,
			REG_DPD, BIT_DPD_PWRON_PLL | BIT_DPD_PDNTX12
				| BIT_DPD_OSC_EN,
			REG_COC_INTR_MASK, 0
		);
		sii8620_write(ctx, REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE);
		ctx->mode = mode;
		return;
	case CM_ECBUS_S:
		sii8620_emsc_enable(ctx);
		sii8620_write_seq_static(ctx,
			REG_TTXSPINUMS, 4,
			REG_TRXSPINUMS, 4,
			REG_TTXHSICNUMS, 0x14,
			REG_TRXHSICNUMS, 0x14,
			REG_TTXTOTNUMS, 0x18,
			REG_TRXTOTNUMS, 0x18,
			REG_PWD_SRST, BIT_PWD_SRST_COC_DOC_RST
				      | BIT_PWD_SRST_CBUS_RST_SW_EN,
			REG_MHL_COC_CTL1, 0xbd,
			REG_PWD_SRST, BIT_PWD_SRST_CBUS_RST_SW_EN,
			REG_COC_CTLB, 0x01,
			REG_COC_CTL0, 0x5c,
			REG_COC_CTL14, 0x03,
			REG_COC_CTL15, 0x80,
			REG_MHL_DP_CTL6, BIT_MHL_DP_CTL6_DP_TAP1_SGN
					 | BIT_MHL_DP_CTL6_DP_TAP1_EN
					 | BIT_MHL_DP_CTL6_DT_PREDRV_FEEDCAP_EN,
			REG_MHL_DP_CTL8, 0x03
		);
		ret = sii8620_wait_for_fsm_state(ctx, 0x03);
		sii8620_write_seq_static(ctx,
			REG_COC_CTL14, 0x00,
			REG_COC_CTL15, 0x80
		);
		if (!ret)
			sii8620_write(ctx, REG_CBUS3_CNVT, 0x85);
		else
			sii8620_disconnect(ctx);
	case CM_DISCONNECTED:
		break;
	default:
		dev_err(ctx->dev, "%s mode %d not supported\n", __func__, mode);
		break;

	sii8620_set_auto_zone(ctx);

	if (mode != CM_MHL1)
		return;

	sii8620_write_seq_static(ctx,
		REG_MHL_DP_CTL0, 0xBC,
		REG_MHL_DP_CTL1, 0xBB,
		REG_MHL_DP_CTL3, 0x48,
		REG_MHL_DP_CTL5, 0x39,
		REG_MHL_DP_CTL2, 0x2A,
		REG_MHL_DP_CTL6, 0x2A,
		REG_MHL_DP_CTL7, 0x08
	);
}

static void sii8620_hpd_unplugged(struct sii8620 *ctx)
{
	sii8620_disable_hpd(ctx);
	ctx->sink_type = SINK_NONE;
	ctx->sink_detected = false;
	ctx->feature_complete = false;
	kfree(ctx->edid);
	ctx->edid = NULL;
}

static void sii8620_disconnect(struct sii8620 *ctx)
{
	sii8620_disable_gen2_write_burst(ctx);
	sii8620_stop_video(ctx);
	sii8620_cbus_reset(ctx);
	sii8620_set_mode(ctx, CM_DISCONNECTED);
	sii8620_write_seq_static(ctx,
		REG_TX_ZONE_CTL1, 0,
		REG_MHL_PLL_CTL0, 0x07,
		REG_COC_CTL0, 0x40,
		REG_CBUS3_CNVT, 0x84,
		REG_COC_CTL14, 0x00,
		REG_COC_CTL0, 0x40,
		REG_HRXCTRL3, 0x07,
		REG_MHL_PLL_CTL0, VAL_MHL_PLL_CTL0_HDMI_CLK_RATIO_1X
			| BIT_MHL_PLL_CTL0_CRYSTAL_CLK_SEL
			| BIT_MHL_PLL_CTL0_ZONE_MASK_OE,
		REG_MHL_DP_CTL0, BIT_MHL_DP_CTL0_DP_OE
			| BIT_MHL_DP_CTL0_TX_OE_OVR,
		REG_MHL_DP_CTL1, 0xBB,
		REG_MHL_DP_CTL3, 0x48,
		REG_MHL_DP_CTL5, 0x3F,
		REG_MHL_DP_CTL2, 0x2F,
		REG_MHL_DP_CTL6, 0x2A,
		REG_MHL_DP_CTL7, 0x03
	);
	sii8620_hpd_unplugged(ctx);
	sii8620_write_seq_static(ctx,
		REG_M3_CTRL, VAL_M3_CTRL_MHL3_VALUE,
		REG_MHL_COC_CTL1, 0x07,
		REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K),
		REG_DISC_CTRL8, 0x00,
		REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT
			| BIT_DISC_CTRL9_WAKE_PULSE_BYPASS,
		REG_INT_CTRL, 0x00,
		REG_MSC_HEARTBEAT_CTRL, 0x27,
		REG_DISC_CTRL1, 0x25,
		REG_CBUS_DISC_INTR0, (u8)~BIT_RGND_READY_INT,
		REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT,
		REG_MDT_INT_1, 0xff,
		REG_MDT_INT_1_MASK, 0x00,
		REG_MDT_INT_0, 0xff,
		REG_MDT_INT_0_MASK, 0x00,
		REG_COC_INTR, 0xff,
		REG_COC_INTR_MASK, 0x00,
		REG_TRXINTH, 0xff,
		REG_TRXINTMH, 0x00,
		REG_CBUS_INT_0, 0xff,
		REG_CBUS_INT_0_MASK, 0x00,
		REG_CBUS_INT_1, 0xff,
		REG_CBUS_INT_1_MASK, 0x00,
		REG_EMSCINTR, 0xff,
		REG_EMSCINTRMASK, 0x00,
		REG_EMSCINTR1, 0xff,
		REG_EMSCINTRMASK1, 0x00,
		REG_INTR8, 0xff,
		REG_INTR8_MASK, 0x00,
		REG_TPI_INTR_ST0, 0xff,
		REG_TPI_INTR_EN, 0x00,
		REG_HDCP2X_INTR0, 0xff,
		REG_HDCP2X_INTR0_MASK, 0x00,
		REG_INTR9, 0xff,
		REG_INTR9_MASK, 0x00,
		REG_INTR3, 0xff,
		REG_INTR3_MASK, 0x00,
		REG_INTR5, 0xff,
		REG_INTR5_MASK, 0x00,
		REG_INTR2, 0xff,
		REG_INTR2_MASK, 0x00,
	);
	memset(ctx->stat, 0, sizeof(ctx->stat));
	memset(ctx->xstat, 0, sizeof(ctx->xstat));
	memset(ctx->devcap, 0, sizeof(ctx->devcap));
	memset(ctx->xdevcap, 0, sizeof(ctx->xdevcap));
	ctx->devcap_read = false;
	ctx->cbus_status = 0;
	sii8620_mt_cleanup(ctx);
}

static void sii8620_mhl_disconnected(struct sii8620 *ctx)
{
	sii8620_write_seq_static(ctx,
		REG_DISC_CTRL4, VAL_DISC_CTRL4(VAL_PUP_OFF, VAL_PUP_20K),
		REG_CBUS_MSC_COMPAT_CTRL,
			BIT_CBUS_MSC_COMPAT_CTRL_XDEVCAP_EN
	);
	sii8620_disconnect(ctx);
}

static void sii8620_irq_disc(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_CBUS_DISC_INTR0);

	if (stat & VAL_CBUS_MHL_DISCON)
		sii8620_mhl_disconnected(ctx);

	if (stat & BIT_RGND_READY_INT) {
		u8 stat2 = sii8620_readb(ctx, REG_DISC_STAT2);

		if ((stat2 & MSK_DISC_STAT2_RGND) == VAL_RGND_1K) {
			sii8620_mhl_discover(ctx);
		} else {
			sii8620_write_seq_static(ctx,
				REG_DISC_CTRL9, BIT_DISC_CTRL9_WAKE_DRVFLT
					| BIT_DISC_CTRL9_NOMHL_EST
					| BIT_DISC_CTRL9_WAKE_PULSE_BYPASS,
				REG_CBUS_DISC_INTR0_MASK, BIT_RGND_READY_INT
					| BIT_CBUS_MHL3_DISCON_INT
					| BIT_CBUS_MHL12_DISCON_INT
					| BIT_NOT_MHL_EST_INT
			);
		}
	}
	if (stat & BIT_MHL_EST_INT)
		sii8620_mhl_init(ctx);

	sii8620_write(ctx, REG_CBUS_DISC_INTR0, stat);
}

static void sii8620_read_burst(struct sii8620 *ctx)
{
	u8 buf[17];

	sii8620_read_buf(ctx, REG_MDT_RCV_READ_PORT, buf, ARRAY_SIZE(buf));
	sii8620_write(ctx, REG_MDT_RCV_CTRL, BIT_MDT_RCV_CTRL_MDT_RCV_EN |
		      BIT_MDT_RCV_CTRL_MDT_DELAY_RCV_EN |
		      BIT_MDT_RCV_CTRL_MDT_RFIFO_CLR_CUR);
	sii8620_readb(ctx, REG_MDT_RFIFO_STAT);
}

static void sii8620_irq_g2wb(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_MDT_INT_0);

	if (stat & BIT_MDT_IDLE_AFTER_HAWB_DISABLE)
		if (sii8620_is_mhl3(ctx))
			sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE),
				MHL_INT_RC_FEAT_COMPLETE);

	if (stat & BIT_MDT_RFIFO_DATA_RDY)
		sii8620_read_burst(ctx);

	if (stat & BIT_MDT_XFIFO_EMPTY)
		sii8620_write(ctx, REG_MDT_XMIT_CTRL, 0);

	sii8620_write(ctx, REG_MDT_INT_0, stat);
}

static void sii8620_status_dcap_ready(struct sii8620 *ctx)
	enum sii8620_mode mode;

	mode = ctx->stat[MHL_DST_VERSION] >= 0x30 ? CM_MHL3 : CM_MHL1;
	if (mode > ctx->mode)
		sii8620_set_mode(ctx, mode);
	sii8620_peer_specific_init(ctx);
	sii8620_write(ctx, REG_INTR9_MASK, BIT_INTR9_DEVCAP_DONE
		      | BIT_INTR9_EDID_DONE | BIT_INTR9_EDID_ERROR);
}

static void sii8620_status_changed_path(struct sii8620 *ctx)
{
	u8 link_mode;

	if (ctx->use_packed_pixel)
		link_mode = MHL_DST_LM_CLK_MODE_PACKED_PIXEL;
	else
		link_mode = MHL_DST_LM_CLK_MODE_NORMAL;

	if (ctx->stat[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED)
		link_mode |= MHL_DST_LM_PATH_ENABLED;

	sii8620_mt_write_stat(ctx, MHL_DST_REG(LINK_MODE),
			      link_mode);
}

static void sii8620_msc_mr_write_stat(struct sii8620 *ctx)
{
	u8 st[MHL_DST_SIZE], xst[MHL_XDS_SIZE];

	sii8620_read_buf(ctx, REG_MHL_STAT_0, st, MHL_DST_SIZE);
	sii8620_read_buf(ctx, REG_MHL_EXTSTAT_0, xst, MHL_XDS_SIZE);

	sii8620_update_array(ctx->stat, st, MHL_DST_SIZE);
	sii8620_update_array(ctx->xstat, xst, MHL_XDS_SIZE);

	if (ctx->stat[MHL_DST_CONNECTED_RDY] & st[MHL_DST_CONNECTED_RDY] &
	    MHL_DST_CONN_DCAP_RDY) {
		sii8620_status_dcap_ready(ctx);
		if (!sii8620_is_mhl3(ctx))
			sii8620_mt_read_devcap(ctx, false);
	}

	if (st[MHL_DST_LINK_MODE] & MHL_DST_LM_PATH_ENABLED)
		sii8620_status_changed_path(ctx);
}

static void sii8620_ecbus_up(struct sii8620 *ctx, int ret)
{
	if (ret < 0)
		return;

	sii8620_set_mode(ctx, CM_ECBUS_S);
}

static void sii8620_got_ecbus_speed(struct sii8620 *ctx, int ret)
{
	if (ret < 0)
		return;

	sii8620_mt_write_stat(ctx, MHL_XDS_REG(CURR_ECBUS_MODE),
			      MHL_XDS_ECBUS_S | MHL_XDS_SLOT_MODE_8BIT);
	sii8620_mt_rap(ctx, MHL_RAP_CBUS_MODE_UP);
	sii8620_mt_set_cont(ctx, sii8620_ecbus_up);
}

static void sii8620_mhl_burst_emsc_support_set(struct mhl_burst_emsc_support *d,
	enum mhl_burst_id id)
{
	sii8620_mhl_burst_hdr_set(&d->hdr, MHL_BURST_ID_EMSC_SUPPORT);
	d->num_entries = 1;
	d->burst_id[0] = cpu_to_be16(id);
}

static void sii8620_send_features(struct sii8620 *ctx)
{
	u8 buf[16];

	sii8620_write(ctx, REG_MDT_XMIT_CTRL, BIT_MDT_XMIT_CTRL_EN
		| BIT_MDT_XMIT_CTRL_FIXED_BURST_LEN);
	sii8620_mhl_burst_emsc_support_set((void *)buf,
		MHL_BURST_ID_HID_PAYLOAD);
	sii8620_write_buf(ctx, REG_MDT_XMIT_WRITE_PORT, buf, ARRAY_SIZE(buf));
}

static bool sii8620_rcp_consume(struct sii8620 *ctx, u8 scancode)
{
	bool pressed = !(scancode & MHL_RCP_KEY_RELEASED_MASK);

	scancode &= MHL_RCP_KEY_ID_MASK;

	if (!IS_ENABLED(CONFIG_RC_CORE) || !ctx->rc_dev)
		return false;

	if (pressed)
		rc_keydown(ctx->rc_dev, RC_PROTO_CEC, scancode, 0);
	else
		rc_keyup(ctx->rc_dev);

	return true;
}

static void sii8620_msc_mr_set_int(struct sii8620 *ctx)
{
	u8 ints[MHL_INT_SIZE];

	sii8620_read_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE);
	sii8620_write_buf(ctx, REG_MHL_INT_0, ints, MHL_INT_SIZE);

	if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_DCAP_CHG) {
		switch (ctx->mode) {
		case CM_MHL3:
			sii8620_mt_read_xdevcap_reg(ctx, MHL_XDC_ECBUS_SPEEDS);
			sii8620_mt_set_cont(ctx, sii8620_got_ecbus_speed);
			break;
		case CM_ECBUS_S:
			sii8620_mt_read_devcap(ctx, true);
			break;
		default:
			break;
		}
	}
	if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_FEAT_REQ)
		sii8620_send_features(ctx);
	if (ints[MHL_INT_RCHANGE] & MHL_INT_RC_FEAT_COMPLETE) {
		ctx->feature_complete = true;
		if (ctx->edid)
			sii8620_enable_hpd(ctx);
	}
}

static struct sii8620_mt_msg *sii8620_msc_msg_first(struct sii8620 *ctx)
{
	struct device *dev = ctx->dev;

	if (list_empty(&ctx->mt_queue)) {
		dev_err(dev, "unexpected MSC MT response\n");
		return NULL;
	}

	return list_first_entry(&ctx->mt_queue, struct sii8620_mt_msg, node);
}

static void sii8620_msc_mt_done(struct sii8620 *ctx)
{
	struct sii8620_mt_msg *msg = sii8620_msc_msg_first(ctx);

	if (!msg)
		return;

	msg->ret = sii8620_readb(ctx, REG_MSC_MT_RCVD_DATA0);
	ctx->mt_state = MT_STATE_DONE;
}

static void sii8620_msc_mr_msc_msg(struct sii8620 *ctx)
{
	struct sii8620_mt_msg *msg;
	u8 buf[2];

	sii8620_read_buf(ctx, REG_MSC_MR_MSC_MSG_RCVD_1ST_DATA, buf, 2);

	switch (buf[0]) {
	case MHL_MSC_MSG_RAPK:
		msg = sii8620_msc_msg_first(ctx);
		if (!msg)
			return;
		msg->ret = buf[1];
		ctx->mt_state = MT_STATE_DONE;
		break;
	case MHL_MSC_MSG_RCP:
		if (!sii8620_rcp_consume(ctx, buf[1]))
			sii8620_mt_rcpe(ctx,
					MHL_RCPE_STATUS_INEFFECTIVE_KEY_CODE);
		sii8620_mt_rcpk(ctx, buf[1]);
		break;
	default:
		dev_err(ctx->dev, "%s message type %d,%d not supported",
			__func__, buf[0], buf[1]);
	}
}

static void sii8620_irq_msc(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_CBUS_INT_0);

	if (stat & ~BIT_CBUS_HPD_CHG)
		sii8620_write(ctx, REG_CBUS_INT_0, stat & ~BIT_CBUS_HPD_CHG);

	if (stat & BIT_CBUS_HPD_CHG) {
		u8 cbus_stat = sii8620_readb(ctx, REG_CBUS_STATUS);

		if ((cbus_stat ^ ctx->cbus_status) & BIT_CBUS_STATUS_CBUS_HPD) {
			sii8620_write(ctx, REG_CBUS_INT_0, BIT_CBUS_HPD_CHG);
		} else {
			stat ^= BIT_CBUS_STATUS_CBUS_HPD;
			cbus_stat ^= BIT_CBUS_STATUS_CBUS_HPD;
		}
		ctx->cbus_status = cbus_stat;
	}

	if (stat & BIT_CBUS_MSC_MR_WRITE_STAT)
		sii8620_msc_mr_write_stat(ctx);

	if (stat & BIT_CBUS_HPD_CHG) {
		if (ctx->cbus_status & BIT_CBUS_STATUS_CBUS_HPD) {
			ctx->sink_detected = true;
			sii8620_identify_sink(ctx);
		} else {
			sii8620_hpd_unplugged(ctx);
		}
	}

	if (stat & BIT_CBUS_MSC_MR_SET_INT)
		sii8620_msc_mr_set_int(ctx);

	if (stat & BIT_CBUS_MSC_MT_DONE)
		sii8620_msc_mt_done(ctx);

	if (stat & BIT_CBUS_MSC_MR_MSC_MSG)
		sii8620_msc_mr_msc_msg(ctx);
}

static void sii8620_irq_coc(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_COC_INTR);

	if (stat & BIT_COC_CALIBRATION_DONE) {
		u8 cstat = sii8620_readb(ctx, REG_COC_STAT_0);

		cstat &= BIT_COC_STAT_0_PLL_LOCKED | MSK_COC_STAT_0_FSM_STATE;
		if (cstat == (BIT_COC_STAT_0_PLL_LOCKED | 0x02)) {
			sii8620_write_seq_static(ctx,
				REG_COC_CTLB, 0,
				REG_TRXINTMH, BIT_TDM_INTR_SYNC_DATA
					      | BIT_TDM_INTR_SYNC_WAIT
			);
		}
	}

	sii8620_write(ctx, REG_COC_INTR, stat);
}

static void sii8620_irq_merr(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_CBUS_INT_1);

	sii8620_write(ctx, REG_CBUS_INT_1, stat);
}

static void sii8620_irq_edid(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_INTR9);

	sii8620_write(ctx, REG_INTR9, stat);

	if (stat & BIT_INTR9_DEVCAP_DONE)
		ctx->mt_state = MT_STATE_DONE;
}

static void sii8620_irq_scdt(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_INTR5);

	if (stat & BIT_INTR_SCDT_CHANGE) {
		u8 cstat = sii8620_readb(ctx, REG_TMDS_CSTAT_P3);

		if (cstat & BIT_TMDS_CSTAT_P3_SCDT)
			sii8620_start_video(ctx);
	}

	sii8620_write(ctx, REG_INTR5, stat);
}

static void sii8620_got_xdevcap(struct sii8620 *ctx, int ret)
{
	if (ret < 0)
		return;

	sii8620_mt_read_devcap(ctx, false);
}

static void sii8620_irq_tdm(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_TRXINTH);
	u8 tdm = sii8620_readb(ctx, REG_TRXSTA2);

	if ((tdm & MSK_TDM_SYNCHRONIZED) == VAL_TDM_SYNCHRONIZED) {
		ctx->mode = CM_ECBUS_S;
		ctx->burst.rx_ack = 0;
		ctx->burst.r_size = SII8620_BURST_BUF_LEN;
		sii8620_burst_tx_rbuf_info(ctx, SII8620_BURST_BUF_LEN);
		sii8620_mt_read_devcap(ctx, true);
		sii8620_mt_set_cont(ctx, sii8620_got_xdevcap);
	} else {
		sii8620_write_seq_static(ctx,
			REG_MHL_PLL_CTL2, 0,
			REG_MHL_PLL_CTL2, BIT_MHL_PLL_CTL2_CLKDETECT_EN
		);
	}

	sii8620_write(ctx, REG_TRXINTH, stat);
}

static void sii8620_irq_block(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_EMSCINTR);

	if (stat & BIT_EMSCINTR_SPI_DVLD) {
		u8 bstat = sii8620_readb(ctx, REG_SPIBURSTSTAT);

		if (bstat & BIT_SPIBURSTSTAT_EMSC_NORMAL_MODE)
			sii8620_burst_receive(ctx);
	}

	sii8620_write(ctx, REG_EMSCINTR, stat);
}

static void sii8620_irq_ddc(struct sii8620 *ctx)
{
	u8 stat = sii8620_readb(ctx, REG_INTR3);

	if (stat & BIT_DDC_CMD_DONE) {
		sii8620_write(ctx, REG_INTR3_MASK, 0);
		if (sii8620_is_mhl3(ctx) && !ctx->feature_complete)
			sii8620_mt_set_int(ctx, MHL_INT_REG(RCHANGE),
					   MHL_INT_RC_FEAT_REQ);
		else