*/
 #include <linux/acpi.h>
 #include <linux/delay.h>
+#include <linux/firmware.h>
 #include <linux/i2c.h>
 #include <linux/module.h>
 #include <linux/pci.h>
        FW_INVALID,
 };
 
+#define CCGX_RAB_DEVICE_MODE                   0x0000
+#define CCGX_RAB_INTR_REG                      0x0006
+#define  DEV_INT                               BIT(0)
+#define  PORT0_INT                             BIT(1)
+#define  PORT1_INT                             BIT(2)
+#define  UCSI_READ_INT                         BIT(7)
+#define CCGX_RAB_JUMP_TO_BOOT                  0x0007
+#define  TO_BOOT                               'J'
+#define  TO_ALT_FW                             'A'
+#define CCGX_RAB_RESET_REQ                     0x0008
+#define  RESET_SIG                             'R'
+#define  CMD_RESET_I2C                         0x0
+#define  CMD_RESET_DEV                         0x1
+#define CCGX_RAB_ENTER_FLASHING                        0x000A
+#define  FLASH_ENTER_SIG                       'P'
+#define CCGX_RAB_VALIDATE_FW                   0x000B
+#define CCGX_RAB_FLASH_ROW_RW                  0x000C
+#define  FLASH_SIG                             'F'
+#define  FLASH_RD_CMD                          0x0
+#define  FLASH_WR_CMD                          0x1
+#define  FLASH_FWCT1_WR_CMD                    0x2
+#define  FLASH_FWCT2_WR_CMD                    0x3
+#define  FLASH_FWCT_SIG_WR_CMD                 0x4
+#define CCGX_RAB_READ_ALL_VER                  0x0010
+#define CCGX_RAB_READ_FW2_VER                  0x0020
+#define CCGX_RAB_UCSI_CONTROL                  0x0039
+#define CCGX_RAB_UCSI_CONTROL_START            BIT(0)
+#define CCGX_RAB_UCSI_CONTROL_STOP             BIT(1)
+#define CCGX_RAB_UCSI_DATA_BLOCK(offset)       (0xf000 | ((offset) & 0xff))
+#define REG_FLASH_RW_MEM        0x0200
+#define DEV_REG_IDX                            CCGX_RAB_DEVICE_MODE
+#define CCGX_RAB_PDPORT_ENABLE                 0x002C
+#define  PDPORT_1              BIT(0)
+#define  PDPORT_2              BIT(1)
+#define CCGX_RAB_RESPONSE                      0x007E
+#define  ASYNC_EVENT                           BIT(7)
+
+/* CCGx events & async msg codes */
+#define RESET_COMPLETE         0x80
+#define EVENT_INDEX            RESET_COMPLETE
+#define PORT_CONNECT_DET       0x84
+#define PORT_DISCONNECT_DET    0x85
+#define ROLE_SWAP_COMPELETE    0x87
+
+/* ccg firmware */
+#define CYACD_LINE_SIZE         527
+#define CCG4_ROW_SIZE           256
+#define FW1_METADATA_ROW        0x1FF
+#define FW2_METADATA_ROW        0x1FE
+#define FW_CFG_TABLE_SIG_SIZE  256
+
+static int secondary_fw_min_ver = 41;
+
+enum enum_flash_mode {
+       SECONDARY_BL,   /* update secondary using bootloader */
+       PRIMARY,        /* update primary using secondary */
+       SECONDARY,      /* update secondary using primary */
+       FLASH_NOT_NEEDED,       /* update not required */
+       FLASH_INVALID,
+};
+
+static const char * const ccg_fw_names[] = {
+       "ccg_boot.cyacd",
+       "ccg_primary.cyacd",
+       "ccg_secondary.cyacd"
+};
+
 struct ccg_dev_info {
 #define CCG_DEVINFO_FWMODE_SHIFT (0)
 #define CCG_DEVINFO_FWMODE_MASK (0x3 << CCG_DEVINFO_FWMODE_SHIFT)
        struct version_format app;
 };
 
