// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
 *
 */

#define pr_fmt(fmt) "%s: " fmt, __func__

#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/firmware.h>
#include <linux/crc8.h>
#include <sound/soc.h>
#include <linux/of_gpio.h>
#include <linux/extcon-provider.h>
#include <linux/gpio/consumer.h>
#include <linux/usb/usbpd.h>
#include <linux/bitfield.h>

#define WRITE_BUF_MAX_SIZE	128
#define READ_BUF_MAX_SIZE	128
#define BLOCK_ERASE_DELAY_TIME	500
#define WRITE_DATA_DELAY_TIME	1

#define LT8711UXE2_IRQ_USB_HPD			BIT(0)
#define LT8711UXE2_IRQ_DP_ALT_MODE_CHANGE	BIT(1)
#define LT8711UXE2_IRQ_HDMI_OUTPUT_CHANGE	BIT(2)

#define LT8711UXE2_GPIO_HIGH		0
#define LT8711UXE2_GPIO_LOW		1
#define LT8711UXE2_DP_2LANE		LT8711UXE2_GPIO_HIGH
#define LT8711UXE2_DP_4LANE		LT8711UXE2_GPIO_LOW
#define LT8711UXE2_DP_ALT_ENABLE	LT8711UXE2_GPIO_HIGH
#define LT8711UXE2_DP_ALT_DISABLE	LT8711UXE2_GPIO_LOW

#define IRQ_TYPE_EQUALS(irq_val, mask) ((irq_val) & (mask))

DECLARE_CRC8_TABLE(lt8711uxe2_crc_table);

enum LT8711UXE2_DATA_ROLE {
	LT8711UXE2_DISCONNECTED = 0,
	LT8711UXE2_DFP_ATTACHED,
	LT8711UXE2_UFP_ATTACHED
};

/* List of detectable cables */
static const unsigned int lt8711uxe2_extcon_cable_with_redriver[] = {
	EXTCON_USB,
	EXTCON_USB_HOST,
	EXTCON_DISP_DP,
	EXTCON_MECHANICAL,
	EXTCON_NONE,
};

static const unsigned int lt8711uxe2_extcon_cable[] = {
	EXTCON_USB,
	EXTCON_USB_HOST,
	EXTCON_MECHANICAL,
	EXTCON_NONE,
};

struct lt8711uxe2_reg_cfg {
	u8 reg;
	u8 val;
};

enum lt8711uxe2_fw_upgrade_status {
	UPDATE_SUCCESS = 0,
	UPDATE_RUNNING = 1,
	UPDATE_FAILED = 2,
};

struct lt8711uxe2 {
	struct extcon_dev *edev;
	struct device *dev;

	struct i2c_client *i2c_client;

	u8 i2c_wbuf[WRITE_BUF_MAX_SIZE];
	u8 i2c_rbuf[READ_BUF_MAX_SIZE];

	enum lt8711uxe2_fw_upgrade_status fw_status;
	struct work_struct irq_work;
	struct mutex mutex;
	bool usb_ss_support;
	bool with_redriver;
	bool with_hdmi_switch;
	struct extcon_dev *hdmi_sw_edev;

	unsigned int chip_fw_version;
	unsigned int image_fw_version;
	u32 reset_gpio;
	u32 irq_gpio;
	u32 dp_lane_sel_gpio;
	u32 dp_alt_en_gpio;
	int irq;
	u8 alt_mode;

	u32 cc_finished_gpio;
	int cc_irq;
	bool is_cc_finished;
};

static void lt8711uxe2_check_state(struct lt8711uxe2 *pdata);
static void lt8711uxe2_set_hdmi_switch_state(struct lt8711uxe2 *pdata, bool enabled);

/*
 * Write one reg with more values;
 * Reg -> value0, value1, value2.
 */
static int lt8711uxe2_write(struct lt8711uxe2 *pdata, u8 reg,
		const u8 *buf, int size)
{
	struct i2c_client *client = pdata->i2c_client;
	struct i2c_msg msg = {
		.addr = client->addr,
		.flags = 0,
		.len = size + 1,
		.buf = pdata->i2c_wbuf,
	};

	pdata->i2c_wbuf[0] = reg;
	if (size > (WRITE_BUF_MAX_SIZE - 1)) {
		dev_err(pdata->dev, "invalid write buffer size %d\n", size);
		return -EINVAL;
	}

	memcpy(pdata->i2c_wbuf + 1, buf, size);

	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
		dev_err(pdata->dev, "i2c write failed\n");
		return -EIO;
	}

	return 0;
}

/*
 * Write one reg with one value;
 * Reg -> value
 */
static int lt8711uxe2_write_byte(struct lt8711uxe2 *pdata, const u8 reg,
			u8 value)
{
	struct i2c_client *client = pdata->i2c_client;
	struct i2c_msg msg = {
		.addr = client->addr,
		.flags = 0,
		.len = 2,
		.buf = pdata->i2c_wbuf,
	};

	memset(pdata->i2c_wbuf, 0, WRITE_BUF_MAX_SIZE);
	pdata->i2c_wbuf[0] = reg;
	pdata->i2c_wbuf[1] = value;

	if (i2c_transfer(client->adapter, &msg, 1) < 1) {
		dev_err(pdata->dev, "i2c write failed\n");
		return -EIO;
	}

	return 0;
}

/*
 * Write more regs with more values;
 * Reg1 -> value1
 * Reg2 -> value2
 */
static void lt8711uxe2_write_array(struct lt8711uxe2 *pdata,
			struct lt8711uxe2_reg_cfg *reg_arry, int size)
{
	int i = 0;

	for (i = 0; i < size; i++)
		lt8711uxe2_write_byte(pdata, reg_arry[i].reg, reg_arry[i].val);
}

static int lt8711uxe2_read(struct lt8711uxe2 *pdata, u8 reg, char *buf,
			u32 size)
{
	struct i2c_client *client = pdata->i2c_client;
	struct i2c_msg msg[2] = {{
		  .addr = client->addr,
		  .flags = 0,
		  .len = 1,
		  .buf = pdata->i2c_wbuf,
	  },
	{
		  .addr = client->addr,
		  .flags = I2C_M_RD,
		  .len = size,
		  .buf = pdata->i2c_rbuf,
	}};

	if (size > READ_BUF_MAX_SIZE) {
		dev_err(pdata->dev, "invalid read buff size %d\n", size);
		return -EINVAL;
	}

	memset(pdata->i2c_wbuf, 0x0, WRITE_BUF_MAX_SIZE);
	memset(pdata->i2c_rbuf, 0x0, READ_BUF_MAX_SIZE);
	pdata->i2c_wbuf[0] = reg;

	if (i2c_transfer(client->adapter, msg, 2) != 2) {
		dev_err(pdata->dev, "i2c read failed\n");
		return -EIO;
	}

	memcpy(buf, pdata->i2c_rbuf, size);

	return 0;
}

