From patchwork Mon Jul 11 12:01:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabrice Gasnier X-Patchwork-Id: 589750 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0017AC43334 for ; Mon, 11 Jul 2022 12:01:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229721AbiGKMBt (ORCPT ); Mon, 11 Jul 2022 08:01:49 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55268 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229685AbiGKMBr (ORCPT ); Mon, 11 Jul 2022 08:01:47 -0400 Received: from mx07-00178001.pphosted.com (mx08-00178001.pphosted.com [91.207.212.93]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id CCCAA37F8C; Mon, 11 Jul 2022 05:01:46 -0700 (PDT) Received: from pps.filterd (m0046660.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.17.1.5/8.17.1.5) with ESMTP id 26BB1qC9000697; Mon, 11 Jul 2022 14:01:38 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=foss.st.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=selector1; bh=jHypv5E2Ntg6iXVsvy1Ne3ieQ+rIz+C8sBC7FRD48UU=; b=vW4bZd/jCn66wdgq1ucTAX+nWsOI+ewsHPGNeO5MhBrjj+xAiq7wlzfmc9RGhvtLrOiM FTsrZ3RVO4AgsStSZD0TYONBvkYL5oubLWgdP2mQW6dEIief0UlMqt/fVX5gxyosAkPo IGHCrL3sVyNapt+qmzfdL6luVVqYPlDvW0ZsQdmlJz5bmi5vPFQILhm3SAxdx2ww/mub /3o3YVapTQC9N4UWBl4UJtfaIGfihpV9OZCrh1ZE7oOY7QxOc4mHeCwGt4y6bWcpXelL MGfkbacMha5LmsGoJBUHU5XXDf+iF/5aFG+v0tP2sHo+nYnCjU22fe6tc1tUv0q+rTfj Pw== Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-00178001.pphosted.com (PPS) with ESMTPS id 3h6xtak5vd-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 11 Jul 2022 14:01:38 +0200 Received: from euls16034.sgp.st.com (euls16034.sgp.st.com [10.75.44.20]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 0301F10002A; Mon, 11 Jul 2022 14:01:37 +0200 (CEST) Received: from Webmail-eu.st.com (shfdag1node2.st.com [10.75.129.70]) by euls16034.sgp.st.com (STMicroelectronics) with ESMTP id ED10E21ED2B; Mon, 11 Jul 2022 14:01:37 +0200 (CEST) Received: from localhost (10.75.127.50) by SHFDAG1NODE2.st.com (10.75.129.70) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.1.2308.20; Mon, 11 Jul 2022 14:01:37 +0200 From: Fabrice Gasnier To: , , , CC: , , , , , , , Subject: [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller Date: Mon, 11 Jul 2022 14:01:19 +0200 Message-ID: <20220711120122.25804-2-fabrice.gasnier@foss.st.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> References: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> MIME-Version: 1.0 X-Originating-IP: [10.75.127.50] X-ClientProxiedBy: SFHDAG2NODE3.st.com (10.75.127.6) To SHFDAG1NODE2.st.com (10.75.129.70) X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.205,Aquarius:18.0.883,Hydra:6.0.517,FMLib:17.11.122.1 definitions=2022-07-11_17,2022-07-08_01,2022-06-22_01 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Add DT schema documentation for the STM32G0 Type-C PD (Power Delivery) controller. STM32G0 provides an integrated USB Type-C and power delivery interface. It can be programmed with a firmware to handle UCSI protocol over I2C interface. A GPIO is used as an interrupt line. It may be used as a wakeup source, so use optional "wakeup-source" and "power-domains" properties to support wakeup. Signed-off-by: Fabrice Gasnier --- Changes in v2: - Krzysztof's review comments: update commit message, use ports, use unevaluatedProperties: false for usb-connector schema, define maxItems for power-domains, adopt generic node names, remove quotes --- .../bindings/usb/st,typec-stm32g0.yaml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/st,typec-stm32g0.yaml diff --git a/Documentation/devicetree/bindings/usb/st,typec-stm32g0.yaml b/Documentation/devicetree/bindings/usb/st,typec-stm32g0.yaml new file mode 100644 index 0000000000000..7b3a2c2124e38 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/st,typec-stm32g0.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/st,typec-stm32g0.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: STMicroelectronics STM32G0 USB Type-C PD controller + +description: | + The STM32G0 MCU can be programmed to control Type-C connector(s) through I2C + typically using the UCSI protocol over I2C, with a dedicated alert + (interrupt) pin. + +maintainers: + - Fabrice Gasnier + +properties: + compatible: + const: st,stm32g0-typec + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + connector: + type: object + $ref: /schemas/connector/usb-connector.yaml# + unevaluatedProperties: false + + firmware-name: + description: | + Should contain the name of the default firmware image + file located on the firmware search path + + wakeup-source: true + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + typec@53 { + compatible = "st,stm32g0-typec"; + reg = <0x53>; + /* Alert pin on GPIO PE12 */ + interrupts = <12 IRQ_TYPE_EDGE_FALLING>; + interrupt-parent = <&gpioe>; + + /* Example with one type-C connector */ + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + port@0 { + reg = <0>; + con_usb_c_ep: endpoint { + remote-endpoint = <&usb_ep>; + }; + }; + }; + }; + }; + }; + + usb { + usb-role-switch; + port { + usb_ep: endpoint { + remote-endpoint = <&con_usb_c_ep>; + }; + }; + }; +... From patchwork Mon Jul 11 12:01:20 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabrice Gasnier X-Patchwork-Id: 590297 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 94B90CCA480 for ; Mon, 11 Jul 2022 12:01:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230325AbiGKMBv (ORCPT ); Mon, 11 Jul 2022 08:01:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55280 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229960AbiGKMBt (ORCPT ); Mon, 11 Jul 2022 08:01:49 -0400 Received: from mx07-00178001.pphosted.com (mx07-00178001.pphosted.com [185.132.182.106]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1FAA837F94; Mon, 11 Jul 2022 05:01:47 -0700 (PDT) Received: from pps.filterd (m0288072.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.17.1.5/8.17.1.5) with ESMTP id 26B7xtWr028419; Mon, 11 Jul 2022 14:01:39 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=foss.st.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=selector1; bh=Bu5xyPbtPYx5VqFElRIOuAZAo8muyqqWqfpgWulD9O8=; b=VEBsY26RtjHm2LKoEz75g0bPZJQMSLkSKJPW+LQlLVkVAMXfqOxuMyLuPDiRenI5SnaW eaGPeYFUszwKLAG/3rMUgFUHY4+KYtpCh0jGdK6Xs8LV3ooYKPHbk3lFozWVv2v9Lhgi gxm5QlUphzndLUXaE56WwfbNN53RGF2GXZMEvLHacYB3K6t8GSXN/lc0kPxWxTVB+lr3 roDhNlLjGSs4NR/Bbd53C6Tqgqpj2iAb/zdeK2P+/XYbtomHJyWCbJBpHH2fQtesBHws l+cEw14N66l1+4+t9FiA3Nn1MLWPFvfWGMxRByTv9Yi0z/2uJFiCPovZymdVPWAY7a7k Qg== Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-00178001.pphosted.com (PPS) with ESMTPS id 3h6y3mu6g1-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 11 Jul 2022 14:01:39 +0200 Received: from euls16034.sgp.st.com (euls16034.sgp.st.com [10.75.44.20]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 2571D10002A; Mon, 11 Jul 2022 14:01:39 +0200 (CEST) Received: from Webmail-eu.st.com (shfdag1node2.st.com [10.75.129.70]) by euls16034.sgp.st.com (STMicroelectronics) with ESMTP id 1FD5221ED2C; Mon, 11 Jul 2022 14:01:39 +0200 (CEST) Received: from localhost (10.75.127.50) by SHFDAG1NODE2.st.com (10.75.129.70) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.1.2308.20; Mon, 11 Jul 2022 14:01:38 +0200 From: Fabrice Gasnier To: , , , CC: , , , , , , , Subject: [PATCH v2 2/4] usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller Date: Mon, 11 Jul 2022 14:01:20 +0200 Message-ID: <20220711120122.25804-3-fabrice.gasnier@foss.st.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> References: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> MIME-Version: 1.0 X-Originating-IP: [10.75.127.50] X-ClientProxiedBy: SFHDAG2NODE3.st.com (10.75.127.6) To SHFDAG1NODE2.st.com (10.75.129.70) X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.205,Aquarius:18.0.883,Hydra:6.0.517,FMLib:17.11.122.1 definitions=2022-07-11_17,2022-07-08_01,2022-06-22_01 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org STM32G0 provides an integrated USB Type-C and power delivery interface. It can be programmed with a firmware to handle UCSI protocol over I2C interface. A GPIO is used as an interrupt line. Signed-off-by: Fabrice Gasnier --- Changes in v2: Christophe's remarks: - use kmalloc instead of kzalloc - Use-after-free of buf: directly print the offset --- drivers/usb/typec/ucsi/Kconfig | 10 ++ drivers/usb/typec/ucsi/Makefile | 1 + drivers/usb/typec/ucsi/ucsi_stm32g0.c | 218 ++++++++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 drivers/usb/typec/ucsi/ucsi_stm32g0.c diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index 5e9b37b3f25e1..8f9c4b9f31f79 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -48,4 +48,14 @@ config UCSI_ACPI To compile the driver as a module, choose M here: the module will be called ucsi_acpi +config UCSI_STM32G0 + tristate "UCSI Interface Driver for STM32G0" + depends on I2C + help + This driver enables UCSI support on platforms that expose a STM32G0 + Type-C controller over I2C interface. + + To compile the driver as a module, choose M here: the module will be + called ucsi_stm32g0. + endif diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile index 8a8eb5cb8e0f0..480d533d762fe 100644 --- a/drivers/usb/typec/ucsi/Makefile +++ b/drivers/usb/typec/ucsi/Makefile @@ -17,3 +17,4 @@ endif obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o +obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c new file mode 100644 index 0000000000000..bb5271bf7cdcf --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +/* + * UCSI driver for STMicroelectronics STM32G0 Type-C PD controller + * + * Copyright (C) 2022, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier . + */ + +#include +#include +#include +#include + +#include "ucsi.h" + +struct ucsi_stm32g0 { + struct i2c_client *client; + struct completion complete; + struct device *dev; + unsigned long flags; + struct ucsi *ucsi; +}; + +static int ucsi_stm32g0_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + u8 reg = offset; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = val, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c read %02x, %02x error: %d\n", client->addr, reg, ret); + + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int ucsi_stm32g0_async_write(struct ucsi *ucsi, unsigned int offset, const void *val, + size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + } + }; + unsigned char *buf; + int ret; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = offset; + memcpy(&buf[1], val, len); + msg[0].len = len + 1; + msg[0].buf = buf; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + kfree(buf); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c write %02x, %02x error: %d\n", client->addr, offset, ret); + + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +static int ucsi_stm32g0_sync_write(struct ucsi *ucsi, unsigned int offset, const void *val, + size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + int ret; + + set_bit(COMMAND_PENDING, &g0->flags); + + ret = ucsi_stm32g0_async_write(ucsi, offset, val, len); + if (ret) + goto out_clear_bit; + + if (!wait_for_completion_timeout(&g0->complete, msecs_to_jiffies(5000))) + ret = -ETIMEDOUT; + +out_clear_bit: + clear_bit(COMMAND_PENDING, &g0->flags); + + return ret; +} + +static irqreturn_t ucsi_stm32g0_irq_handler(int irq, void *data) +{ + struct ucsi_stm32g0 *g0 = data; + u32 cci; + int ret; + + ret = ucsi_stm32g0_read(g0->ucsi, UCSI_CCI, &cci, sizeof(cci)); + if (ret) + return IRQ_NONE; + + if (UCSI_CCI_CONNECTOR(cci)) + ucsi_connector_change(g0->ucsi, UCSI_CCI_CONNECTOR(cci)); + + if (test_bit(COMMAND_PENDING, &g0->flags) && + cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) + complete(&g0->complete); + + return IRQ_HANDLED; +} + +static const struct ucsi_operations ucsi_stm32g0_ops = { + .read = ucsi_stm32g0_read, + .sync_write = ucsi_stm32g0_sync_write, + .async_write = ucsi_stm32g0_async_write, +}; + +static int ucsi_stm32g0_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ucsi_stm32g0 *g0; + int ret; + + g0 = devm_kzalloc(dev, sizeof(*g0), GFP_KERNEL); + if (!g0) + return -ENOMEM; + + g0->dev = dev; + g0->client = client; + init_completion(&g0->complete); + i2c_set_clientdata(client, g0); + + g0->ucsi = ucsi_create(dev, &ucsi_stm32g0_ops); + if (IS_ERR(g0->ucsi)) + return PTR_ERR(g0->ucsi); + + ucsi_set_drvdata(g0->ucsi, g0); + + /* Request alert interrupt */ + ret = request_threaded_irq(client->irq, NULL, ucsi_stm32g0_irq_handler, IRQF_ONESHOT, + dev_name(&client->dev), g0); + if (ret) { + dev_err_probe(dev, ret, "request IRQ failed\n"); + goto destroy; + } + + ret = ucsi_register(g0->ucsi); + if (ret) { + dev_err_probe(dev, ret, "ucsi_register failed\n"); + goto freeirq; + } + + return 0; + +freeirq: + free_irq(client->irq, g0); +destroy: + ucsi_destroy(g0->ucsi); + + return ret; +} + +static int ucsi_stm32g0_remove(struct i2c_client *client) +{ + struct ucsi_stm32g0 *g0 = i2c_get_clientdata(client); + + ucsi_unregister(g0->ucsi); + free_irq(client->irq, g0); + ucsi_destroy(g0->ucsi); + + return 0; +} + +static const struct of_device_id __maybe_unused ucsi_stm32g0_typec_of_match[] = { + { .compatible = "st,stm32g0-typec" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ucsi_stm32g0_typec_of_match); + +static const struct i2c_device_id ucsi_stm32g0_typec_i2c_devid[] = { + {"stm32g0-typec", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ucsi_stm32g0_typec_i2c_devid); + +static struct i2c_driver ucsi_stm32g0_i2c_driver = { + .driver = { + .name = "ucsi-stm32g0-i2c", + .of_match_table = of_match_ptr(ucsi_stm32g0_typec_of_match), + }, + .probe = ucsi_stm32g0_probe, + .remove = ucsi_stm32g0_remove, + .id_table = ucsi_stm32g0_typec_i2c_devid +}; +module_i2c_driver(ucsi_stm32g0_i2c_driver); + +MODULE_AUTHOR("Fabrice Gasnier "); +MODULE_DESCRIPTION("STMicroelectronics STM32G0 Type-C controller"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:ucsi-stm32g0"); From patchwork Mon Jul 11 12:01:21 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabrice Gasnier X-Patchwork-Id: 589749 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 501A1CCA483 for ; Mon, 11 Jul 2022 12:01:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230404AbiGKMBx (ORCPT ); Mon, 11 Jul 2022 08:01:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55292 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230039AbiGKMBu (ORCPT ); Mon, 11 Jul 2022 08:01:50 -0400 Received: from mx07-00178001.pphosted.com (mx07-00178001.pphosted.com [185.132.182.106]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7C4C237F8C; Mon, 11 Jul 2022 05:01:48 -0700 (PDT) Received: from pps.filterd (m0288072.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.17.1.5/8.17.1.5) with ESMTP id 26B9JJwJ001519; Mon, 11 Jul 2022 14:01:39 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=foss.st.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=selector1; bh=VpdBlelgHrVPHcER4WmVOcNdfWij204nmMAxjB06kpA=; b=EiLHj2fufN2/pApx9/joOcJTQPTCdrDxFBl+/bN3Y449zaPLY3rcDZXkO7CIq8mfRHuw NkcTh5r6Tu5BvJpOHx+szDx9RqYZBBpfjBP0gUCqBPYx5fV0cA9O0Ieqi3K/dEke1AZb Ws2ZqvtgwN4jjDY4eS4eQKClEH5crWV9ENGu1qOErghYKNDSxzo3omYq7vyYPSFKIaoj XDhGIO88TGocwVRx3G9urPtYdTPvv012OUtbCPaViXOA2MNDoYYQKc9KH3J/cLi2CfLi fsMkm65PvCt5Dm2PtRutlYgvUEj11cZJV0LVGXiQEIFq0Sx4iCqyiTZvQ6sD9hwmU914 YQ== Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-00178001.pphosted.com (PPS) with ESMTPS id 3h6y3mu6g0-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 11 Jul 2022 14:01:39 +0200 Received: from euls16034.sgp.st.com (euls16034.sgp.st.com [10.75.44.20]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 1D7FF100034; Mon, 11 Jul 2022 14:01:39 +0200 (CEST) Received: from Webmail-eu.st.com (shfdag1node2.st.com [10.75.129.70]) by euls16034.sgp.st.com (STMicroelectronics) with ESMTP id 185D221ED2B; Mon, 11 Jul 2022 14:01:39 +0200 (CEST) Received: from localhost (10.75.127.48) by SHFDAG1NODE2.st.com (10.75.129.70) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.1.2308.20; Mon, 11 Jul 2022 14:01:38 +0200 From: Fabrice Gasnier To: , , , CC: , , , , , , , Subject: [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support Date: Mon, 11 Jul 2022 14:01:21 +0200 Message-ID: <20220711120122.25804-4-fabrice.gasnier@foss.st.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> References: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> MIME-Version: 1.0 X-Originating-IP: [10.75.127.48] X-ClientProxiedBy: SFHDAG2NODE3.st.com (10.75.127.6) To SHFDAG1NODE2.st.com (10.75.129.70) X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.205,Aquarius:18.0.883,Hydra:6.0.517,FMLib:17.11.122.1 definitions=2022-07-11_17,2022-07-08_01,2022-06-22_01 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org STM32G0 comes with STM32 bootloader in its system memory. Add support for some I2C bootloader commands as described in application notes AN2606 and AN4221, to enable STM32G0 UCSI firmware update. Upon probing, the driver needs to know the STM32G0 state: - In bootloader mode, STM32 G0 answers at i2c addr 0x51. - In running mode, STM32 G0 firmware may answer at two address. - The main address specified in DT is used for UCSI. - 0x51 addr can be re-used for FW controls like getting software version or jump to booloader request. So probe using the main firmware i2c address first, before attempting bootloader address (e.g. check for blank, erased or previously aborted firmware update). Signed-off-by: Fabrice Gasnier Acked-by: Heikki Krogerus --- Changes in V2: - use kmalloc instead of kzalloc (update since Christophe's remarks on previous patch) --- drivers/usb/typec/ucsi/ucsi_stm32g0.c | 533 +++++++++++++++++++++++++- 1 file changed, 520 insertions(+), 13 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c index bb5271bf7cdcf..b1d891c9a92c0 100644 --- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -6,21 +6,324 @@ * Author: Fabrice Gasnier . */ +#include +#include #include #include #include #include +#include #include "ucsi.h" +/* STM32G0 I2C bootloader addr: 0b1010001x (See AN2606) */ +#define STM32G0_I2C_BL_ADDR (0xa2 >> 1) + +/* STM32G0 I2C bootloader max data size */ +#define STM32G0_I2C_BL_SZ 256 + +/* STM32 I2C bootloader commands (See AN4221) */ +#define STM32_CMD_GVR 0x01 /* Gets the bootloader version */ +#define STM32_CMD_GVR_LEN 1 +#define STM32_CMD_RM 0x11 /* Reag memory */ +#define STM32_CMD_WM 0x31 /* Write memory */ +#define STM32_CMD_ADDR_LEN 5 /* Address len for go, mem write... */ +#define STM32_CMD_ERASE 0x44 /* Erase page, bank or all */ +#define STM32_CMD_ERASE_SPECIAL_LEN 3 +#define STM32_CMD_GLOBAL_MASS_ERASE 0xffff /* All-bank erase */ + +/* STM32 I2C bootloader answer status */ +#define STM32G0_I2C_BL_ACK 0x79 +#define STM32G0_I2C_BL_NACK 0x1f +#define STM32G0_I2C_BL_BUSY 0x76 + +/* STM32G0 flash definitions */ +#define STM32G0_USER_OPTION_BYTES 0x1fff7800 +#define STM32G0_USER_OB_NBOOT0 BIT(26) +#define STM32G0_USER_OB_NBOOT_SEL BIT(24) +#define STM32G0_USER_OB_BOOT_MAIN (STM32G0_USER_OB_NBOOT0 | STM32G0_USER_OB_NBOOT_SEL) +#define STM32G0_MAIN_MEM_ADDR 0x08000000 + +/* STM32 Firmware definitions: additional commands */ +#define STM32G0_FW_GETVER 0x00 /* Gets the firmware version */ +#define STM32G0_FW_GETVER_LEN 4 +#define STM32G0_FW_RSTGOBL 0x21 /* Reset and go to bootloader */ +#define STM32G0_FW_KEYWORD 0xa56959a6 + +/* ucsi_stm32g0_fw_info located at the end of the firmware */ +struct ucsi_stm32g0_fw_info { + u32 version; + u32 keyword; +}; + struct ucsi_stm32g0 { struct i2c_client *client; + struct i2c_client *i2c_bl; + bool in_bootloader; + u8 bl_version; struct completion complete; struct device *dev; unsigned long flags; + const char *fw_name; struct ucsi *ucsi; }; +/* + * Bootloader commands helpers: + * - send command (2 bytes) + * - check ack + * Then either: + * - receive data + * - receive data + check ack + * - send data + check ack + * These operations depends on the command and have various length. + */ +static int ucsi_stm32g0_bl_check_ack(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + unsigned char ack; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = 1, + .buf = &ack, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl ack (%02x), error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + /* The 'ack' byte should contain bootloader answer: ack/nack/busy */ + switch (ack) { + case STM32G0_I2C_BL_ACK: + return 0; + case STM32G0_I2C_BL_NACK: + return -ENOENT; + case STM32G0_I2C_BL_BUSY: + return -EBUSY; + default: + dev_err(g0->dev, "i2c bl ack (%02x), invalid byte: %02x\n", + client->addr, ack); + return -EINVAL; + } +} + +static int ucsi_stm32g0_bl_cmd_check_ack(struct ucsi *ucsi, unsigned int cmd, bool check_ack) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + unsigned char buf[2]; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + }, + }; + int ret; + + /* + * Send STM32 bootloader command format is two bytes: + * - command code + * - XOR'ed command code + */ + buf[0] = cmd; + buf[1] = cmd ^ 0xff; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_dbg(g0->dev, "i2c bl cmd %d (%02x), error: %d\n", cmd, client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + if (check_ack) + return ucsi_stm32g0_bl_check_ack(ucsi); + + return 0; +} + +static int ucsi_stm32g0_bl_cmd(struct ucsi *ucsi, unsigned int cmd) +{ + return ucsi_stm32g0_bl_cmd_check_ack(ucsi, cmd, true); +} + +static int ucsi_stm32g0_bl_rcv_check_ack(struct ucsi *ucsi, void *data, size_t len, bool check_ack) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = data, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl rcv %02x, error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + if (check_ack) + return ucsi_stm32g0_bl_check_ack(ucsi); + + return 0; +} + +static int ucsi_stm32g0_bl_rcv(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_check_ack(ucsi, data, len, true); +} + +static int ucsi_stm32g0_bl_rcv_woack(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_check_ack(ucsi, data, len, false); +} + +static int ucsi_stm32g0_bl_send(struct ucsi *ucsi, void *data, size_t len) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->i2c_bl; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = len, + .buf = data, + }, + }; + int ret; + + ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + dev_err(g0->dev, "i2c bl send %02x, error: %d\n", client->addr, ret); + + return ret < 0 ? ret : -EIO; + } + + return ucsi_stm32g0_bl_check_ack(ucsi); +} + +/* Bootloader commands */ +static int ucsi_stm32g0_bl_get_version(struct ucsi *ucsi, u8 *bl_version) +{ + int ret; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_GVR); + if (ret) + return ret; + + return ucsi_stm32g0_bl_rcv(ucsi, bl_version, STM32_CMD_GVR_LEN); +} + +static int ucsi_stm32g0_bl_send_addr(struct ucsi *ucsi, u32 addr) +{ + u8 data8[STM32_CMD_ADDR_LEN]; + + /* Address format: 4 bytes addr (MSB first) + XOR'ed addr bytes */ + put_unaligned_be32(addr, data8); + data8[4] = data8[0] ^ data8[1] ^ data8[2] ^ data8[3]; + + return ucsi_stm32g0_bl_send(ucsi, data8, STM32_CMD_ADDR_LEN); +} + +static int ucsi_stm32g0_bl_global_mass_erase(struct ucsi *ucsi) +{ + u8 data8[4]; + u16 *data16 = (u16 *)&data8[0]; + int ret; + + data16[0] = STM32_CMD_GLOBAL_MASS_ERASE; + data8[2] = data8[0] ^ data8[1]; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_ERASE); + if (ret) + return ret; + + return ucsi_stm32g0_bl_send(ucsi, data8, STM32_CMD_ERASE_SPECIAL_LEN); +} + +static int ucsi_stm32g0_bl_write(struct ucsi *ucsi, u32 addr, const void *data, size_t len) +{ + u8 *data8; + int i, ret; + + if (!len || len > STM32G0_I2C_BL_SZ) + return -EINVAL; + + /* Write memory: len bytes -1, data up to 256 bytes + XOR'ed bytes */ + data8 = kmalloc(STM32G0_I2C_BL_SZ + 2, GFP_KERNEL); + if (!data8) + return -ENOMEM; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_WM); + if (ret) + goto free; + + ret = ucsi_stm32g0_bl_send_addr(ucsi, addr); + if (ret) + goto free; + + data8[0] = len - 1; + memcpy(data8 + 1, data, len); + data8[len + 1] = data8[0]; + for (i = 1; i <= len; i++) + data8[len + 1] ^= data8[i]; + + ret = ucsi_stm32g0_bl_send(ucsi, data8, len + 2); +free: + kfree(data8); + + return ret; +} + +static int ucsi_stm32g0_bl_read(struct ucsi *ucsi, u32 addr, void *data, size_t len) +{ + int ret; + + if (!len || len > STM32G0_I2C_BL_SZ) + return -EINVAL; + + ret = ucsi_stm32g0_bl_cmd(ucsi, STM32_CMD_RM); + if (ret) + return ret; + + ret = ucsi_stm32g0_bl_send_addr(ucsi, addr); + if (ret) + return ret; + + ret = ucsi_stm32g0_bl_cmd(ucsi, len - 1); + if (ret) + return ret; + + return ucsi_stm32g0_bl_rcv_woack(ucsi, data, len); +} + +/* Firmware commands (the same address as the bootloader) */ +static int ucsi_stm32g0_fw_cmd(struct ucsi *ucsi, unsigned int cmd) +{ + return ucsi_stm32g0_bl_cmd_check_ack(ucsi, cmd, false); +} + +static int ucsi_stm32g0_fw_rcv(struct ucsi *ucsi, void *data, size_t len) +{ + return ucsi_stm32g0_bl_rcv_woack(ucsi, data, len); +} + +/* UCSI ops */ static int ucsi_stm32g0_read(struct ucsi *ucsi, unsigned int offset, void *val, size_t len) { struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); @@ -133,6 +436,191 @@ static const struct ucsi_operations ucsi_stm32g0_ops = { .async_write = ucsi_stm32g0_async_write, }; +static int ucsi_stm32g0_register(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + int ret; + + /* Request alert interrupt */ + ret = request_threaded_irq(client->irq, NULL, ucsi_stm32g0_irq_handler, IRQF_ONESHOT, + dev_name(g0->dev), g0); + if (ret) { + dev_err(g0->dev, "request IRQ failed: %d\n", ret); + return ret; + } + + ret = ucsi_register(ucsi); + if (ret) { + dev_err_probe(g0->dev, ret, "ucsi_register failed\n"); + free_irq(client->irq, g0); + return ret; + } + + return 0; +} + +static void ucsi_stm32g0_unregister(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + struct i2c_client *client = g0->client; + + ucsi_unregister(ucsi); + free_irq(client->irq, g0); +} + +static void ucsi_stm32g0_fw_cb(const struct firmware *fw, void *context) +{ + struct ucsi_stm32g0 *g0; + const u8 *data, *end; + const struct ucsi_stm32g0_fw_info *fw_info; + u32 addr = STM32G0_MAIN_MEM_ADDR, ob, fw_version; + int ret, size; + + if (!context) + return; + + g0 = ucsi_get_drvdata(context); + + if (!fw) + goto fw_release; + + fw_info = (struct ucsi_stm32g0_fw_info *)(fw->data + fw->size - sizeof(*fw_info)); + + if (!g0->in_bootloader) { + /* Read running firmware version */ + ret = ucsi_stm32g0_fw_cmd(g0->ucsi, STM32G0_FW_GETVER); + if (ret) { + dev_err(g0->dev, "Get version cmd failed %d\n", ret); + goto fw_release; + } + ret = ucsi_stm32g0_fw_rcv(g0->ucsi, &fw_version, + STM32G0_FW_GETVER_LEN); + if (ret) { + dev_err(g0->dev, "Get version failed %d\n", ret); + goto fw_release; + } + + /* Sanity check on keyword and firmware version */ + if (fw_info->keyword != STM32G0_FW_KEYWORD || fw_info->version == fw_version) + goto fw_release; + + dev_info(g0->dev, "Flashing FW: %08x (%08x cur)\n", fw_info->version, fw_version); + + /* Switch to bootloader mode */ + ucsi_stm32g0_unregister(g0->ucsi); + ret = ucsi_stm32g0_fw_cmd(g0->ucsi, STM32G0_FW_RSTGOBL); + if (ret) { + dev_err(g0->dev, "bootloader cmd failed %d\n", ret); + goto fw_release; + } + g0->in_bootloader = true; + + /* STM32G0 reboot delay */ + msleep(100); + } + + ret = ucsi_stm32g0_bl_global_mass_erase(g0->ucsi); + if (ret) { + dev_err(g0->dev, "Erase failed %d\n", ret); + goto fw_release; + } + + data = fw->data; + end = fw->data + fw->size; + while (data < end) { + if ((end - data) < STM32G0_I2C_BL_SZ) + size = end - data; + else + size = STM32G0_I2C_BL_SZ; + + ret = ucsi_stm32g0_bl_write(g0->ucsi, addr, data, size); + if (ret) { + dev_err(g0->dev, "Write failed %d\n", ret); + goto fw_release; + } + addr += size; + data += size; + } + + dev_dbg(g0->dev, "Configure to boot from main flash\n"); + + ret = ucsi_stm32g0_bl_read(g0->ucsi, STM32G0_USER_OPTION_BYTES, &ob, sizeof(ob)); + if (ret) { + dev_err(g0->dev, "read user option bytes failed %d\n", ret); + goto fw_release; + } + + dev_dbg(g0->dev, "STM32G0_USER_OPTION_BYTES 0x%08x\n", ob); + + /* Configure user option bytes to boot from main flash next time */ + ob |= STM32G0_USER_OB_BOOT_MAIN; + + /* Writing option bytes will also reset G0 for updates to be loaded */ + ret = ucsi_stm32g0_bl_write(g0->ucsi, STM32G0_USER_OPTION_BYTES, &ob, sizeof(ob)); + if (ret) { + dev_err(g0->dev, "write user option bytes failed %d\n", ret); + goto fw_release; + } + + dev_info(g0->dev, "Starting, option bytes:0x%08x\n", ob); + + /* STM32G0 FW boot delay */ + msleep(500); + + /* Register UCSI interface */ + if (!ucsi_stm32g0_register(g0->ucsi)) + g0->in_bootloader = false; + +fw_release: + release_firmware(fw); +} + +static int ucsi_stm32g0_probe_bootloader(struct ucsi *ucsi) +{ + struct ucsi_stm32g0 *g0 = ucsi_get_drvdata(ucsi); + int ret; + u16 ucsi_version; + + /* firmware-name is optional */ + if (device_property_present(g0->dev, "firmware-name")) { + ret = device_property_read_string(g0->dev, "firmware-name", &g0->fw_name); + if (ret < 0) + return dev_err_probe(g0->dev, ret, "Error reading firmware-name\n"); + } + + if (g0->fw_name) { + /* STM32G0 in bootloader mode communicates at reserved address 0x51 */ + g0->i2c_bl = i2c_new_dummy_device(g0->client->adapter, STM32G0_I2C_BL_ADDR); + if (IS_ERR(g0->i2c_bl)) { + ret = dev_err_probe(g0->dev, PTR_ERR(g0->i2c_bl), + "Failed to register booloader I2C address\n"); + return ret; + } + } + + /* + * Try to guess if the STM32G0 is running a UCSI firmware. First probe the UCSI FW at its + * i2c address. Fallback to bootloader i2c address only if firmware-name is specified. + */ + ret = ucsi_stm32g0_read(ucsi, UCSI_VERSION, &ucsi_version, sizeof(ucsi_version)); + if (!ret || !g0->fw_name) + return ret; + + /* Speculatively read the bootloader version that has a known length. */ + ret = ucsi_stm32g0_bl_get_version(ucsi, &g0->bl_version); + if (ret < 0) { + i2c_unregister_device(g0->i2c_bl); + return ret; + } + + /* Device in bootloader mode */ + g0->in_bootloader = true; + dev_info(g0->dev, "Bootloader Version 0x%02x\n", g0->bl_version); + + return 0; +} + static int ucsi_stm32g0_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; @@ -154,24 +642,41 @@ static int ucsi_stm32g0_probe(struct i2c_client *client, const struct i2c_device ucsi_set_drvdata(g0->ucsi, g0); - /* Request alert interrupt */ - ret = request_threaded_irq(client->irq, NULL, ucsi_stm32g0_irq_handler, IRQF_ONESHOT, - dev_name(&client->dev), g0); - if (ret) { - dev_err_probe(dev, ret, "request IRQ failed\n"); + ret = ucsi_stm32g0_probe_bootloader(g0->ucsi); + if (ret < 0) goto destroy; + + /* + * Don't register in bootloader mode: wait for the firmware to be loaded and started before + * registering UCSI device. + */ + if (!g0->in_bootloader) { + ret = ucsi_stm32g0_register(g0->ucsi); + if (ret < 0) + goto freei2c; } - ret = ucsi_register(g0->ucsi); - if (ret) { - dev_err_probe(dev, ret, "ucsi_register failed\n"); - goto freeirq; + if (g0->fw_name) { + /* + * Asynchronously flash (e.g. bootloader mode) or update the running firmware, + * not to hang the boot process + */ + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, g0->fw_name, g0->dev, + GFP_KERNEL, g0->ucsi, ucsi_stm32g0_fw_cb); + if (ret < 0) { + dev_err_probe(dev, ret, "firmware request failed\n"); + goto unregister; + } } return 0; -freeirq: - free_irq(client->irq, g0); +unregister: + if (!g0->in_bootloader) + ucsi_stm32g0_unregister(g0->ucsi); +freei2c: + if (g0->fw_name) + i2c_unregister_device(g0->i2c_bl); destroy: ucsi_destroy(g0->ucsi); @@ -182,8 +687,10 @@ static int ucsi_stm32g0_remove(struct i2c_client *client) { struct ucsi_stm32g0 *g0 = i2c_get_clientdata(client); - ucsi_unregister(g0->ucsi); - free_irq(client->irq, g0); + if (!g0->in_bootloader) + ucsi_stm32g0_unregister(g0->ucsi); + if (g0->fw_name) + i2c_unregister_device(g0->i2c_bl); ucsi_destroy(g0->ucsi); return 0; From patchwork Mon Jul 11 12:01:22 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fabrice Gasnier X-Patchwork-Id: 590296 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id BC3D1C43334 for ; Mon, 11 Jul 2022 12:01:56 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230414AbiGKMBz (ORCPT ); Mon, 11 Jul 2022 08:01:55 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55324 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230313AbiGKMBv (ORCPT ); Mon, 11 Jul 2022 08:01:51 -0400 Received: from mx07-00178001.pphosted.com (mx07-00178001.pphosted.com [185.132.182.106]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 48D3137F9A; Mon, 11 Jul 2022 05:01:50 -0700 (PDT) Received: from pps.filterd (m0288072.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.17.1.5/8.17.1.5) with ESMTP id 26B7rbCu031037; Mon, 11 Jul 2022 14:01:42 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=foss.st.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=selector1; bh=0D+QICwuMJjMUowKXseLMhNC9cUJEgGQwNXk/NCdjUw=; b=UivEsLwxVhimION6NOwNAtbhDi8OL/LYzSHUWU9t0R7ZR6ESp6XW9LDyxJW7TmdzhBZR jR9nKRG3q04PfXzUZ1aWBA7Qnc7gZba7InffVwnbMeVWJen6lePQdXhlIyo83ajQbONg LRwtZaQlIiSqnLD/PiugkXeuYV3uKAGX8qlX/wayd+JD2DlpZihZcHstjtvHSX+o/Ya0 VS1SP/9YSHmLgq4+ysD7WlKuVyVZ9y4ROZkUo5Ho5EG9EHDnOUIBCqgRbCGd4OeqvxQI pThaCzl7vpceT5YsLoRf4cAMckON1LK2lP2h+/QFXz4aYi1djDIcNqM8StxrhW8vgvmf kA== Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx07-00178001.pphosted.com (PPS) with ESMTPS id 3h6y3mu6g6-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Mon, 11 Jul 2022 14:01:42 +0200 Received: from euls16034.sgp.st.com (euls16034.sgp.st.com [10.75.44.20]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 9F9B610002A; Mon, 11 Jul 2022 14:01:41 +0200 (CEST) Received: from Webmail-eu.st.com (shfdag1node2.st.com [10.75.129.70]) by euls16034.sgp.st.com (STMicroelectronics) with ESMTP id 9A5F721ED2B; Mon, 11 Jul 2022 14:01:41 +0200 (CEST) Received: from localhost (10.75.127.48) by SHFDAG1NODE2.st.com (10.75.129.70) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256) id 15.1.2308.20; Mon, 11 Jul 2022 14:01:39 +0200 From: Fabrice Gasnier To: , , , CC: , , , , , , , Subject: [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management Date: Mon, 11 Jul 2022 14:01:22 +0200 Message-ID: <20220711120122.25804-5-fabrice.gasnier@foss.st.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> References: <20220711120122.25804-1-fabrice.gasnier@foss.st.com> MIME-Version: 1.0 X-Originating-IP: [10.75.127.48] X-ClientProxiedBy: SFHDAG2NODE3.st.com (10.75.127.6) To SHFDAG1NODE2.st.com (10.75.129.70) X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.205,Aquarius:18.0.883,Hydra:6.0.517,FMLib:17.11.122.1 definitions=2022-07-11_17,2022-07-08_01,2022-06-22_01 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Type-C connector can be used as a wakeup source (typically to detect changes on the port, attach or detach...). Add suspend / resume routines to enable wake irqs, and signal a wakeup event in case the IRQ has fired while in suspend. The i2c core is doing the necessary initialization when the "wakeup-source" flag is provided. Note: the interrupt handler shouldn't be called before the i2c bus resumes. So, the interrupts are disabled during suspend period, and re-enabled upon resume, to avoid i2c transfer while suspended, from the irq handler. Signed-off-by: Fabrice Gasnier --- drivers/usb/typec/ucsi/ucsi_stm32g0.c | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_stm32g0.c b/drivers/usb/typec/ucsi/ucsi_stm32g0.c index b1d891c9a92c0..061551d464f12 100644 --- a/drivers/usb/typec/ucsi/ucsi_stm32g0.c +++ b/drivers/usb/typec/ucsi/ucsi_stm32g0.c @@ -66,6 +66,8 @@ struct ucsi_stm32g0 { unsigned long flags; const char *fw_name; struct ucsi *ucsi; + bool suspended; + bool wakeup_event; }; /* @@ -416,6 +418,9 @@ static irqreturn_t ucsi_stm32g0_irq_handler(int irq, void *data) u32 cci; int ret; + if (g0->suspended) + g0->wakeup_event = true; + ret = ucsi_stm32g0_read(g0->ucsi, UCSI_CCI, &cci, sizeof(cci)); if (ret) return IRQ_NONE; @@ -696,6 +701,52 @@ static int ucsi_stm32g0_remove(struct i2c_client *client) return 0; } +static int ucsi_stm32g0_suspend(struct device *dev) +{ + struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); + struct i2c_client *client = g0->client; + + if (g0->in_bootloader) + return 0; + + /* Keep the interrupt disabled until the i2c bus has been resumed */ + disable_irq(client->irq); + + g0->suspended = true; + g0->wakeup_event = false; + + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + enable_irq_wake(client->irq); + + return 0; +} + +static int ucsi_stm32g0_resume(struct device *dev) +{ + struct ucsi_stm32g0 *g0 = dev_get_drvdata(dev); + struct i2c_client *client = g0->client; + + if (g0->in_bootloader) + return 0; + + if (device_may_wakeup(dev) || device_wakeup_path(dev)) + disable_irq_wake(client->irq); + + enable_irq(client->irq); + + /* Enforce any pending handler gets called to signal a wakeup_event */ + synchronize_irq(client->irq); + + if (g0->wakeup_event) + pm_wakeup_event(g0->dev, 0); + + g0->suspended = false; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_stm32g0_pm_ops, ucsi_stm32g0_suspend, ucsi_stm32g0_resume); + static const struct of_device_id __maybe_unused ucsi_stm32g0_typec_of_match[] = { { .compatible = "st,stm32g0-typec" }, {}, @@ -712,6 +763,7 @@ static struct i2c_driver ucsi_stm32g0_i2c_driver = { .driver = { .name = "ucsi-stm32g0-i2c", .of_match_table = of_match_ptr(ucsi_stm32g0_typec_of_match), + .pm = pm_sleep_ptr(&ucsi_stm32g0_pm_ops), }, .probe = ucsi_stm32g0_probe, .remove = ucsi_stm32g0_remove,