+struct fw_config_table {
+       u32 identity;
+       u16 table_size;
+       u8 fwct_version;
+       u8 is_key_change;
+       u8 guid[16];
+       struct version_format base;
+       struct version_format app;
+       u8 primary_fw_digest[32];
+       u32 key_exp_length;
+       u8 key_modulus[256];
+       u8 key_exp[4];
+};
+
+/* CCGx response codes */
+enum ccg_resp_code {
+       CMD_NO_RESP             = 0x00,
+       CMD_SUCCESS             = 0x02,
+       FLASH_DATA_AVAILABLE    = 0x03,
+       CMD_INVALID             = 0x05,
+       FLASH_UPDATE_FAIL       = 0x07,
+       INVALID_FW              = 0x08,
+       INVALID_ARG             = 0x09,
+       CMD_NOT_SUPPORT         = 0x0A,
+       TRANSACTION_FAIL        = 0x0C,
+       PD_CMD_FAIL             = 0x0D,
+       UNDEF_ERROR             = 0x0F,
+       INVALID_RESP            = 0x10,
+};
+
+#define CCG_EVENT_MAX  (EVENT_INDEX + 43)
+
+struct ccg_cmd {
+       u16 reg;
+       u32 data;
+       int len;
+       u32 delay; /* ms delay for cmd timeout  */
+};
+
+struct ccg_resp {
+       u8 code;
+       u8 length;
+};
+
 struct ucsi_ccg {
        struct device *dev;
        struct ucsi *ucsi;
        struct ccg_dev_info info;
        /* version info for boot, primary and secondary */
        struct version_info version[FW2 + 1];
-};
+       /* CCG HPI communication flags */
+       unsigned long flags;
+#define RESET_PENDING  0
+#define DEV_CMD_PENDING        1
+       struct ccg_resp dev_resp;
+       u8 cmd_resp;
+       int port_num;
+       int irq;
+       struct work_struct work;
+       struct mutex lock; /* to sync between user and driver thread */
 
-#define CCGX_RAB_DEVICE_MODE                   0x0000
-#define CCGX_RAB_INTR_REG                      0x0006
-#define CCGX_RAB_READ_ALL_VER                  0x0010
-#define CCGX_RAB_READ_FW2_VER                  0x0020
-#define CCGX_RAB_UCSI_CONTROL                  0x0039
-#define CCGX_RAB_UCSI_CONTROL_START            BIT(0)
-#define CCGX_RAB_UCSI_CONTROL_STOP             BIT(1)
-#define CCGX_RAB_UCSI_DATA_BLOCK(offset)       (0xf000 | ((offset) & 0xff))
+       /* fw build with vendor information */
+       u16 fw_build;
+};
 
 static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len)
 {
        return 0;
 }
 