static void lt8711uxe2_ctl_en(struct lt8711uxe2 *pdata)
{
	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
	lt8711uxe2_write_byte(pdata, 0xEE, 0x01);
}

static void lt8711uxe2_ctl_disable(struct lt8711uxe2 *pdata)
{
	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
	lt8711uxe2_write_byte(pdata, 0xEE, 0x00);
}

static int lt8711uxe2_parse_dt(struct lt8711uxe2 *pdata)
{
	int ret = 0;

	if (of_property_read_u32(pdata->dev->of_node, "img-fw-rev",
			&pdata->image_fw_version) < 0) {
		dev_err(pdata->dev, "failed reading image firmware version\n");
		pdata->image_fw_version = 0;
	}
	pdata->irq_gpio = of_get_named_gpio(pdata->dev->of_node,
				"lt,irq-gpio", 0);
	if (!gpio_is_valid(pdata->irq_gpio)) {
		dev_err(pdata->dev, "irq gpio not specified\n");
		ret = -EINVAL;
	} else
		pr_debug("irq_gpio=%d\n", pdata->irq_gpio);

	pdata->reset_gpio = of_get_named_gpio(pdata->dev->of_node,
				"lt,reset-gpio", 0);
	if (!gpio_is_valid(pdata->reset_gpio)) {
		dev_err(pdata->dev, "reset gpio not specified\n");
		ret = -EINVAL;
	} else
		pr_debug("reset_gpio=%d\n", pdata->reset_gpio);

	pdata->dp_lane_sel_gpio = of_get_named_gpio(pdata->dev->of_node,
					"lt,dp-lane-sel", 0);
	if (!gpio_is_valid(pdata->dp_lane_sel_gpio))
		pr_debug("dp_lane_sel gpio not specified\n");
	else
		pr_debug("dp_lane_sel_gpio=%d\n", pdata->dp_lane_sel_gpio);

	pdata->dp_alt_en_gpio = of_get_named_gpio(pdata->dev->of_node,
					"lt,dp-alt-en", 0);
	if (!gpio_is_valid(pdata->dp_alt_en_gpio))
		pr_debug("dp_alt_en gpio not specified\n");
	else
		pr_debug("dp_alt_en_gpio=%d\n", pdata->dp_alt_en_gpio);

	pdata->cc_finished_gpio = of_get_named_gpio(pdata->dev->of_node,
					"lt,cc-gpio", 0);
	if (!gpio_is_valid(pdata->cc_finished_gpio))
		pr_err("cc_finished_gpio not specified\n");
	else
		pr_debug("cc_finished_gpio=%d\n", pdata->cc_finished_gpio);

	return ret;
}

static int lt8711uxe2_gpio_configure(struct lt8711uxe2 *pdata, bool on)
{
	int ret = 0;

	if (on) {
		ret = gpio_request(pdata->irq_gpio, "lt8711uxe2-irq-gpio");
		if (ret) {
			dev_err(pdata->dev, "lt8711uxe2 irq gpio request failed\n");
			goto error;
		}
		ret = gpio_direction_input(pdata->irq_gpio);
		if (ret) {
			dev_err(pdata->dev, "lt8711uxe2 irq gpio direction failed\n");
			goto irq_err;
		}

		ret = gpio_request(pdata->reset_gpio, "lt8711uxe2-reset-gpio");
		if (ret) {
			dev_err(pdata->dev, "lt8711uxe2 reset gpio request failed\n");
			goto irq_err;
		}

		ret = gpio_direction_output(pdata->reset_gpio,
				LT8711UXE2_GPIO_LOW);
		if (ret) {
			dev_err(pdata->dev, "lt8711uxe2 reset gpio direction failed\n");
			goto reset_err;
		}

		if (gpio_is_valid(pdata->dp_lane_sel_gpio)) {
			ret = gpio_request(pdata->dp_lane_sel_gpio,
					"lt8711uxe2-dp_lane_sel-gpio");
			if (ret) {
				dev_err(pdata->dev, "dp_lane_sel gpio request failed\n");
				goto reset_err;
			}
			/* Default select 2 lane DP + 2 lane USB. */
			ret = gpio_direction_output(pdata->dp_lane_sel_gpio,
					LT8711UXE2_DP_2LANE);
			if (ret) {
				dev_err(pdata->dev, "dp_lane_sel gpio direction failed\n");
				goto dp_lane_sel_err;
			}
		}

		if (gpio_is_valid(pdata->dp_alt_en_gpio)) {
			ret = gpio_request(pdata->dp_alt_en_gpio,
					"lt8711uxe2-dp_alt_en-gpio");
			if (ret) {
				dev_err(pdata->dev, "dp_alt_en gpio request failed\n");
				goto dp_lane_sel_err;
			}
			/* Default enable DP alternate mode. */
			ret = gpio_direction_output(pdata->dp_alt_en_gpio,
						    LT8711UXE2_DP_ALT_ENABLE);
			if (ret) {
				dev_err(pdata->dev, "dp_alt_en gpio direction failed\n");
				goto dp_alt_en_err;
			}
		}

		if (gpio_is_valid(pdata->cc_finished_gpio)) {
			ret = gpio_request(pdata->cc_finished_gpio, "lt8711uxe2-cc-gpio");
			if (ret) {
				dev_err(pdata->dev, "lt8711uxe2 cc gpio request failed\n");
				goto dp_alt_en_err;
			}

			ret = gpio_direction_input(pdata->cc_finished_gpio);
			if (ret) {
				dev_err(pdata->dev, "lt8711uxe2 cc gpio direction failed\n");
				goto cc_finished_err;
			}
		}

	} else {
		gpio_free(pdata->cc_finished_gpio);
		if (gpio_is_valid(pdata->dp_lane_sel_gpio))
			gpio_free(pdata->dp_alt_en_gpio);
		if (gpio_is_valid(pdata->dp_alt_en_gpio))
			gpio_free(pdata->dp_lane_sel_gpio);
		if (gpio_is_valid(pdata->reset_gpio))
			gpio_free(pdata->reset_gpio);
		gpio_free(pdata->irq_gpio);
	}
	return ret;

cc_finished_err:
	if (gpio_is_valid(pdata->cc_finished_gpio))
		gpio_free(pdata->cc_finished_gpio);
dp_alt_en_err:
	if (gpio_is_valid(pdata->dp_lane_sel_gpio))
		gpio_free(pdata->dp_alt_en_gpio);
dp_lane_sel_err:
	if (gpio_is_valid(pdata->dp_alt_en_gpio))
		gpio_free(pdata->dp_lane_sel_gpio);
reset_err:
	gpio_free(pdata->reset_gpio);
irq_err:
	gpio_free(pdata->irq_gpio);
error:
	return ret;
}

