// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2024-2025, Qualcomm Innovation Center, Inc. All rights reserved. * */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_HIGH); 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, false); else lt8711uxe2_reset(pdata, true); ret = lt8711uxe2_read_firmware_version(pdata); if (ret) dev_warn(pdata->dev, "failed to read fw version, not checking fw up to date\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, <8711uxe2_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); 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, <8711uxe2_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, <8711uxe2_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 = <8711uxe2_pm_ops, }, .probe = lt8711uxe2_probe, .remove = lt8711uxe2_remove, .id_table = lt8711uxe2_id, }; module_i2c_driver(lt8711uxe2_driver); MODULE_LICENSE("GPL");