All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0
@ 2022-07-11 12:01 Fabrice Gasnier
  2022-07-11 12:01 ` [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller Fabrice Gasnier
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-11 12:01 UTC (permalink / raw)
  To: krzysztof.kozlowski+dt, robh+dt, heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue, fabrice.gasnier

STM32G0 provides an integrated USB Type-C and power delivery interface [1].
It can be programmed with a firmware [2] to act as a PPM. Currently it
implements UCSI protocol over I2C interface. A GPIO is used as an interrupt
line.

This series adds a driver to support it, including:
- dt-bindings documentation
- optional STM32G0 firmware control and update, over a secondary I2C address
- power management

[1] https://wiki.st.com/stm32mcu/wiki/Introduction_to_USB_Power_Delivery_with_STM32
[2] https://github.com/STMicroelectronics/x-cube-ucsi

Changes in v2:
- Krzysztof's review comments on dt-bindings: update commit message, use ports,
  use unevaluatedProperties: false for usb-connector schema, define maxItems
  for power-domains, adopt generic node names, remove quotes.
- Christophe's comments on driver:
  use kmalloc instead of kzalloc
  Use-after-free of buf: directly print the offset

Fabrice Gasnier (4):
  dt-bindings: usb: typec: add bindings for stm32g0 controller
  usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller
  usb: typec: ucsi: stm32g0: add bootloader support
  usb: typec: ucsi: stm32g0: add support for power management

 .../bindings/usb/st,typec-stm32g0.yaml        |  90 ++
 drivers/usb/typec/ucsi/Kconfig                |  10 +
 drivers/usb/typec/ucsi/Makefile               |   1 +
 drivers/usb/typec/ucsi/ucsi_stm32g0.c         | 777 ++++++++++++++++++
 4 files changed, 878 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/st,typec-stm32g0.yaml
 create mode 100644 drivers/usb/typec/ucsi/ucsi_stm32g0.c

-- 
2.25.1


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller
  2022-07-11 12:01 [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0 Fabrice Gasnier
@ 2022-07-11 12:01 ` Fabrice Gasnier
  2022-07-12  8:42   ` Krzysztof Kozlowski
  2022-07-11 12:01 ` [PATCH v2 2/4] usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller Fabrice Gasnier
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-11 12:01 UTC (permalink / raw)
  To: krzysztof.kozlowski+dt, robh+dt, heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue, fabrice.gasnier

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 <fabrice.gasnier@foss.st.com>
---
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 <fabrice.gasnier@foss.st.com>
+
+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 <dt-bindings/interrupt-controller/irq.h>
+    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>;
+        };
+      };
+    };
+...
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 2/4] usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller
  2022-07-11 12:01 [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0 Fabrice Gasnier
  2022-07-11 12:01 ` [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller Fabrice Gasnier
@ 2022-07-11 12:01 ` Fabrice Gasnier
  2022-07-12  8:35   ` Heikki Krogerus
  2022-07-11 12:01 ` [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support Fabrice Gasnier
  2022-07-11 12:01 ` [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management Fabrice Gasnier
  3 siblings, 1 reply; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-11 12:01 UTC (permalink / raw)
  To: krzysztof.kozlowski+dt, robh+dt, heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue, fabrice.gasnier

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 <fabrice.gasnier@foss.st.com>
---
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 <fabrice.gasnier@foss.st.com>.
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#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	= &reg,
+		},
+		{
+			.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 <fabrice.gasnier@foss.st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STM32G0 Type-C controller");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("platform:ucsi-stm32g0");
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support
  2022-07-11 12:01 [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0 Fabrice Gasnier
  2022-07-11 12:01 ` [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller Fabrice Gasnier
  2022-07-11 12:01 ` [PATCH v2 2/4] usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller Fabrice Gasnier
@ 2022-07-11 12:01 ` Fabrice Gasnier
  2022-07-12  8:38   ` Heikki Krogerus
  2022-07-11 12:01 ` [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management Fabrice Gasnier
  3 siblings, 1 reply; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-11 12:01 UTC (permalink / raw)
  To: krzysztof.kozlowski+dt, robh+dt, heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue, fabrice.gasnier

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 <fabrice.gasnier@foss.st.com>
---
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 <fabrice.gasnier@foss.st.com>.
  */
 
+#include <linux/delay.h>
+#include <linux/firmware.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <asm/unaligned.h>
 
 #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;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management
  2022-07-11 12:01 [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0 Fabrice Gasnier
                   ` (2 preceding siblings ...)
  2022-07-11 12:01 ` [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support Fabrice Gasnier
@ 2022-07-11 12:01 ` Fabrice Gasnier
  2022-07-12  8:56   ` Heikki Krogerus
  3 siblings, 1 reply; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-11 12:01 UTC (permalink / raw)
  To: krzysztof.kozlowski+dt, robh+dt, heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue, fabrice.gasnier

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 <fabrice.gasnier@foss.st.com>
---
 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,
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/4] usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller
  2022-07-11 12:01 ` [PATCH v2 2/4] usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller Fabrice Gasnier
@ 2022-07-12  8:35   ` Heikki Krogerus
  0 siblings, 0 replies; 12+ messages in thread
From: Heikki Krogerus @ 2022-07-12  8:35 UTC (permalink / raw)
  To: Fabrice Gasnier
  Cc: krzysztof.kozlowski+dt, robh+dt, gregkh, christophe.jaillet,
	devicetree, linux-usb, linux-kernel, linux-stm32,
	amelie.delaunay, alexandre.torgue

Mon, Jul 11, 2022 at 02:01:20PM +0200, Fabrice Gasnier kirjoitti:
> 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 <fabrice.gasnier@foss.st.com>

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
> 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 <fabrice.gasnier@foss.st.com>.
> + */
> +
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +#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	= &reg,
> +		},
> +		{
> +			.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 <fabrice.gasnier@foss.st.com>");
> +MODULE_DESCRIPTION("STMicroelectronics STM32G0 Type-C controller");
> +MODULE_LICENSE("Dual BSD/GPL");
> +MODULE_ALIAS("platform:ucsi-stm32g0");
> -- 
> 2.25.1

-- 
heikki

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support
  2022-07-11 12:01 ` [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support Fabrice Gasnier
@ 2022-07-12  8:38   ` Heikki Krogerus
  0 siblings, 0 replies; 12+ messages in thread
From: Heikki Krogerus @ 2022-07-12  8:38 UTC (permalink / raw)
  To: Fabrice Gasnier
  Cc: krzysztof.kozlowski+dt, robh+dt, gregkh, christophe.jaillet,
	devicetree, linux-usb, linux-kernel, linux-stm32,
	amelie.delaunay, alexandre.torgue

Mon, Jul 11, 2022 at 02:01:21PM +0200, Fabrice Gasnier kirjoitti:
> 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 <fabrice.gasnier@foss.st.com>

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
> 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 <fabrice.gasnier@foss.st.com>.
>   */
>  
> +#include <linux/delay.h>
> +#include <linux/firmware.h>
>  #include <linux/i2c.h>
>  #include <linux/interrupt.h>
>  #include <linux/module.h>
>  #include <linux/platform_device.h>
> +#include <asm/unaligned.h>
>  
>  #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;
> -- 
> 2.25.1

-- 
heikki

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller
  2022-07-11 12:01 ` [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller Fabrice Gasnier
@ 2022-07-12  8:42   ` Krzysztof Kozlowski
  2022-07-12 10:18     ` Fabrice Gasnier
  0 siblings, 1 reply; 12+ messages in thread
From: Krzysztof Kozlowski @ 2022-07-12  8:42 UTC (permalink / raw)
  To: Fabrice Gasnier, krzysztof.kozlowski+dt, robh+dt,
	heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue

On 11/07/2022 14:01, Fabrice Gasnier wrote:
> 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 <fabrice.gasnier@foss.st.com>

Thank you for your patch. There is something to discuss/improve.

> ---
> 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 <fabrice.gasnier@foss.st.com>
> +
> +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

Isn't connector a required property? I would assume the device does not
make much sense to operate without it.

What about firmware-name? Do you expect hardware to work fine without it
(default firmware?)?


Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management
  2022-07-11 12:01 ` [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management Fabrice Gasnier
@ 2022-07-12  8:56   ` Heikki Krogerus
  2022-07-12 10:01     ` Fabrice Gasnier
  0 siblings, 1 reply; 12+ messages in thread
From: Heikki Krogerus @ 2022-07-12  8:56 UTC (permalink / raw)
  To: Fabrice Gasnier
  Cc: krzysztof.kozlowski+dt, robh+dt, gregkh, christophe.jaillet,
	devicetree, linux-usb, linux-kernel, linux-stm32,
	amelie.delaunay, alexandre.torgue

Hi,

Mon, Jul 11, 2022 at 02:01:22PM +0200, Fabrice Gasnier kirjoitti:
> 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 <fabrice.gasnier@foss.st.com>

Does this really need a separate patch? Does the support depend on the
second patch somehow?

If not, then just merge this into the first patch. That
g0->in_bootloader check you can add in the second patch.

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
>  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,
> -- 
> 2.25.1

-- 
heikki

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management
  2022-07-12  8:56   ` Heikki Krogerus
@ 2022-07-12 10:01     ` Fabrice Gasnier
  0 siblings, 0 replies; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-12 10:01 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: krzysztof.kozlowski+dt, robh+dt, gregkh, christophe.jaillet,
	devicetree, linux-usb, linux-kernel, linux-stm32,
	amelie.delaunay, alexandre.torgue

On 7/12/22 10:56, Heikki Krogerus wrote:
> Hi,
> 
> Mon, Jul 11, 2022 at 02:01:22PM +0200, Fabrice Gasnier kirjoitti:
>> 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 <fabrice.gasnier@foss.st.com>
> 
> Does this really need a separate patch? Does the support depend on the
> second patch somehow?

Hi Heikki,

There's no dependency. I did a separate patch mainly to ease the review.

> 
> If not, then just merge this into the first patch. That
> g0->in_bootloader check you can add in the second patch.

Ok, I'll merge this into the first patches as you suggest, and add your
Acked-by.

Thanks for reviewing,
Best Regards,
Fabrice

> 
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> 
>> ---
>>  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,
>> -- 
>> 2.25.1
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller
  2022-07-12  8:42   ` Krzysztof Kozlowski
@ 2022-07-12 10:18     ` Fabrice Gasnier
  0 siblings, 0 replies; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-12 10:18 UTC (permalink / raw)
  To: Krzysztof Kozlowski, krzysztof.kozlowski+dt, robh+dt,
	heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue

On 7/12/22 10:42, Krzysztof Kozlowski wrote:
> On 11/07/2022 14:01, Fabrice Gasnier wrote:
>> 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 <fabrice.gasnier@foss.st.com>
> 
> Thank you for your patch. There is something to discuss/improve.
> 
>> ---
>> 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 <fabrice.gasnier@foss.st.com>
>> +
>> +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
> 
> Isn't connector a required property? I would assume the device does not
> make much sense to operate without it.

Hi Krzysztof,

Indeed, that's sensible. I'll add connector to the required properties.

> 
> What about firmware-name? Do you expect hardware to work fine without it
> (default firmware?)?


Basically, the default firmware could be loaded in production. The
firmware content itself may be customized to restrict the firmware
update feature. That's a pure application decision of the firmware.
There could be other means to update it too (like via CC lines, with
external tools), instead of I2C lines.

Hence, the firmware-name is made optional here.

I can update the commit message with this explanation if this clarifies it.

Thanks for reviewing,
Best Regards,
Fabrice

> 
> 
> Best regards,
> Krzysztof

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support
  2022-07-11 11:48 [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0 Fabrice Gasnier
@ 2022-07-11 11:48 ` Fabrice Gasnier
  0 siblings, 0 replies; 12+ messages in thread
From: Fabrice Gasnier @ 2022-07-11 11:48 UTC (permalink / raw)
  To: krzysztof.kozlowski+dt, robh+dt, heikki.krogerus, gregkh
  Cc: christophe.jaillet, devicetree, linux-usb, linux-kernel,
	linux-stm32, amelie.delaunay, alexandre.torgue, fabrice.gasnier

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 <fabrice.gasnier@foss.st.com>
---
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 <fabrice.gasnier@foss.st.com>.
  */
 
+#include <linux/delay.h>
+#include <linux/firmware.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
+#include <asm/unaligned.h>
 
 #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;
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2022-07-12 10:19 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-11 12:01 [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0 Fabrice Gasnier
2022-07-11 12:01 ` [PATCH v2 1/4] dt-bindings: usb: typec: add bindings for stm32g0 controller Fabrice Gasnier
2022-07-12  8:42   ` Krzysztof Kozlowski
2022-07-12 10:18     ` Fabrice Gasnier
2022-07-11 12:01 ` [PATCH v2 2/4] usb: typec: ucsi: stm32g0: add support for stm32g0 i2c controller Fabrice Gasnier
2022-07-12  8:35   ` Heikki Krogerus
2022-07-11 12:01 ` [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support Fabrice Gasnier
2022-07-12  8:38   ` Heikki Krogerus
2022-07-11 12:01 ` [PATCH v2 4/4] usb: typec: ucsi: stm32g0: add support for power management Fabrice Gasnier
2022-07-12  8:56   ` Heikki Krogerus
2022-07-12 10:01     ` Fabrice Gasnier
  -- strict thread matches above, loose matches on Subject: below --
2022-07-11 11:48 [PATCH v2 0/4] usb: typec: ucsi: add support for stm32g0 Fabrice Gasnier
2022-07-11 11:48 ` [PATCH v2 3/4] usb: typec: ucsi: stm32g0: add bootloader support Fabrice Gasnier

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.