static void lt8711uxe2_reset(struct lt8711uxe2 *pdata, bool on_off)
{
	pr_debug("reset: %d\n", on_off);
	mutex_lock(&pdata->mutex);
	if (on_off) {
		gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_HIGH);
		pr_debug("reset GPIO_HIGH\n");
		msleep(20);
		gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_LOW);
		pr_debug("reset GPIO_LOW\n");
		msleep(100);
		gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_HIGH);
		pr_debug("reset GPIO_HIGH\n");
		/* Need longer time to make sure LT8711UXE2 initialized. */
		msleep(500);
	} else
		gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_HIGH);

	mutex_unlock(&pdata->mutex);
}

static int lt8711uxe2_read_firmware_version(struct lt8711uxe2 *pdata)
{
	u8 rev[3] = {};
	int ret = 0;

	memset(rev, 0x0, 3);

	lt8711uxe2_ctl_en(pdata);
	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);

	ret = lt8711uxe2_read(pdata, 0x81, rev, 3);

	if (ret == 0) {
		pdata->chip_fw_version = ((rev[0] << 16) |
					(rev[1] << 8) | rev[2]);
		dev_info(pdata->dev, "Firmware version: 0x%x\n", pdata->chip_fw_version);
	} else
		dev_err(pdata->dev, "read Firmware version fail\n");

	lt8711uxe2_ctl_disable(pdata);

	return ret;
}

static int lt8711uxe2_read_alt_mode(struct lt8711uxe2 *pdata)
{
	u8 alt_mode_stat = 0;
	int ret = 0;

	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
	ret = lt8711uxe2_read(pdata, 0x85, &alt_mode_stat, 1);
	if (ret == 0) {
		pdata->alt_mode = alt_mode_stat;
		pr_debug("alt mode:%d\n", pdata->alt_mode);
	} else
		dev_err(pdata->dev, "read alt mode fail\n");

	return ret;
}

static void lt8711uxe2_config(struct lt8711uxe2 *pdata)
{
	struct lt8711uxe2_reg_cfg reg_cfg[] = {
		{ 0xFF, 0xE0 }, { 0xEE, 0x01 }, { 0x5E, 0xC1 }, { 0x58, 0x00 },
		{ 0x59, 0x50 }, { 0x5A, 0x10 }, { 0x5A, 0x00 }, { 0x58, 0x21 },
	};

	lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}

static void lt8711uxe2_flash_write_en(struct lt8711uxe2 *pdata)
{
	struct lt8711uxe2_reg_cfg reg_cfg0[] = {
		{ 0xFF, 0xE1 },
		{ 0x03, 0x3F },
	};

	struct lt8711uxe2_reg_cfg reg_cfg1[] = {
		{ 0xFF, 0xE0 },
		{ 0x5A, 0x04 },
		{ 0x5A, 0x00 },
	};

	lt8711uxe2_write_array(pdata, reg_cfg0, ARRAY_SIZE(reg_cfg0));
	msleep(WRITE_DATA_DELAY_TIME);
	lt8711uxe2_write_byte(pdata, 0x03, 0xFF);
	msleep(WRITE_DATA_DELAY_TIME);
	lt8711uxe2_write_array(pdata, reg_cfg1, ARRAY_SIZE(reg_cfg1));
}

static void lt8711uxe2_flash_write_di(struct lt8711uxe2 *pdata)
{
	struct lt8711uxe2_reg_cfg reg_cfg0[] = {
		{ 0x5A, 0x08 },
		{ 0x5A, 0x00 },
	};

	lt8711uxe2_write_array(pdata, reg_cfg0, ARRAY_SIZE(reg_cfg0));
}

static void lt8711uxe2_block_erase(struct lt8711uxe2 *pdata, u32 addr)
{
	struct lt8711uxe2_reg_cfg reg_cfg[] = {
		{ 0xFF, 0xE0 },
		{ 0xEE, 0x01 },
		{ 0x5A, 0x04 },
		{ 0x5A, 0x00 },
		{ 0x5B, (addr & 0xFF0000) >> 16 },
		{ 0x5C, (addr & 0xFF00) >> 8 },
		{ 0x5D, addr & 0xFF },
		{ 0x5A, 0x01 },
		{ 0x5A, 0x00 },
	};

	pr_debug("block erase\n");
	lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
	msleep(BLOCK_ERASE_DELAY_TIME);
}

static void lt8711uxe2_flash_read_addr_set(struct lt8711uxe2 *pdata, u32 addr)
{
	struct lt8711uxe2_reg_cfg reg_cfg[] = {
		{ 0x5E, 0x5F },
		{ 0x5A, 0x20 },
		{ 0x5A, 0x00 },
		{ 0x5B, (addr & 0xFF0000) >> 16 },
		{ 0x5C, (addr & 0xFF00) >> 8 },
		{ 0x5D, addr & 0xFF },
		{ 0x5A, 0x10 },
		{ 0x5A, 0x00 },
		{ 0x58, 0x21 },
	};

	lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}

static void lt8711uxe2_fw_read_back(struct lt8711uxe2 *pdata, u8 *buff,
				int size, u32 addr)
{
	u8 page_data[32];
	int page_number = 0, i = 0;

	/*
	 * Read 32 bytes once.
	 */
	page_number = size / 32;
	if (size % 32)
		page_number++;

	for (i = 0; i < page_number; i++) {
		memset(page_data, 0x0, 32);
		lt8711uxe2_flash_read_addr_set(pdata, addr);
		lt8711uxe2_read(pdata, 0x5F, page_data, 32);
		memcpy(buff, page_data, 32);
		buff += 32;
		addr += 32;
		pr_debug("fw read page: %d\n", i);
	}
}

static void lt8711uxe2_flash_write_config(struct lt8711uxe2 *pdata)
{
	struct lt8711uxe2_reg_cfg reg_cfg[] = {
		{ 0x5E, 0xDF },
		{ 0x5A, 0x20 },
		{ 0x5A, 0x00 },
		{ 0x58, 0x21 },
	};

	lt8711uxe2_flash_write_en(pdata);
	lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}