+static inline bool invalid_async_evt(int code)
+{
+       return (code >= CCG_EVENT_MAX) || (code < EVENT_INDEX);
+}
+
+static void ccg_process_response(struct ucsi_ccg *uc)
+{
+       struct device *dev = uc->dev;
+
+       if (uc->dev_resp.code & ASYNC_EVENT) {
+               if (uc->dev_resp.code == RESET_COMPLETE) {
+                       if (test_bit(RESET_PENDING, &uc->flags))
+                               uc->cmd_resp = uc->dev_resp.code;
+                       get_fw_info(uc);
+               }
+               if (invalid_async_evt(uc->dev_resp.code))
+                       dev_err(dev, "invalid async evt %d\n",
+                               uc->dev_resp.code);
+       } else {
+               if (test_bit(DEV_CMD_PENDING, &uc->flags)) {
+                       uc->cmd_resp = uc->dev_resp.code;
+                       clear_bit(DEV_CMD_PENDING, &uc->flags);
+               } else {
+                       dev_err(dev, "dev resp 0x%04x but no cmd pending\n",
+                               uc->dev_resp.code);
+               }
+       }
+}
+
+static int ccg_read_response(struct ucsi_ccg *uc)
+{
+       unsigned long target = jiffies + msecs_to_jiffies(1000);
+       struct device *dev = uc->dev;
+       u8 intval;
+       int status;
+
+       /* wait for interrupt status to get updated */
+       do {
+               status = ccg_read(uc, CCGX_RAB_INTR_REG, &intval,
+                                 sizeof(intval));
+               if (status < 0)
+                       return status;
+
+               if (intval & DEV_INT)
+                       break;
+               usleep_range(500, 600);
+       } while (time_is_after_jiffies(target));
+
+       if (time_is_before_jiffies(target)) {
+               dev_err(dev, "response timeout error\n");
+               return -ETIME;
+       }
+
+       status = ccg_read(uc, CCGX_RAB_RESPONSE, (u8 *)&uc->dev_resp,
+                         sizeof(uc->dev_resp));
+       if (status < 0)
+               return status;
+
+       status = ccg_write(uc, CCGX_RAB_INTR_REG, &intval, sizeof(intval));
+       if (status < 0)
+               return status;
+
+       return 0;
+}
+
+/* Caller must hold uc->lock */
+static int ccg_send_command(struct ucsi_ccg *uc, struct ccg_cmd *cmd)
+{
+       struct device *dev = uc->dev;
+       int ret;
+
+       switch (cmd->reg & 0xF000) {
+       case DEV_REG_IDX:
+               set_bit(DEV_CMD_PENDING, &uc->flags);
+               break;
+       default:
+               dev_err(dev, "invalid cmd register\n");
+               break;
+       }
+
+       ret = ccg_write(uc, cmd->reg, (u8 *)&cmd->data, cmd->len);
+       if (ret < 0)
+               return ret;
+
+       msleep(cmd->delay);
+
+       ret = ccg_read_response(uc);
+       if (ret < 0) {
+               dev_err(dev, "response read error\n");
+               switch (cmd->reg & 0xF000) {
+               case DEV_REG_IDX:
+                       clear_bit(DEV_CMD_PENDING, &uc->flags);
+                       break;
+               default:
+                       dev_err(dev, "invalid cmd register\n");
+                       break;
+               }
+               return -EIO;
+       }
+       ccg_process_response(uc);
+
+       return uc->cmd_resp;
+}
+
+static int ccg_cmd_enter_flashing(struct ucsi_ccg *uc)
+{
+       struct ccg_cmd cmd;
+       int ret;
+
+       cmd.reg = CCGX_RAB_ENTER_FLASHING;
+       cmd.data = FLASH_ENTER_SIG;
+       cmd.len = 1;
+       cmd.delay = 50;
+
+       mutex_lock(&uc->lock);
+
+       ret = ccg_send_command(uc, &cmd);
+
+       mutex_unlock(&uc->lock);
+
+       if (ret != CMD_SUCCESS) {
+               dev_err(uc->dev, "enter flashing failed ret=%d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int ccg_cmd_reset(struct ucsi_ccg *uc)
+{
+       struct ccg_cmd cmd;
+       u8 *p;
+       int ret;
+
+       p = (u8 *)&cmd.data;
+       cmd.reg = CCGX_RAB_RESET_REQ;
+       p[0] = RESET_SIG;
+       p[1] = CMD_RESET_DEV;
+       cmd.len = 2;
+       cmd.delay = 5000;
+
+       mutex_lock(&uc->lock);
+
+       set_bit(RESET_PENDING, &uc->flags);
+
+       ret = ccg_send_command(uc, &cmd);
+       if (ret != RESET_COMPLETE)
+               goto err_clear_flag;
+
+       ret = 0;
+
+err_clear_flag:
+       clear_bit(RESET_PENDING, &uc->flags);
+
+       mutex_unlock(&uc->lock);
+
+       return ret;
+}
+
+static int ccg_cmd_port_control(struct ucsi_ccg *uc, bool enable)
+{
+       struct ccg_cmd cmd;
+       int ret;
+
+       cmd.reg = CCGX_RAB_PDPORT_ENABLE;
+       if (enable)
+               cmd.data = (uc->port_num == 1) ?
+                           PDPORT_1 : (PDPORT_1 | PDPORT_2);
+       else
+               cmd.data = 0x0;
+       cmd.len = 1;
+       cmd.delay = 10;
+
+       mutex_lock(&uc->lock);
+
+       ret = ccg_send_command(uc, &cmd);
+
+       mutex_unlock(&uc->lock);
+
+       if (ret != CMD_SUCCESS) {
+               dev_err(uc->dev, "port control failed ret=%d\n", ret);
+               return ret;
+       }
+       return 0;
+}
+
+static int ccg_cmd_jump_boot_mode(struct ucsi_ccg *uc, int bl_mode)
+{
+       struct ccg_cmd cmd;
+       int ret;
+
+       cmd.reg = CCGX_RAB_JUMP_TO_BOOT;
+
+       if (bl_mode)
+               cmd.data = TO_BOOT;
+       else
+               cmd.data = TO_ALT_FW;
+
+       cmd.len = 1;
+       cmd.delay = 100;
+
+       mutex_lock(&uc->lock);
+
+       set_bit(RESET_PENDING, &uc->flags);
+
+       ret = ccg_send_command(uc, &cmd);
+       if (ret != RESET_COMPLETE)
+               goto err_clear_flag;
+
+       ret = 0;
+
+err_clear_flag:
+       clear_bit(RESET_PENDING, &uc->flags);
+
+       mutex_unlock(&uc->lock);
+
+       return ret;
+}
+
+static int
+ccg_cmd_write_flash_row(struct ucsi_ccg *uc, u16 row,
+                       const void *data, u8 fcmd)
+{
+       struct i2c_client *client = uc->client;
+       struct ccg_cmd cmd;
+       u8 buf[CCG4_ROW_SIZE + 2];
+       u8 *p;
+       int ret;
+
+       /* Copy the data into the flash read/write memory. */
+       put_unaligned_le16(REG_FLASH_RW_MEM, buf);
+
+       memcpy(buf + 2, data, CCG4_ROW_SIZE);
+
+       mutex_lock(&uc->lock);
+
+       ret = i2c_master_send(client, buf, CCG4_ROW_SIZE + 2);
+       if (ret != CCG4_ROW_SIZE + 2) {
+               dev_err(uc->dev, "REG_FLASH_RW_MEM write fail %d\n", ret);
+               return ret < 0 ? ret : -EIO;
+       }
+
+       /* Use the FLASH_ROW_READ_WRITE register to trigger */
+       /* writing of data to the desired flash row */
+       p = (u8 *)&cmd.data;
+       cmd.reg = CCGX_RAB_FLASH_ROW_RW;
+       p[0] = FLASH_SIG;
+       p[1] = fcmd;
+       put_unaligned_le16(row, &p[2]);
+       cmd.len = 4;
+       cmd.delay = 50;
+       if (fcmd == FLASH_FWCT_SIG_WR_CMD)
+               cmd.delay += 400;
+       if (row == 510)
+               cmd.delay += 220;
+       ret = ccg_send_command(uc, &cmd);
+
+       mutex_unlock(&uc->lock);
+
+       if (ret != CMD_SUCCESS) {
+               dev_err(uc->dev, "write flash row failed ret=%d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static int ccg_cmd_validate_fw(struct ucsi_ccg *uc, unsigned int fwid)
+{
+       struct ccg_cmd cmd;
+       int ret;
+
+       cmd.reg = CCGX_RAB_VALIDATE_FW;
+       cmd.data = fwid;
+       cmd.len = 1;
+       cmd.delay = 500;
+
+       mutex_lock(&uc->lock);
+
+       ret = ccg_send_command(uc, &cmd);
+
+       mutex_unlock(&uc->lock);
+
+       if (ret != CMD_SUCCESS)
+               return ret;
+
+       return 0;
+}
+
+static bool ccg_check_vendor_version(struct ucsi_ccg *uc,
+                                    struct version_format *app,
+                                    struct fw_config_table *fw_cfg)
+{
+       struct device *dev = uc->dev;
+
+       /* Check if the fw build is for supported vendors */
+       if (le16_to_cpu(app->build) != uc->fw_build) {
+               dev_info(dev, "current fw is not from supported vendor\n");
+               return false;
+       }
+
+       /* Check if the new fw build is for supported vendors */
+       if (le16_to_cpu(fw_cfg->app.build) != uc->fw_build) {
+               dev_info(dev, "new fw is not from supported vendor\n");
+               return false;
+       }
+       return true;
+}
+
+static bool ccg_check_fw_version(struct ucsi_ccg *uc, const char *fw_name,
+                                struct version_format *app)
+{
+       const struct firmware *fw = NULL;
+       struct device *dev = uc->dev;
+       struct fw_config_table fw_cfg;
+       u32 cur_version, new_version;
+       bool is_later = false;
+
+       if (request_firmware(&fw, fw_name, dev) != 0) {
+               dev_err(dev, "error: Failed to open cyacd file %s\n", fw_name);
+               return false;
+       }
+
+       /*
+        * check if signed fw
+        * last part of fw image is fw cfg table and signature
+        */
+       if (fw->size < sizeof(fw_cfg) + FW_CFG_TABLE_SIG_SIZE)
+               goto out_release_firmware;
+
+       memcpy((uint8_t *)&fw_cfg, fw->data + fw->size -
+              sizeof(fw_cfg) - FW_CFG_TABLE_SIG_SIZE, sizeof(fw_cfg));
+
+       if (fw_cfg.identity != ('F' | 'W' << 8 | 'C' << 16 | 'T' << 24)) {
+               dev_info(dev, "not a signed image\n");
+               goto out_release_firmware;
+       }
+
+       /* compare input version with FWCT version */
+       cur_version = le16_to_cpu(app->build) | app->patch << 16 |
+                       app->ver << 24;
+
+       new_version = le16_to_cpu(fw_cfg.app.build) | fw_cfg.app.patch << 16 |
+                       fw_cfg.app.ver << 24;
+
+       if (!ccg_check_vendor_version(uc, app, &fw_cfg))
+               goto out_release_firmware;
+
+       if (new_version > cur_version)
+               is_later = true;
+
+out_release_firmware:
+       release_firmware(fw);
+       return is_later;
+}
+
+static int ccg_fw_update_needed(struct ucsi_ccg *uc,
+                               enum enum_flash_mode *mode)
+{
+       struct device *dev = uc->dev;
+       int err;
+       struct version_info version[3];
+
+       err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info),
+                      sizeof(uc->info));
+       if (err) {
+               dev_err(dev, "read device mode failed\n");
+               return err;
+       }
+
+       err = ccg_read(uc, CCGX_RAB_READ_ALL_VER, (u8 *)version,
+                      sizeof(version));
+       if (err) {
+               dev_err(dev, "read device mode failed\n");
+               return err;
+       }
+
+       if (memcmp(&version[FW1], "\0\0\0\0\0\0\0\0",
+                  sizeof(struct version_info)) == 0) {
+               dev_info(dev, "secondary fw is not flashed\n");
+               *mode = SECONDARY_BL;
+       } else if (le16_to_cpu(version[FW1].base.build) <
+               secondary_fw_min_ver) {
+               dev_info(dev, "secondary fw version is too low (< %d)\n",
+                        secondary_fw_min_ver);
+               *mode = SECONDARY;
+       } else if (memcmp(&version[FW2], "\0\0\0\0\0\0\0\0",
+                  sizeof(struct version_info)) == 0) {
+               dev_info(dev, "primary fw is not flashed\n");
+               *mode = PRIMARY;
+       } else if (ccg_check_fw_version(uc, ccg_fw_names[PRIMARY],
+                  &version[FW2].app)) {
+               dev_info(dev, "found primary fw with later version\n");
+               *mode = PRIMARY;
+       } else {
+               dev_info(dev, "secondary and primary fw are the latest\n");
+               *mode = FLASH_NOT_NEEDED;
+       }
+       return 0;
+}
+
+static int do_flash(struct ucsi_ccg *uc, enum enum_flash_mode mode)
+{
+       struct device *dev = uc->dev;
+       const struct firmware *fw = NULL;
+       const char *p, *s;
+       const char *eof;
+       int err, row, len, line_sz, line_cnt = 0;
+       unsigned long start_time = jiffies;
+       struct fw_config_table  fw_cfg;
+       u8 fw_cfg_sig[FW_CFG_TABLE_SIG_SIZE];
+       u8 *wr_buf;
+
+       err = request_firmware(&fw, ccg_fw_names[mode], dev);
+       if (err) {
+               dev_err(dev, "request %s failed err=%d\n",
+                       ccg_fw_names[mode], err);
+               return err;
+       }
+
+       if (((uc->info.mode & CCG_DEVINFO_FWMODE_MASK) >>
+                       CCG_DEVINFO_FWMODE_SHIFT) == FW2) {
+               err = ccg_cmd_port_control(uc, false);
+               if (err < 0)
+                       goto release_fw;
+               err = ccg_cmd_jump_boot_mode(uc, 0);
+               if (err < 0)
+                       goto release_fw;
+       }
+
+       eof = fw->data + fw->size;
+
+       /*
+        * check if signed fw
+        * last part of fw image is fw cfg table and signature
+        */
+       if (fw->size < sizeof(fw_cfg) + sizeof(fw_cfg_sig))
+               goto not_signed_fw;
+
+       memcpy((uint8_t *)&fw_cfg, fw->data + fw->size -
+              sizeof(fw_cfg) - sizeof(fw_cfg_sig), sizeof(fw_cfg));
+
+       if (fw_cfg.identity != ('F' | ('W' << 8) | ('C' << 16) | ('T' << 24))) {
+               dev_info(dev, "not a signed image\n");
+               goto not_signed_fw;
+       }
+       eof = fw->data + fw->size - sizeof(fw_cfg) - sizeof(fw_cfg_sig);
+
+       memcpy((uint8_t *)&fw_cfg_sig,
+              fw->data + fw->size - sizeof(fw_cfg_sig), sizeof(fw_cfg_sig));
+
+       /* flash fw config table and signature first */
+       err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg,
+                                     FLASH_FWCT1_WR_CMD);
+       if (err)
+               goto release_fw;
+
+       err = ccg_cmd_write_flash_row(uc, 0, (u8 *)&fw_cfg + CCG4_ROW_SIZE,
+                                     FLASH_FWCT2_WR_CMD);
+       if (err)
+               goto release_fw;
+
+       err = ccg_cmd_write_flash_row(uc, 0, &fw_cfg_sig,
+                                     FLASH_FWCT_SIG_WR_CMD);
+       if (err)
+               goto release_fw;
+
+not_signed_fw:
+       wr_buf = kzalloc(CCG4_ROW_SIZE + 4, GFP_KERNEL);
+       if (!wr_buf)
+               return -ENOMEM;
+
+       err = ccg_cmd_enter_flashing(uc);
+       if (err)
+               goto release_mem;
+
+       /*****************************************************************
+        * CCG firmware image (.cyacd) file line format
+        *
+        * :00rrrrllll[dd....]cc/r/n
+        *
+        * :00   header
+        * rrrr is row number to flash                          (4 char)
+        * llll is data len to flash                            (4 char)
+        * dd   is a data field represents one byte of data     (512 char)
+        * cc   is checksum                                     (2 char)
+        * \r\n newline
+        *
+        * Total length: 3 + 4 + 4 + 512 + 2 + 2 = 527
+        *
+        *****************************************************************/
+
+       p = strnchr(fw->data, fw->size, ':');
+       while (p < eof) {
+               s = strnchr(p + 1, eof - p - 1, ':');
+
+               if (!s)
+                       s = eof;
+
+               line_sz = s - p;
+
+               if (line_sz != CYACD_LINE_SIZE) {
+                       dev_err(dev, "Bad FW format line_sz=%d\n", line_sz);
+                       err =  -EINVAL;
+                       goto release_mem;
+               }
+
+               if (hex2bin(wr_buf, p + 3, CCG4_ROW_SIZE + 4)) {
+                       err =  -EINVAL;
+                       goto release_mem;
+               }
+
+               row = get_unaligned_be16(wr_buf);
+               len = get_unaligned_be16(&wr_buf[2]);
+
+               if (len != CCG4_ROW_SIZE) {
+                       err =  -EINVAL;
+                       goto release_mem;
+               }
+
+               err = ccg_cmd_write_flash_row(uc, row, wr_buf + 4,
+                                             FLASH_WR_CMD);
+               if (err)
+                       goto release_mem;
+
+               line_cnt++;
+               p = s;
+       }
+
+       dev_info(dev, "total %d row flashed. time: %dms\n",
+                line_cnt, jiffies_to_msecs(jiffies - start_time));
+
+       err = ccg_cmd_validate_fw(uc, (mode == PRIMARY) ? FW2 :  FW1);
+       if (err)
+               dev_err(dev, "%s validation failed err=%d\n",
+                       (mode == PRIMARY) ? "FW2" :  "FW1", err);
+       else
+               dev_info(dev, "%s validated\n",
+                        (mode == PRIMARY) ? "FW2" :  "FW1");
+
+       err = ccg_cmd_port_control(uc, false);
+       if (err < 0)
+               goto release_mem;
+
+       err = ccg_cmd_reset(uc);
+       if (err < 0)
+               goto release_mem;
+
+       err = ccg_cmd_port_control(uc, true);
+       if (err < 0)
+               goto release_mem;
+
+release_mem:
+       kfree(wr_buf);
+
+release_fw:
+       release_firmware(fw);
+       return err;
+}
+
+/*******************************************************************************
+ * CCG4 has two copies of the firmware in addition to the bootloader.
+ * If the device is running FW1, FW2 can be updated with the new version.
+ * Dual firmware mode allows the CCG device to stay in a PD contract and support
+ * USB PD and Type-C functionality while a firmware update is in progress.
+ ******************************************************************************/
+static int ccg_fw_update(struct ucsi_ccg *uc, enum enum_flash_mode flash_mode)
+{
+       int err;
+
+       while (flash_mode != FLASH_NOT_NEEDED) {
+               err = do_flash(uc, flash_mode);
+               if (err < 0)
+                       return err;
+               err = ccg_fw_update_needed(uc, &flash_mode);
+               if (err < 0)
+                       return err;
+       }
+       dev_info(uc->dev, "CCG FW update successful\n");
+
+       return err;
+}
+
+static int ccg_restart(struct ucsi_ccg *uc)
+{
+       struct device *dev = uc->dev;
+       int status;
+
+       status = ucsi_ccg_init(uc);
+       if (status < 0) {
+               dev_err(dev, "ucsi_ccg_start fail, err=%d\n", status);
+               return status;
+       }
+
+       status = request_threaded_irq(uc->irq, NULL, ccg_irq_handler,
+                                     IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+                                     dev_name(dev), uc);
+       if (status < 0) {
+               dev_err(dev, "request_threaded_irq failed - %d\n", status);
+               return status;
+       }
+
+       uc->ucsi = ucsi_register_ppm(dev, &uc->ppm);
+       if (IS_ERR(uc->ucsi)) {
+               dev_err(uc->dev, "ucsi_register_ppm failed\n");
+               return PTR_ERR(uc->ucsi);
+       }
+
+       return 0;
+}
+
+static void ccg_update_firmware(struct work_struct *work)
+{
+       struct ucsi_ccg *uc = container_of(work, struct ucsi_ccg, work);
+       enum enum_flash_mode flash_mode;
+       int status;
+
+       status = ccg_fw_update_needed(uc, &flash_mode);
+       if (status < 0)
+               return;
+
+       if (flash_mode != FLASH_NOT_NEEDED) {
+               ucsi_unregister_ppm(uc->ucsi);
+               free_irq(uc->irq, uc);
+
+               ccg_fw_update(uc, flash_mode);
+               ccg_restart(uc);
+       }
+}
+
+static ssize_t do_flash_store(struct device *dev,
+                             struct device_attribute *attr,
+                             const char *buf, size_t n)
+{
+       struct ucsi_ccg *uc = i2c_get_clientdata(to_i2c_client(dev));
+       bool flash;
+
+       if (kstrtobool(buf, &flash))
+               return -EINVAL;
+
+       if (!flash)
+               return n;
+
+       if (uc->fw_build == 0x0) {
+               dev_err(dev, "fail to flash FW due to missing FW build info\n");
+               return -EINVAL;
+       }
+
+       schedule_work(&uc->work);
+       return n;
+}
+
+static DEVICE_ATTR_WO(do_flash);
+
+static struct attribute *ucsi_ccg_sysfs_attrs[] = {
+       &dev_attr_do_flash.attr,
+       NULL,
+};
+
+static struct attribute_group ucsi_ccg_attr_group = {
+       .attrs = ucsi_ccg_sysfs_attrs,
+};
+
 static int ucsi_ccg_probe(struct i2c_client *client,
                          const struct i2c_device_id *id)
 {
        uc->ppm.sync = ucsi_ccg_sync;
        uc->dev = dev;
        uc->client = client;
+       mutex_init(&uc->lock);
+       INIT_WORK(&uc->work, ccg_update_firmware);
+
+       /* Only fail FW flashing when FW build information is not provided */
+       status = device_property_read_u16(dev, "ccgx,firmware-build",
+                                         &uc->fw_build);
+       if (status)
+               dev_err(uc->dev, "failed to get FW build information\n");
 
        /* reset ccg device and initialize ucsi */
        status = ucsi_ccg_init(uc);
                return status;
        }
 
-       status = devm_request_threaded_irq(dev, client->irq, NULL,
-                                          ccg_irq_handler,
-                                          IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
-                                          dev_name(dev), uc);
+       uc->port_num = 1;
+
+       if (uc->info.mode & CCG_DEVINFO_PDPORTS_MASK)
+               uc->port_num++;
+
+       status = request_threaded_irq(client->irq, NULL, ccg_irq_handler,
+                                     IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
+                                     dev_name(dev), uc);
        if (status < 0) {
                dev_err(uc->dev, "request_threaded_irq failed - %d\n", status);
                return status;
        }
 
+       uc->irq = client->irq;
+
        uc->ucsi = ucsi_register_ppm(dev, &uc->ppm);
        if (IS_ERR(uc->ucsi)) {
                dev_err(uc->dev, "ucsi_register_ppm failed\n");
        }
 
        i2c_set_clientdata(client, uc);
+
+       status = sysfs_create_group(&uc->dev->kobj, &ucsi_ccg_attr_group);
+       if (status)
+               dev_err(uc->dev, "cannot create sysfs group: %d\n", status);
+
        return 0;
 }
 
 {
        struct ucsi_ccg *uc = i2c_get_clientdata(client);
 
+       cancel_work_sync(&uc->work);
        ucsi_unregister_ppm(uc->ucsi);
+       free_irq(uc->irq, uc);
+       sysfs_remove_group(&uc->dev->kobj, &ucsi_ccg_attr_group);
 
        return 0;
 }