From patchwork Fri Jan 18 01:13:33 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ajay Gupta X-Patchwork-Id: 10769271 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 05DD7139A for ; Fri, 18 Jan 2019 01:13:56 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id E21AD286D4 for ; Fri, 18 Jan 2019 01:13:55 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D3DED28751; Fri, 18 Jan 2019 01:13:55 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 0D071286D4 for ; Fri, 18 Jan 2019 01:13:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726411AbfARBNy (ORCPT ); Thu, 17 Jan 2019 20:13:54 -0500 Received: from hqemgate15.nvidia.com ([216.228.121.64]:11381 "EHLO hqemgate15.nvidia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726182AbfARBNy (ORCPT ); Thu, 17 Jan 2019 20:13:54 -0500 Received: from hqpgpgate101.nvidia.com (Not Verified[216.228.121.13]) by hqemgate15.nvidia.com (using TLS: TLSv1.2, DES-CBC3-SHA) id ; Thu, 17 Jan 2019 17:13:30 -0800 Received: from hqmail.nvidia.com ([172.20.161.6]) by hqpgpgate101.nvidia.com (PGP Universal service); Thu, 17 Jan 2019 17:13:51 -0800 X-PGP-Universal: processed; by hqpgpgate101.nvidia.com on Thu, 17 Jan 2019 17:13:51 -0800 Received: from ajayg.nvidia.com (172.17.174.172) by HQMAIL101.nvidia.com (172.20.187.10) with Microsoft SMTP Server (TLS) id 15.0.1395.4; Fri, 18 Jan 2019 01:13:49 +0000 From: Ajay Gupta To: CC: , Ajay Gupta Subject: [PATCH 7/7] usb: typec: ucsi: add firmware flashing support Date: Thu, 17 Jan 2019 17:13:33 -0800 Message-ID: <20190118011333.16301-1-ajayg@nvidia.com> X-Mailer: git-send-email 2.17.1 X-NVConfidentiality: public MIME-Version: 1.0 X-Originating-IP: [172.17.174.172] X-ClientProxiedBy: DRBGMAIL101.nvidia.com (10.18.16.20) To HQMAIL101.nvidia.com (172.20.187.10) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1; t=1547774010; bh=w8BnzdA7X+NEdJShnpnE9RqSAOvtpUvZ0pUHvz45NsY=; h=X-PGP-Universal:From:To:CC:Subject:Date:Message-ID:X-Mailer: X-NVConfidentiality:MIME-Version:X-Originating-IP: X-ClientProxiedBy:Content-Type; b=USL1baTmESASUbHwXw4CsgMeagl7kUpGw51jhdzNRxcW3WNZAu1xIShbCCCuKt3Q1 w2NRXX+kkismtjh4rS0jqKe7pfPJJo1e1nipaEULKVFqCvoM8cZ3qDN1aNmA74bEt1 JO+XJDu1zJTL9m9oVrAk1zFp9XJ16gduRQOzpN5/m0/o3bzE4Yg1yIT1od2d0b6Fb0 f2Ta8ZdXrnkYC0hUP9j0zRnQfGWiNcIiPbxz3VGu97xuU7MmPVsFEToHqS/yKRY6ZT 3cE/uVMhgwACmHjpT3Ds2dv97nGdVNa9/4CsqsMW+dK5SyODNS3qKkr3NluAOg3aY7 KoSWSjtFe/EfA== Sender: linux-usb-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP CCGx 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. Signed-off-by: Ajay Gupta --- drivers/usb/typec/ucsi/ucsi_ccg.c | 283 ++++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 1aa6ede764b5..da83c145a392 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -239,6 +239,8 @@ struct ucsi_ccg { 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 */ }; @@ -904,6 +906,276 @@ static int ccg_fw_update_needed(struct ucsi_ccg *uc, 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.fw_mode == 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 = strchr(fw->data, ':'); + while (p < eof) { + s = strnchr(p + 1, CYACD_LINE_SIZE, ':'); + + 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 = (wr_buf[0] << 8) + wr_buf[1]; + len = (wr_buf[2] << 8) + wr_buf[3]; + + 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, mode == SECONDARY); + 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 = devm_request_threaded_irq(dev, 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); + if (uc->irq) + devm_free_irq(uc->dev, 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 i2c_client *i2c_cl = to_i2c_client(dev); + struct ucsi_ccg *uc = i2c_get_clientdata(i2c_cl); + unsigned int mode; + + if (kstrtouint(buf, 10, &mode)) + return -EINVAL; + + if (mode) + schedule_work(&uc->work); + + return n; +} + +static ssize_t do_flash_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "[Usage]\n" + "1) copy ccg_boot.cyacd /lib/firmware/\n" + "2) copy ccg_1.cyacd /lib/firmware/\n" + "3) copy ccg_2.cyacd /lib/firmware/\n" + "4) echo 1 > do_flash\n" + "5) Remove all TypeC cable/adapter after flashing\n"); +} + +static DEVICE_ATTR_RW(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) { @@ -924,6 +1196,8 @@ static int ucsi_ccg_probe(struct i2c_client *client, uc->ppm.sync = ucsi_ccg_sync; uc->dev = dev; uc->client = client; + mutex_init(&uc->lock); + INIT_WORK(&uc->work, ccg_update_firmware); /* reset ccg device and initialize ucsi */ status = ucsi_ccg_init(uc); @@ -952,6 +1226,8 @@ static int ucsi_ccg_probe(struct i2c_client *client, 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"); @@ -968,6 +1244,11 @@ static int ucsi_ccg_probe(struct i2c_client *client, } 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; } @@ -975,7 +1256,9 @@ static int ucsi_ccg_remove(struct i2c_client *client) { struct ucsi_ccg *uc = i2c_get_clientdata(client); + cancel_work_sync(&uc->work); ucsi_unregister_ppm(uc->ucsi); + sysfs_remove_group(&uc->dev->kobj, &ucsi_ccg_attr_group); return 0; }