static void lt8711uxe2_flash_write_addr_set(struct lt8711uxe2 *pdata, u32 addr)
{
	struct lt8711uxe2_reg_cfg reg_cfg[] = {
		{ 0x5B, (u8)((addr & 0xFF0000) >> 16) },
		{ 0x5C, (u8)((addr & 0xFF00) >> 8) },
		{ 0x5D, addr & 0xFF },
		{ 0x5A, 0x10 },
		{ 0x5A, 0x00 },
	};

	lt8711uxe2_write_array(pdata, reg_cfg, ARRAY_SIZE(reg_cfg));
}

static int lt8711uxe2_calculate_crc8(const u8 *fdata, int size)
{
	u8 crc = 0;
	u16 block_size = 0x8000;
	u8 *crc_data = NULL;

	crc_data = kzalloc(block_size, GFP_KERNEL);
	if (!crc_data)
		return -ENOMEM;

	memcpy(crc_data, fdata, size);
	memset(crc_data + size, 0xff, block_size - size);

	crc = crc8(lt8711uxe2_crc_table, crc_data, block_size - 1, crc);

	kfree(crc_data);
	return crc;
}

static void lt8711uxe2_fw_write(struct lt8711uxe2 *pdata,
			const u8 *fdata, int size, u32 addr)
{
	u8 last_buf[32];
	int i = 0, page_size = 32;
	int total_page = 0, rest_data = 0;
	int start_addr = addr;
	u32 crc_addr = ((addr & 0xFF0000) | 0x007FFF);
	const u8 *fdata_start_addr = fdata;
	int crc8;
	u8 crc_buf[2];

	total_page = size / page_size;
	rest_data = size % page_size;

	for (i = 0; i < total_page; i++) {
		lt8711uxe2_flash_write_config(pdata);
		lt8711uxe2_write(pdata, 0x59, fdata, page_size);
		pr_debug("firmware write page: %d\n", i);
		lt8711uxe2_flash_write_addr_set(pdata, start_addr);
		start_addr += page_size;
		fdata += page_size;
		msleep(WRITE_DATA_DELAY_TIME);
	}

	if (rest_data > 0) {
		memset(last_buf, 0xFF, 32);
		memcpy(last_buf, fdata, rest_data);
		lt8711uxe2_flash_write_config(pdata);
		lt8711uxe2_write(pdata, 0x59, last_buf, page_size);
		pr_debug("firmware write page: %d\n", i);
		lt8711uxe2_flash_write_addr_set(pdata, start_addr);
		msleep(WRITE_DATA_DELAY_TIME);
	}

	/* write crc8 */
	memset(crc_buf, 0xFF, 2);
	crc8 = lt8711uxe2_calculate_crc8(fdata_start_addr, size);
	if (crc8 == -ENOMEM)
		return;
	crc_buf[0] = crc8;
	lt8711uxe2_flash_write_config(pdata);
	lt8711uxe2_write(pdata, 0x59, crc_buf, 1);

	lt8711uxe2_flash_write_addr_set(pdata, crc_addr);
	pr_debug("crc8 addr: 0x%06x, crc8 data: 0x%02x\n",
		crc_addr, crc_buf[0]);
	msleep(WRITE_DATA_DELAY_TIME);

	pr_debug("FW write over, total size: %d, page: %d, reset: %d\n", size,
			total_page, rest_data);
}

static void lt8711uxe2_fw_upgrade(struct lt8711uxe2 *pdata,
				 const struct firmware *cfg, u32 addr)
{
	int i = 0;
	int cfg_crc8, fw_crc8;
	u8 *fw_read_data = NULL;
	int data_len = (int)cfg->size;

	pr_debug("FW total size %d\n", data_len);

	fw_read_data = kzalloc(ALIGN(data_len, 32), GFP_KERNEL);
	if (!fw_read_data)
		return;

	pdata->fw_status = UPDATE_RUNNING;
	lt8711uxe2_config(pdata);

	/*
	 * Need erase block 2 timess here.
	 * Sometimes, erase can fail.
	 * This is a workaroud.
	 */

	for (i = 0; i < 2; i++)
		lt8711uxe2_block_erase(pdata, addr);

	lt8711uxe2_fw_write(pdata, cfg->data, data_len, addr);
	msleep(WRITE_DATA_DELAY_TIME);

	lt8711uxe2_fw_read_back(pdata, fw_read_data, data_len, addr);

	cfg_crc8 = lt8711uxe2_calculate_crc8(cfg->data, data_len);
	fw_crc8 = lt8711uxe2_calculate_crc8(fw_read_data, data_len);
	pr_debug("crc8 calculated using F/W bin file: 0x%02x\n",
		cfg_crc8);
	pr_debug("crc8 calculated by reading from LT8711uxe: 0x%02x\n",
		fw_crc8);

	if (cfg_crc8 == -ENOMEM || fw_crc8 == -ENOMEM) {
		kfree(fw_read_data);
		return;
	}
	if (cfg_crc8 == fw_crc8)
		pr_debug("check crc8 pass\n");
	else
		dev_err(pdata->dev, "check crc8 error\n");

	if (!memcmp(cfg->data, fw_read_data, data_len)) {
		pdata->fw_status = UPDATE_SUCCESS;
		pr_debug("firmware upgrade success.\n");
		lt8711uxe2_reset(pdata, true);
	} else {
		pdata->fw_status = UPDATE_FAILED;
		dev_err(pdata->dev, "firmware upgrade failed\n");
	}

	kfree(fw_read_data);
}

static void lt8711uxe2_fw_cb_main(const struct firmware *cfg, void *data)
{
	struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
	u32 addr = 0x000000;

	if (!cfg) {
		dev_err(pdata->dev, "get firmware failed\n");
		return;
	}

	lt8711uxe2_fw_upgrade(pdata, cfg, addr);
	release_firmware(cfg);
}

static void lt8711uxe2_fw_cb_backup(const struct firmware *cfg,
				void *data)
{
	struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
	u32 addr = 0x040000;

	if (!cfg) {
		dev_err(pdata->dev, "get firmware failed\n");
		return;
	}

	lt8711uxe2_fw_upgrade(pdata, cfg, addr);
	release_firmware(cfg);
}

static void lt8711uxe2_fw_debug_write_main_fw(const struct firmware *cfg,
						void *data)
{
	struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
	int data_len = (int)cfg->size;
	u32 addr = 0x000000;

	if (!cfg) {
		dev_err(pdata->dev, "get firmware failed\n");
		return;
	}

	/* only write main fw */
	lt8711uxe2_config(pdata);
	lt8711uxe2_fw_write(pdata, cfg->data, data_len, addr);
	lt8711uxe2_flash_write_di(pdata);
	lt8711uxe2_ctl_disable(pdata);

	release_firmware(cfg);
}

static void lt8711uxe2_fw_debug_write_backup_fw(const struct firmware *cfg,
						void *data)
{
	struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)data;
	int data_len = (int)cfg->size;
	u32 addr = 0x040000;

	if (!cfg) {
		dev_err(pdata->dev, "get firmware failed\n");
		return;
	}

	/* only write backup fw */
	lt8711uxe2_config(pdata);
	lt8711uxe2_fw_write(pdata, cfg->data, data_len, addr);
	lt8711uxe2_flash_write_di(pdata);
	lt8711uxe2_ctl_disable(pdata);

	release_firmware(cfg);
}

static void lt8711uxe2_fw_debug_crc8_main_fw(struct lt8711uxe2 *pdata)
{
	int ret = 0;
	u8 *fw_read_data = NULL;
	u32 addr = 0x000000;
	int block_size = 0x8000;
	u8 crc8;

	pr_debug("get main F/W crc8...\n");

	fw_read_data = kzalloc(ALIGN(block_size, 32), GFP_KERNEL);
	if (!fw_read_data)
		return;

	/* read main fw */
	lt8711uxe2_config(pdata);
	lt8711uxe2_fw_read_back(pdata, fw_read_data, block_size, addr);
	lt8711uxe2_flash_write_di(pdata);
	lt8711uxe2_ctl_disable(pdata);

	/* read chip calculate crc8 */
	lt8711uxe2_ctl_en(pdata);
	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
	ret = lt8711uxe2_read(pdata, 0x21, &crc8, 1);

	if (ret == 0)
		pr_debug("read crc8 register data: 0x%02x\n",
			crc8);
	else
		dev_info(pdata->dev, "read crc8 register fail\n");

	lt8711uxe2_ctl_disable(pdata);

	/* get f/w block last byte (crc8) */
	pr_debug("read flash last byte (crc8 data): 0x%02x\n",
		fw_read_data[block_size - 1]);

	/* use f/w data to calculate crc8 */
	pr_debug("calculate crc8 data: 0x%02x\n",
		lt8711uxe2_calculate_crc8(fw_read_data, block_size - 1));

	kfree(fw_read_data);
}

static void lt8711uxe2_fw_debug_crc8_backup_fw(struct lt8711uxe2 *pdata)
{
	int ret = 0;
	u8 *fw_read_data = NULL;
	u32 addr = 0x040000;
	int block_size = 0x8000;
	u8 crc8;

	pr_debug("get backup F/W crc8...\n");

	fw_read_data = kzalloc(ALIGN(block_size, 32), GFP_KERNEL);
	if (!fw_read_data)
		return;

	/* read backup fw */
	lt8711uxe2_config(pdata);
	lt8711uxe2_fw_read_back(pdata, fw_read_data, block_size, addr);
	lt8711uxe2_flash_write_di(pdata);
	lt8711uxe2_ctl_disable(pdata);

	/* read chip calculate crc8 */
	lt8711uxe2_ctl_en(pdata);
	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
	ret = lt8711uxe2_read(pdata, 0x21, &crc8, 1);

	if (ret == 0)
		pr_debug("read crc8 register data: 0x%02x\n",
			crc8);
	else
		dev_info(pdata->dev, "read crc8 register fail\n");

	lt8711uxe2_ctl_disable(pdata);

	/* get f/w block last byte (crc8) */
	pr_debug("read flash last byte (crc8 data): 0x%02x\n",
			fw_read_data[block_size - 1]);

	/* use f/w data to calculate crc8 */
	pr_debug("calculate crc8 data: 0x%02x\n",
		lt8711uxe2_calculate_crc8(fw_read_data, block_size - 1));

	kfree(fw_read_data);
}

static void lt8711uxe2_fw_debug_crc8_bin(const struct firmware *cfg,
						void *data)
{
	int data_len = (int)cfg->size;

	if (!cfg) {
		pr_err("get firmware failed\n");
		return;
	}

	/* print F/W bin file crc8 */
	pr_debug("get firmware bin file crc8: 0x%02x\n",
		lt8711uxe2_calculate_crc8(cfg->data, data_len));

	release_firmware(cfg);
}

static ssize_t firmware_upgrade_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);
	int ret = 0;
	int get;
	u32 addr;
	int i = 0;

	if (!pdata)
		return -EINVAL;

	if (pdata->fw_status == UPDATE_RUNNING) {
		dev_err(dev, "lt8711uxe2 bridge upgrade is already in progress\n");
		return -EINVAL;
	}

	sscanf(buf, "%d", &get);

	switch (get) {
	case 0:
		/* Upgrade Main F/W SOP */
		ret = request_firmware_nowait(THIS_MODULE, true,
					      "lt8711uxe2_fw.bin",
					      &pdata->i2c_client->dev,
					      GFP_KERNEL, pdata,
					      lt8711uxe2_fw_cb_main);
		if (ret)
			dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
		else
			dev_info(dev, "main F/W starts upgrading, wait for 70s\n");
		break;
	case 1:
		/* Upgrade Backup F/W SOP */
		ret = request_firmware_nowait(THIS_MODULE, true,
					      "lt8711uxe2_fw.bin",
					      &pdata->i2c_client->dev,
					      GFP_KERNEL, pdata,
					      lt8711uxe2_fw_cb_backup);
		if (ret)
			dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
		else
			dev_info(dev, "backup F/W starts upgrading, wait for 70s\n");
		break;
	case 2:
		/*Erase Main F/W Block */
		addr = 0x000000;

		lt8711uxe2_config(pdata);

		for (i = 0; i < 2; i++)
			lt8711uxe2_block_erase(pdata, addr);

		lt8711uxe2_ctl_disable(pdata);
		break;
	case 3:
		/*Erase Backup F/W Block */
		addr = 0x040000;

		lt8711uxe2_config(pdata);

		for (i = 0; i < 2; i++)
			lt8711uxe2_block_erase(pdata, addr);

		lt8711uxe2_ctl_disable(pdata);
		break;
	case 4:
		/* Write Main F/W Block */
		ret = request_firmware_nowait(
			THIS_MODULE, true, "lt8711uxe2_fw.bin",
			&pdata->i2c_client->dev, GFP_KERNEL, pdata,
			lt8711uxe2_fw_debug_write_main_fw);
		if (ret)
			dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
		else
			dev_info(dev, "main F/W starts writing, wait for 50s...\n");
		break;
	case 5:
		/* Write Backup F/W Block */
		ret = request_firmware_nowait(
			THIS_MODULE, true, "lt8711uxe2_fw.bin",
			&pdata->i2c_client->dev, GFP_KERNEL, pdata,
			lt8711uxe2_fw_debug_write_backup_fw);
		if (ret)
			dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
		else
			dev_info(dev, "backup F/W starts writing, wait for 50s...\n");
		break;
	case 6:
		/* Get Main F/W  Block CRC8 */
		lt8711uxe2_fw_debug_crc8_main_fw(pdata);
		break;
	case 7:
		/* Get Backup F/W  Block CRC8 */
		lt8711uxe2_fw_debug_crc8_backup_fw(pdata);
		break;
	case 8:
		/* Calculate F/W Bin File CRC8 */
		ret = request_firmware_nowait(
			THIS_MODULE, true, "lt8711uxe2_fw.bin",
			&pdata->i2c_client->dev, GFP_KERNEL, pdata,
			lt8711uxe2_fw_debug_crc8_bin);
		if (ret)
			dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
		else
			dev_info(dev, "F/W bin file crc8 calculating...\n");
		break;
	default:
		break;
	}

	return ret ? : count;
}

static ssize_t firmware_upgrade_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);

	return scnprintf(buf, PAGE_SIZE, "%d\n", pdata->fw_status); //scnprintf
}

static ssize_t get_fw_version_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);

	if (pdata->fw_status == UPDATE_RUNNING) {
		dev_err(dev, "can't check firmware while upgrading bridge\n");
		return -EINVAL;
	}

	lt8711uxe2_read_firmware_version(pdata);
	return scnprintf(buf, PAGE_SIZE, "%#x\n", pdata->chip_fw_version);
}
static ssize_t dp_alt_en_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int get;
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);

	if (kstrtoint(buf, 0, &get))
		return -EINVAL;

	if (pdata->dp_alt_en_gpio) {
		if (get != 0) {
			gpio_set_value(pdata->dp_alt_en_gpio,
				       LT8711UXE2_DP_ALT_ENABLE);
		} else {
			gpio_set_value(pdata->dp_alt_en_gpio,
				       LT8711UXE2_DP_ALT_DISABLE);
		}
	} else
		dev_err(dev, "Invalid gpio %#x!\n", pdata->dp_alt_en_gpio);

	return count;
}
static ssize_t dp_alt_en_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);

	if (!pdata->dp_alt_en_gpio)
		return scnprintf(buf, PAGE_SIZE, "Invalid gpio\n");
	if (gpio_get_value(pdata->dp_alt_en_gpio) == LT8711UXE2_DP_ALT_ENABLE)
		return scnprintf(buf, PAGE_SIZE, "%s\n", "Enabled");
	else
		return scnprintf(buf, PAGE_SIZE, "%s\n", "Disabled");
}

static ssize_t dp_lane_sel_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int get;
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);

	if (kstrtoint(buf, 0, &get))
		return -EINVAL;

	if (pdata->dp_lane_sel_gpio) {
		switch (get) {
		case 4:
			dev_err(dev, "Select 4 lane DP with USB 2.0\n");
			gpio_set_value(pdata->dp_lane_sel_gpio,
				       LT8711UXE2_DP_4LANE);
			break;
		case 2:
			dev_err(dev, "Select 2 lane DP with USB 3.0\n");
			gpio_set_value(pdata->dp_lane_sel_gpio,
				       LT8711UXE2_DP_2LANE);
			break;
		default:
			dev_warn(dev, "invalid, default select 2 lane DP\n");
			gpio_set_value(pdata->dp_lane_sel_gpio,
				       LT8711UXE2_DP_2LANE);
			break;
		}
	} else
		dev_err(dev, "Invalid gpio %#x!\n", pdata->dp_lane_sel_gpio);

	return count;
}

static ssize_t dp_lane_sel_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);

	if (!pdata->dp_lane_sel_gpio)
		return scnprintf(buf, PAGE_SIZE, "Invalid gpio\n");

	if (gpio_get_value(pdata->dp_lane_sel_gpio) == LT8711UXE2_DP_2LANE)
		return scnprintf(buf, PAGE_SIZE, "%s\n",
				"2 Lane DP with USB 3.0");
	else
		return scnprintf(buf, PAGE_SIZE, "%s\n",
				"4 Lane DP with USB 2.0");
}

static ssize_t get_alt_mode_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct lt8711uxe2 *pdata = dev_get_drvdata(dev);

	return scnprintf(buf, PAGE_SIZE, "%d\n", pdata->alt_mode & BIT(0));
}

/* sysfs entries */
static DEVICE_ATTR_RW(firmware_upgrade);
static DEVICE_ATTR_RO(get_fw_version);
static DEVICE_ATTR_RW(dp_alt_en);
static DEVICE_ATTR_RW(dp_lane_sel);
static DEVICE_ATTR_RO(get_alt_mode);

static struct attribute *lt8711uxe2_sysfs_attrs[] = {
	&dev_attr_firmware_upgrade.attr,
	&dev_attr_get_fw_version.attr,
	&dev_attr_dp_alt_en.attr,
	&dev_attr_dp_lane_sel.attr,
	&dev_attr_get_alt_mode.attr,
	NULL,
};

static struct attribute_group lt8711uxe2_attr_group = {
	.attrs = lt8711uxe2_sysfs_attrs,
};

static void lt8711uxe2_check_state(struct lt8711uxe2 *pdata)
{
	u8 data_role = 0;
	union extcon_property_value flip;
	union extcon_property_value ss_func;
	u8 flip_reg_val = 0;
	bool host_mode = false;
	bool device_mode = false;
	bool connected = false;
	bool flipped = false;
	unsigned int extcon_id = EXTCON_NONE;
	u32 dp_lane = gpio_get_value(pdata->dp_lane_sel_gpio);

	mutex_lock(&pdata->mutex);
	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
	lt8711uxe2_read(pdata, 0x84, &data_role, 1);
	lt8711uxe2_read(pdata, 0x85, &flip_reg_val, 1);
	mutex_unlock(&pdata->mutex);
	flipped = !!(flip_reg_val & BIT(2));
	pr_debug("%s flipped = %s\n", __func__, (flipped ? "True" : "False"));

	switch (data_role) {
	case LT8711UXE2_DISCONNECTED:
		pr_debug("%s LT8711UXE2_DISCONNECTED\n", __func__);
		host_mode = false;
		device_mode = false;
		connected = false;
		break;
	case LT8711UXE2_DFP_ATTACHED:
		pr_debug("%s LT8711UXE2_DFP_ATTACH (device mode)\n", __func__);
		extcon_id = EXTCON_USB;
		host_mode = false;
		device_mode = true;
		connected = true;
		break;
	case LT8711UXE2_UFP_ATTACHED:
		pr_debug("%s LT8711UXE2_UFP_ATTACH (host mode)\n", __func__);
		extcon_id = EXTCON_USB_HOST;
		host_mode = true;
		device_mode = false;
		connected = true;
		break;
	default:
		dev_err(pdata->dev, "Unknown state: %#x\n", data_role);
		return;
	}

	if (gpio_is_valid(pdata->cc_finished_gpio) &&
			(!pdata->is_cc_finished)) {
		pr_err("cc communicate not finish wait it!\n");
		return;
	}

	extcon_set_state(pdata->edev, EXTCON_USB_HOST, host_mode);
	extcon_set_state(pdata->edev, EXTCON_USB, device_mode);

	if (pdata->usb_ss_support) {
		if (dp_lane == LT8711UXE2_DP_2LANE)
			ss_func.intval = 1;
		else
			ss_func.intval = 0;
		extcon_set_property(pdata->edev, extcon_id,
				EXTCON_PROP_USB_SS, ss_func);
		if (pdata->with_redriver)
			extcon_set_property(pdata->edev, EXTCON_DISP_DP,
						EXTCON_PROP_USB_SS, ss_func);
	}
	flip.intval = 0;
	extcon_set_property(pdata->edev, extcon_id,
			EXTCON_PROP_USB_TYPEC_POLARITY, flip);
	if (pdata->with_redriver) {
		flip.intval = flipped;
		extcon_set_property(pdata->edev, EXTCON_DISP_DP,
				EXTCON_PROP_USB_TYPEC_POLARITY, flip);
		extcon_set_state(pdata->edev, EXTCON_DISP_DP, true);
		extcon_sync(pdata->edev, EXTCON_DISP_DP);
	}
	extcon_sync(pdata->edev, EXTCON_USB);
	extcon_sync(pdata->edev, EXTCON_USB_HOST);
}

static void lt8711uxe2_set_hdmi_switch_state(struct lt8711uxe2 *pdata,
		bool enabled)
{
	if (pdata->with_hdmi_switch) {
		extcon_set_state(pdata->edev, EXTCON_MECHANICAL,
				enabled);
		extcon_sync(pdata->edev, EXTCON_MECHANICAL);
	}
}

static irqreturn_t lt8711uxe2_irq_thread_handler(int irq, void *dev_id)
{
	struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)dev_id;
	u8 irq_type = 0;
	u8 alt_mode_stat = 0;

	if (!pdata)
		return IRQ_HANDLED;

	if (pdata->fw_status == UPDATE_RUNNING) {
		dev_err(pdata->dev, "UPDATE_RUNNING abnormal irq!\n");
		return IRQ_HANDLED;
	}
	mutex_lock(&pdata->mutex);
	lt8711uxe2_write_byte(pdata, 0xFF, 0xE0);
	lt8711uxe2_read(pdata, 0x80, &irq_type, 1);
	mutex_unlock(&pdata->mutex);
	if (IRQ_TYPE_EQUALS(irq_type, LT8711UXE2_IRQ_USB_HPD))
		lt8711uxe2_check_state(pdata);
	if (IRQ_TYPE_EQUALS(irq_type, LT8711UXE2_IRQ_DP_ALT_MODE_CHANGE)) {
		lt8711uxe2_read(pdata, 0x85, &alt_mode_stat, 1);
		pdata->alt_mode = alt_mode_stat;
		if (alt_mode_stat & BIT(0))
			pr_debug("Enter to DP alt mode\n");
		else
			pr_debug("Exit DP alt mode.\n");
	}
	if (IRQ_TYPE_EQUALS(irq_type, LT8711UXE2_IRQ_HDMI_OUTPUT_CHANGE)) {
		lt8711uxe2_read(pdata, 0x85, &alt_mode_stat, 1);
		pdata->alt_mode = alt_mode_stat;
		lt8711uxe2_set_hdmi_switch_state(pdata,
			!!(alt_mode_stat & BIT(0)));
		if (alt_mode_stat & BIT(1))
			pr_debug("HDMI output enabled.\n");
		else
			pr_debug("HDMI output disabled.\n");
	}
	return IRQ_HANDLED;
}

static irqreturn_t lt8711uxe2_cc_irq_thread_handler(int irq, void *dev_id)
{
	struct lt8711uxe2 *pdata = (struct lt8711uxe2 *)dev_id;

	if (gpio_get_value(pdata->cc_finished_gpio)) {
		pdata->is_cc_finished = true;
		lt8711uxe2_reset(pdata, true);
		pr_debug("get cc gpio high\n");
	} else {
		gpio_set_value(pdata->reset_gpio, LT8711UXE2_GPIO_LOW);
		pr_debug("get cc gpio low\n");
	}

	return IRQ_HANDLED;
}

static int lt8711uxe2_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct lt8711uxe2 *pdata;
	int ret = 0;

	pdata = devm_kzalloc(&client->dev,
			sizeof(struct lt8711uxe2), GFP_KERNEL);
	if (!pdata)
		return -ENOMEM;

	pdata->dev = &client->dev;
	pdata->i2c_client = client;

	i2c_set_clientdata(client, pdata);

	/* create crc8 table */
	crc8_populate_msb(lt8711uxe2_crc_table, 0x31);

	ret = lt8711uxe2_parse_dt(pdata);
	if (ret) {
		dev_err(pdata->dev, "failed to parse device tree\n");
		goto err_dt_parse;
	}

	ret = lt8711uxe2_gpio_configure(pdata, true);
	if (ret) {
		dev_err(pdata->dev, "failed to configure GPIOs\n");
		goto error;
	}

	mutex_init(&pdata->mutex);

	if (!gpio_is_valid(pdata->cc_finished_gpio)) {
		lt8711uxe2_reset(pdata, true);

		ret = lt8711uxe2_read_firmware_version(pdata);
		if (ret)
			dev_err(pdata->dev, "failed to read fw version\n");

		dev_info(pdata->dev, "chip firmware version: %#06x\n",
					pdata->chip_fw_version);
		dev_info(pdata->dev, "image firmware version: %#06x\n",
					pdata->image_fw_version);
		/* determine if firmware upgrade is needed, only accept exact version */
		if (pdata->image_fw_version > pdata->chip_fw_version) {
			dev_info(pdata->dev, "Upgrading fw %#06x => %#06x\n",
						pdata->chip_fw_version,
						pdata->image_fw_version);
			ret = request_firmware_nowait(THIS_MODULE, true,
						"lt8711uxe2_fw.bin",
						&client->dev,
						GFP_KERNEL, pdata,
						lt8711uxe2_fw_cb_main);
			if (ret) {
				dev_err(pdata->dev,
					"Failed to invoke firmware loader: %d\n",
					ret);
			}
		} else
			dev_info(pdata->dev, "firmware up-to-date\n");
	}

	(void)sysfs_create_group(&client->dev.kobj, &lt8711uxe2_attr_group);

	if (of_property_read_bool(pdata->dev->of_node, "with-redriver"))
		pdata->with_redriver = true;

	if (of_property_read_bool(pdata->dev->of_node, "with-hdmi-switch")) {
		pdata->with_hdmi_switch = true;
	}

	/* Allocate extcon device */
	if (pdata->with_redriver)
		pdata->edev = devm_extcon_dev_allocate(pdata->dev,
					lt8711uxe2_extcon_cable_with_redriver);
	else
		pdata->edev = devm_extcon_dev_allocate(pdata->dev,
					lt8711uxe2_extcon_cable);
	if (IS_ERR(pdata->edev)) {
		dev_err(pdata->dev, "failed to allocate memory for extcon\n");
		ret = -ENOMEM;
		goto remove_group;
	}

	/* Register extcon device */
	ret = devm_extcon_dev_register(pdata->dev, pdata->edev);
	if (ret) {
		dev_err(pdata->dev, "failed to register extcon device\n");
		goto remove_group;
	}

	if (of_property_read_bool(pdata->dev->of_node, "usb-ss-support"))
		pdata->usb_ss_support = true;

	extcon_set_property_capability(pdata->edev, EXTCON_USB,
					EXTCON_PROP_USB_VBUS);
	extcon_set_property_capability(pdata->edev, EXTCON_USB_HOST,
					EXTCON_PROP_USB_VBUS);
	extcon_set_property_capability(pdata->edev, EXTCON_USB,
					EXTCON_PROP_USB_TYPEC_POLARITY);
	extcon_set_property_capability(pdata->edev, EXTCON_USB_HOST,
					EXTCON_PROP_USB_TYPEC_POLARITY);

	if (pdata->usb_ss_support) {
		/* Support reporting polarity and speed via properties */
		extcon_set_property_capability(pdata->edev, EXTCON_USB,
					EXTCON_PROP_USB_SS);
		extcon_set_property_capability(pdata->edev, EXTCON_USB_HOST,
					EXTCON_PROP_USB_SS);
	}
	if (pdata->with_redriver) {
		extcon_set_property_capability(pdata->edev, EXTCON_DISP_DP,
					EXTCON_PROP_USB_TYPEC_POLARITY);
		extcon_set_property_capability(pdata->edev, EXTCON_DISP_DP,
					EXTCON_PROP_USB_SS);
	}
	lt8711uxe2_check_state(pdata);
	lt8711uxe2_read_alt_mode(pdata);

	if (!gpio_is_valid(pdata->cc_finished_gpio)) {
		lt8711uxe2_check_state(pdata);
		lt8711uxe2_read_alt_mode(pdata);

		ret = lt8711uxe2_read_alt_mode(pdata);
		if (!ret) {
			lt8711uxe2_set_hdmi_switch_state(pdata,
				!!(pdata->alt_mode & BIT(0)));
		}
	}

	/* Make sure LT8711UXE2 initialized, then enable irq. */
	pdata->irq = gpio_to_irq(pdata->irq_gpio);
	ret = devm_request_threaded_irq(&client->dev, pdata->irq, NULL,
				lt8711uxe2_irq_thread_handler,
				IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
				"lt8711uxe2_irq", pdata);
	if (ret) {
		dev_err(pdata->dev, "failed to request irq\n");
		goto remove_group;
	}
	enable_irq_wake(pdata->irq);

	if (gpio_is_valid(pdata->cc_finished_gpio)) {
		pdata->cc_irq = gpio_to_irq(pdata->cc_finished_gpio);
		ret = devm_request_threaded_irq(&client->dev, pdata->cc_irq, NULL,
					lt8711uxe2_cc_irq_thread_handler,
					IRQF_TRIGGER_RISING |
					IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
					"lt8711uxe2_cc_irq", pdata);
		if (ret) {
			pr_err("failed to request cc_irq\n");
			goto remove_group;
		}
		enable_irq_wake(pdata->cc_finished_gpio);
	}

	return 0;

remove_group:
	sysfs_remove_group(&client->dev.kobj, &lt8711uxe2_attr_group);
error:
	ret = lt8711uxe2_gpio_configure(pdata, false);
err_dt_parse:
	return ret;
}

static int lt8711uxe2_remove(struct i2c_client *client)
{
	struct lt8711uxe2 *pdata = i2c_get_clientdata(client);

	sysfs_remove_group(&client->dev.kobj, &lt8711uxe2_attr_group);
	lt8711uxe2_gpio_configure(pdata, false);
	return 0;
}

static int lt8711uxe2_pm_suspend(struct device *dev)
{
	return 0;
}

static int lt8711uxe2_pm_resume(struct device *dev)
{
	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
	struct lt8711uxe2 *pdata = NULL;

	if (!client)
		return 0;
	pdata = i2c_get_clientdata(client);
	if (!pdata)
		return 0;
	lt8711uxe2_check_state(pdata);
	return 0;
}

static const struct dev_pm_ops lt8711uxe2_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(lt8711uxe2_pm_suspend, lt8711uxe2_pm_resume)
};

static struct i2c_device_id lt8711uxe2_id[] = {
	{"lt,lt8711uxe2", 0 },
	{}
};

static const struct of_device_id lt8711uxe2_match_table[] = {
	{ .compatible = "lt,lt8711uxe2" },
	{}
};

MODULE_DEVICE_TABLE(of, lt8711uxe2_match_table);

static struct i2c_driver lt8711uxe2_driver = {
	.driver = {
		.name = "lt8711uxe2",
		.of_match_table = lt8711uxe2_match_table,
		.pm = &lt8711uxe2_pm_ops,
	},
	.probe = lt8711uxe2_probe,
	.remove = lt8711uxe2_remove,
	.id_table = lt8711uxe2_id,
};

module_i2c_driver(lt8711uxe2_driver);

MODULE_LICENSE("GPL");
