From f74c7557ed0d321947e8bb4e9d47c1013f8b2227 Mon Sep 17 00:00:00 2001 From: Patryk Duda Date: Tue, 2 Aug 2022 17:41:28 +0200 Subject: [PATCH 01/19] platform/chrome: cros_ec_proto: Update version on GET_NEXT_EVENT failure Some EC based devices (e.g. Fingerpint MCU) can jump to RO part of the firmware (intentionally or due to device reboot). The RO part doesn't change during the device lifecycle, so it won't support newer version of EC_CMD_GET_NEXT_EVENT command. Function cros_ec_query_all() is responsible for finding maximum supported MKBP event version. It's usually called when the device is running RW part of the firmware, so the command version can be potentially higher than version supported by the RO. The problem was fixed by updating maximum supported version when the device returns EC_RES_INVALID_VERSION (mapped to -ENOPROTOOPT). That way the kernel will use highest common version supported by RO and RW. Fixes: 3300fdd630d4 ("platform/chrome: cros_ec: handle MKBP more events flag") Cc: # 5.10+ Reviewed-by: Guenter Roeck Signed-off-by: Patryk Duda Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220802154128.21175-1-pdk@semihalf.com --- drivers/platform/chrome/cros_ec_proto.c | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 05d2e8765a66..475a6dd72db6 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -773,6 +773,7 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev, u8 event_type; u32 host_event; int ret; + u32 ver_mask; /* * Default value for wake_event. @@ -794,6 +795,37 @@ int cros_ec_get_next_event(struct cros_ec_device *ec_dev, return get_keyboard_state_event(ec_dev); ret = get_next_event(ec_dev); + /* + * -ENOPROTOOPT is returned when EC returns EC_RES_INVALID_VERSION. + * This can occur when EC based device (e.g. Fingerprint MCU) jumps to + * the RO image which doesn't support newer version of the command. In + * this case we will attempt to update maximum supported version of the + * EC_CMD_GET_NEXT_EVENT. + */ + if (ret == -ENOPROTOOPT) { + dev_dbg(ec_dev->dev, + "GET_NEXT_EVENT returned invalid version error.\n"); + ret = cros_ec_get_host_command_version_mask(ec_dev, + EC_CMD_GET_NEXT_EVENT, + &ver_mask); + if (ret < 0 || ver_mask == 0) + /* + * Do not change the MKBP supported version if we can't + * obtain supported version correctly. Please note that + * calling EC_CMD_GET_NEXT_EVENT returned + * EC_RES_INVALID_VERSION which means that the command + * is present. + */ + return -ENOPROTOOPT; + + ec_dev->mkbp_event_supported = fls(ver_mask); + dev_dbg(ec_dev->dev, "MKBP support version changed to %u\n", + ec_dev->mkbp_event_supported - 1); + + /* Try to get next event with new MKBP support version set. */ + ret = get_next_event(ec_dev); + } + if (ret <= 0) return ret; From 77947238dad3b83bbe085ebcd576ea55afb70425 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 16 Aug 2022 21:48:29 +0000 Subject: [PATCH 02/19] platform/chrome: Add Type-C mux set command definitions Copy EC header definitions for the USB Type-C Mux control command from the EC code base. Also pull in "TBT_UFP_REPLY" definitions, since that is the prior entry in the enum. These headers are already present in the EC code base. [1] [1] https://chromium.googlesource.com/chromiumos/platform/ec/+/b80f85a94a423273c1638ef7b662c56931a138dd/include/ec_commands.h Signed-off-by: Prashant Malani Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220816214857.2088914-2-pmalani@chromium.org --- include/linux/platform_data/cros_ec_commands.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 8b1b795867a1..5744a2d746aa 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -5724,8 +5724,21 @@ enum typec_control_command { TYPEC_CONTROL_COMMAND_EXIT_MODES, TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, TYPEC_CONTROL_COMMAND_ENTER_MODE, + TYPEC_CONTROL_COMMAND_TBT_UFP_REPLY, + TYPEC_CONTROL_COMMAND_USB_MUX_SET, }; +/* Replies the AP may specify to the TBT EnterMode command as a UFP */ +enum typec_tbt_ufp_reply { + TYPEC_TBT_UFP_REPLY_NAK, + TYPEC_TBT_UFP_REPLY_ACK, +}; + +struct typec_usb_mux_set { + uint8_t mux_index; /* Index of the mux to set in the chain */ + uint8_t mux_flags; /* USB_PD_MUX_*-encoded USB mux state to set */ +} __ec_align1; + struct ec_params_typec_control { uint8_t port; uint8_t command; /* enum typec_control_command */ @@ -5739,6 +5752,8 @@ struct ec_params_typec_control { union { uint32_t clear_events_mask; uint8_t mode_to_enter; /* enum typec_mode */ + uint8_t tbt_ufp_reply; /* enum typec_tbt_ufp_reply */ + struct typec_usb_mux_set mux_params; uint8_t placeholder[128]; }; } __ec_align1; @@ -5817,6 +5832,9 @@ enum tcpc_cc_polarity { #define PD_STATUS_EVENT_SOP_DISC_DONE BIT(0) #define PD_STATUS_EVENT_SOP_PRIME_DISC_DONE BIT(1) #define PD_STATUS_EVENT_HARD_RESET BIT(2) +#define PD_STATUS_EVENT_DISCONNECTED BIT(3) +#define PD_STATUS_EVENT_MUX_0_SET_DONE BIT(4) +#define PD_STATUS_EVENT_MUX_1_SET_DONE BIT(5) struct ec_params_typec_status { uint8_t port; From affc804c44c8259ae53423aa3b5c20907e3a9a34 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 16 Aug 2022 21:48:30 +0000 Subject: [PATCH 03/19] platform/chrome: cros_typec_switch: Add switch driver Introduce a driver to configure USB Type-C mode switches and retimers which are controlled by the ChromeOS EC (Embedded Controller). This allows Type-C port drivers, as well as alternate mode drivers to configure their relevant mode switches and retimers according to the Type-C state they want to achieve. ACPI devices with ID GOOG001A will bind to this driver. Currently, we only register a retimer switch with a stub set function. Subsequent patches will implement the host command set functionality, and introduce mode switches. Signed-off-by: Prashant Malani Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220816214857.2088914-3-pmalani@chromium.org --- MAINTAINERS | 1 + drivers/platform/chrome/Kconfig | 11 ++ drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/cros_typec_switch.c | 169 ++++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 drivers/platform/chrome/cros_typec_switch.c diff --git a/MAINTAINERS b/MAINTAINERS index 8a5012ba6ff9..c1e4977cb1c3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4894,6 +4894,7 @@ M: Prashant Malani L: chrome-platform@lists.linux.dev S: Maintained F: drivers/platform/chrome/cros_ec_typec.c +F: drivers/platform/chrome/cros_typec_switch.c CHROMEOS EC USB PD NOTIFY DRIVER M: Prashant Malani diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index c45fb376d653..6b954c5acadb 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -265,6 +265,17 @@ config CHROMEOS_PRIVACY_SCREEN this should probably always be built into the kernel to avoid or minimize drm probe deferral. +config CROS_TYPEC_SWITCH + tristate "ChromeOS EC Type-C Switch Control" + depends on MFD_CROS_EC_DEV && TYPEC && ACPI + default MFD_CROS_EC_DEV + help + If you say Y here, you get support for configuring the ChromeOS EC Type-C + muxes and retimers. + + To compile this driver as a module, choose M here: the module will be + called cros_typec_switch. + source "drivers/platform/chrome/wilco_ec/Kconfig" # Kunit test cases diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index f7e74a845afc..2950610101f1 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o obj-$(CONFIG_CROS_EC) += cros_ec.o obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o +obj-$(CONFIG_CROS_TYPEC_SWITCH) += cros_typec_switch.o obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c new file mode 100644 index 000000000000..db773bf57899 --- /dev/null +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2022 Google LLC + * + * This driver provides the ability to configure Type-C muxes and retimers which are controlled by + * the ChromeOS EC. + */ + +#include +#include +#include +#include +#include + +#define DRV_NAME "cros-typec-switch" + +/* Handles and other relevant data required for each port's switches. */ +struct cros_typec_port { + int port_num; + struct typec_retimer *retimer; + struct cros_typec_switch_data *sdata; +}; + +/* Driver-specific data. */ +struct cros_typec_switch_data { + struct device *dev; + struct cros_ec_device *ec; + struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; +}; + +static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + return 0; +} + +static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) +{ + int i; + + for (i = 0; i < EC_USB_PD_MAX_PORTS; i++) { + if (!sdata->ports[i]) + continue; + typec_retimer_unregister(sdata->ports[i]->retimer); + } +} + +static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode) +{ + struct typec_retimer_desc retimer_desc = { + .fwnode = fwnode, + .drvdata = port, + .name = fwnode_get_name(fwnode), + .set = cros_typec_retimer_set, + }; + + port->retimer = typec_retimer_register(port->sdata->dev, &retimer_desc); + if (IS_ERR(port->retimer)) + return PTR_ERR(port->retimer); + + return 0; +} + +static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) +{ + struct cros_typec_port *port; + struct device *dev = sdata->dev; + struct fwnode_handle *fwnode; + struct acpi_device *adev; + unsigned long long index; + int nports, ret; + + nports = device_get_child_node_count(dev); + if (nports == 0) { + dev_err(dev, "No switch devices found.\n"); + return -ENODEV; + } + + device_for_each_child_node(dev, fwnode) { + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) { + ret = -ENOMEM; + goto err_switch; + } + + adev = to_acpi_device_node(fwnode); + if (!adev) { + dev_err(fwnode->dev, "Couldn't get ACPI device handle\n"); + ret = -ENODEV; + goto err_switch; + } + + ret = acpi_evaluate_integer(adev->handle, "_ADR", NULL, &index); + if (ACPI_FAILURE(ret)) { + dev_err(fwnode->dev, "_ADR wasn't evaluated\n"); + ret = -ENODATA; + goto err_switch; + } + + if (index < 0 || index >= EC_USB_PD_MAX_PORTS) { + dev_err(fwnode->dev, "Invalid port index number: %llu", index); + ret = -EINVAL; + goto err_switch; + } + port->sdata = sdata; + port->port_num = index; + sdata->ports[index] = port; + + ret = cros_typec_register_retimer(port, fwnode); + if (ret) { + dev_err(dev, "Retimer switch register failed\n"); + goto err_switch; + } + + dev_dbg(dev, "Retimer switch registered for index %llu\n", index); + } + + return 0; +err_switch: + cros_typec_unregister_switches(sdata); + return ret; +} + +static int cros_typec_switch_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_typec_switch_data *sdata; + + sdata = devm_kzalloc(dev, sizeof(*sdata), GFP_KERNEL); + if (!sdata) + return -ENOMEM; + + sdata->dev = dev; + sdata->ec = dev_get_drvdata(pdev->dev.parent); + + platform_set_drvdata(pdev, sdata); + + return cros_typec_register_switches(sdata); +} + +static int cros_typec_switch_remove(struct platform_device *pdev) +{ + struct cros_typec_switch_data *sdata = platform_get_drvdata(pdev); + + cros_typec_unregister_switches(sdata); + return 0; +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id cros_typec_switch_acpi_id[] = { + { "GOOG001A", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, cros_typec_switch_acpi_id); +#endif + +static struct platform_driver cros_typec_switch_driver = { + .driver = { + .name = DRV_NAME, + .acpi_match_table = ACPI_PTR(cros_typec_switch_acpi_id), + }, + .probe = cros_typec_switch_probe, + .remove = cros_typec_switch_remove, +}; + +module_platform_driver(cros_typec_switch_driver); + +MODULE_AUTHOR("Prashant Malani "); +MODULE_DESCRIPTION("ChromeOS EC Type-C Switch control"); +MODULE_LICENSE("GPL"); From d4536a216c3f8ea0abcf90110750eb297ce48b45 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 16 Aug 2022 21:48:31 +0000 Subject: [PATCH 04/19] platform/chrome: cros_typec_switch: Set EC retimer Invoke ChromeOS EC host commands to set EC-controlled retimer switches to the state the Type-C framework instructs. Signed-off-by: Prashant Malani Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220816214857.2088914-4-pmalani@chromium.org --- drivers/platform/chrome/cros_typec_switch.c | 53 ++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index db773bf57899..eae7d26416aa 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -9,7 +9,10 @@ #include #include #include +#include #include +#include +#include #include #define DRV_NAME "cros-typec-switch" @@ -28,9 +31,57 @@ struct cros_typec_switch_data { struct cros_typec_port *ports[EC_USB_PD_MAX_PORTS]; }; +static int cros_typec_cmd_mux_set(struct cros_typec_switch_data *sdata, int port_num, u8 index, + u8 state) +{ + struct ec_params_typec_control req = { + .port = port_num, + .command = TYPEC_CONTROL_COMMAND_USB_MUX_SET, + .mux_params = { + .mux_index = index, + .mux_flags = state, + }, + }; + + return cros_ec_cmd(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, sizeof(req), NULL, 0); +} + +static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *alt) +{ + int ret = -EOPNOTSUPP; + + if (mode == TYPEC_STATE_SAFE) + ret = USB_PD_MUX_SAFE_MODE; + else if (mode == TYPEC_STATE_USB) + ret = USB_PD_MUX_USB_ENABLED; + else if (alt && alt->svid == USB_TYPEC_DP_SID) + ret = USB_PD_MUX_DP_ENABLED; + + return ret; +} + +/* + * The ChromeOS EC treats both mode-switches and retimers as "muxes" for the purposes of the + * host command API. This common function configures and verifies the retimer/mode-switch + * according to the provided setting. + */ +static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int port_num, int index, + unsigned long mode, struct typec_altmode *alt) +{ + int ret = cros_typec_get_mux_state(mode, alt); + + if (ret < 0) + return ret; + + return cros_typec_cmd_mux_set(sdata, port_num, index, (u8)ret); +} + static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) { - return 0; + struct cros_typec_port *port = typec_retimer_get_drvdata(retimer); + + /* Retimers have index 1. */ + return cros_typec_configure_mux(port->sdata, port->port_num, 1, state->mode, state->alt); } static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) From cf6c767244ed599492ac4275d1d94435d9ec7917 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 16 Aug 2022 21:48:32 +0000 Subject: [PATCH 05/19] platform/chrome: cros_typec_switch: Add event check The ChromeOS EC updates Type-C status events when mux set requests from the Application Processor (AP) are completed. Add a check to the flow of configuring muxes to look for this status done bit, so that the driver is aware that the mux set completed successfully or not. Signed-off-by: Prashant Malani Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220816214857.2088914-5-pmalani@chromium.org --- drivers/platform/chrome/cros_typec_switch.c | 71 ++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index eae7d26416aa..dc84cdec8f7f 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -7,6 +7,8 @@ */ #include +#include +#include #include #include #include @@ -60,6 +62,39 @@ static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *al return ret; } +static int cros_typec_send_clear_event(struct cros_typec_switch_data *sdata, int port_num, + u32 events_mask) +{ + struct ec_params_typec_control req = { + .port = port_num, + .command = TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, + .clear_events_mask = events_mask, + }; + + return cros_ec_cmd(sdata->ec, 0, EC_CMD_TYPEC_CONTROL, &req, sizeof(req), NULL, 0); +} + +static bool cros_typec_check_event(struct cros_typec_switch_data *sdata, int port_num, u32 mask) +{ + struct ec_response_typec_status resp; + struct ec_params_typec_status req = { + .port = port_num, + }; + int ret; + + ret = cros_ec_cmd(sdata->ec, 0, EC_CMD_TYPEC_STATUS, &req, sizeof(req), + &resp, sizeof(resp)); + if (ret < 0) { + dev_warn(sdata->dev, "EC_CMD_TYPEC_STATUS failed for port: %d\n", port_num); + return false; + } + + if (resp.events & mask) + return true; + + return false; +} + /* * The ChromeOS EC treats both mode-switches and retimers as "muxes" for the purposes of the * host command API. This common function configures and verifies the retimer/mode-switch @@ -68,12 +103,44 @@ static int cros_typec_get_mux_state(unsigned long mode, struct typec_altmode *al static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int port_num, int index, unsigned long mode, struct typec_altmode *alt) { - int ret = cros_typec_get_mux_state(mode, alt); + unsigned long end; + u32 event_mask; + u8 mux_state; + int ret; + ret = cros_typec_get_mux_state(mode, alt); + if (ret < 0) + return ret; + mux_state = (u8)ret; + + /* Clear any old mux set done event. */ + if (index == 0) + event_mask = PD_STATUS_EVENT_MUX_0_SET_DONE; + else + event_mask = PD_STATUS_EVENT_MUX_1_SET_DONE; + + ret = cros_typec_send_clear_event(sdata, port_num, event_mask); if (ret < 0) return ret; - return cros_typec_cmd_mux_set(sdata, port_num, index, (u8)ret); + /* Send the set command. */ + ret = cros_typec_cmd_mux_set(sdata, port_num, index, mux_state); + if (ret < 0) + return ret; + + /* Check for the mux set done event. */ + end = jiffies + msecs_to_jiffies(1000); + do { + if (cros_typec_check_event(sdata, port_num, event_mask)) + return 0; + + usleep_range(500, 1000); + } while (time_before(jiffies, end)); + + dev_err(sdata->dev, "Timed out waiting for mux set done on index: %d, state: %d\n", + index, mux_state); + + return -ETIMEDOUT; } static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) From 9e6e05169980e83a870dd595012ec014a5fc440c Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 16 Aug 2022 21:48:33 +0000 Subject: [PATCH 06/19] platform/chrome: cros_typec_switch: Register mode switches Register mode switch devices for Type-C connectors, when they are specified by firmware. These control Type-C configuration for any USB Type-C mode switches (sometimes known as "muxes") which are controlled by the ChromeOS EC. Signed-off-by: Prashant Malani Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220816214857.2088914-6-pmalani@chromium.org --- drivers/platform/chrome/cros_typec_switch.c | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index dc84cdec8f7f..f85687adb594 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #define DRV_NAME "cros-typec-switch" @@ -22,6 +23,7 @@ /* Handles and other relevant data required for each port's switches. */ struct cros_typec_port { int port_num; + struct typec_mux_dev *mode_switch; struct typec_retimer *retimer; struct cros_typec_switch_data *sdata; }; @@ -143,6 +145,15 @@ static int cros_typec_configure_mux(struct cros_typec_switch_data *sdata, int po return -ETIMEDOUT; } +static int cros_typec_mode_switch_set(struct typec_mux_dev *mode_switch, + struct typec_mux_state *state) +{ + struct cros_typec_port *port = typec_mux_get_drvdata(mode_switch); + + /* Mode switches have index 0. */ + return cros_typec_configure_mux(port->sdata, port->port_num, 0, state->mode, state->alt); +} + static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) { struct cros_typec_port *port = typec_retimer_get_drvdata(retimer); @@ -159,9 +170,27 @@ static void cros_typec_unregister_switches(struct cros_typec_switch_data *sdata) if (!sdata->ports[i]) continue; typec_retimer_unregister(sdata->ports[i]->retimer); + typec_mux_unregister(sdata->ports[i]->mode_switch); } } +static int cros_typec_register_mode_switch(struct cros_typec_port *port, + struct fwnode_handle *fwnode) +{ + struct typec_mux_desc mode_switch_desc = { + .fwnode = fwnode, + .drvdata = port, + .name = fwnode_get_name(fwnode), + .set = cros_typec_mode_switch_set, + }; + + port->mode_switch = typec_mux_register(port->sdata->dev, &mode_switch_desc); + if (IS_ERR(port->mode_switch)) + return PTR_ERR(port->mode_switch); + + return 0; +} + static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode) { struct typec_retimer_desc retimer_desc = { @@ -230,6 +259,17 @@ static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) } dev_dbg(dev, "Retimer switch registered for index %llu\n", index); + + if (!device_property_present(fwnode->dev, "mode-switch")) + continue; + + ret = cros_typec_register_mode_switch(port, fwnode); + if (ret) { + dev_err(dev, "Mode switch register failed\n"); + goto err_switch; + } + + dev_dbg(dev, "Mode switch registered for index %llu\n", index); } return 0; From d5f66527db9e0e6a871d9005200b3394156cf16f Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 16 Aug 2022 21:48:34 +0000 Subject: [PATCH 07/19] platform/chrome: cros_ec_typec: Cleanup switch handle return paths Some of the return paths for the cros_typec_get_switch_handles() aren't necessary. Clean up the return paths to only undo the handle get's which succeeded. Signed-off-by: Prashant Malani Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220816214857.2088914-7-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index de6ee0f926a6..ee54add992db 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -158,12 +158,10 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, return 0; role_sw_err: - usb_role_switch_put(port->role_sw); -ori_sw_err: typec_switch_put(port->ori_sw); -mux_err: +ori_sw_err: typec_mux_put(port->mux); - +mux_err: return -ENODEV; } From 1a8912caba02522f612d465a4849ce98915b96ad Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 16 Aug 2022 21:48:35 +0000 Subject: [PATCH 08/19] platform/chrome: cros_ec_typec: Get retimer handle Where available, obtain the handle to retimer switch specified via firmware, and update the mux configuration callsites to add retimer support for supported modes. Signed-off-by: Prashant Malani Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220816214857.2088914-8-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 44 +++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index ee54add992db..3ffe3c082ea2 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,7 @@ struct cros_typec_port { struct usb_pd_identity c_identity; struct typec_switch *ori_sw; struct typec_mux *mux; + struct typec_retimer *retimer; struct usb_role_switch *role_sw; /* Variables keeping track of switch state. */ @@ -143,6 +145,12 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, goto mux_err; } + port->retimer = fwnode_typec_retimer_get(fwnode); + if (IS_ERR(port->retimer)) { + dev_dbg(dev, "Retimer handle not found.\n"); + goto retimer_sw_err; + } + port->ori_sw = fwnode_typec_switch_get(fwnode); if (IS_ERR(port->ori_sw)) { dev_dbg(dev, "Orientation switch handle not found.\n"); @@ -160,6 +168,8 @@ static int cros_typec_get_switch_handles(struct cros_typec_port *port, role_sw_err: typec_switch_put(port->ori_sw); ori_sw_err: + typec_retimer_put(port->retimer); +retimer_sw_err: typec_mux_put(port->mux); mux_err: return -ENODEV; @@ -204,6 +214,21 @@ static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int po } } +/* + * Map the Type-C Mux state to retimer state and call the retimer set function. We need this + * because we re-use the Type-C mux state for retimers. + */ +static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_mux_state state) +{ + struct typec_retimer_state rstate = { + .alt = state.alt, + .mode = state.mode, + .data = state.data, + }; + + return typec_retimer_set(retimer, &rstate); +} + static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) { port->state.alt = NULL; @@ -212,6 +237,7 @@ static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE); typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE); + cros_typec_retimer_set(port->retimer, port->state); return typec_mux_set(port->mux, &port->state); } @@ -409,9 +435,14 @@ unregister_ports: static int cros_typec_usb_safe_state(struct cros_typec_port *port) { + int ret; port->state.mode = TYPEC_STATE_SAFE; - return typec_mux_set(port->mux, &port->state); + ret = cros_typec_retimer_set(port->retimer, port->state); + if (!ret) + ret = typec_mux_set(port->mux, &port->state); + + return ret; } /* @@ -508,7 +539,11 @@ static int cros_typec_enable_dp(struct cros_typec_data *typec, port->state.data = &dp_data; port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode)); - return typec_mux_set(port->mux, &port->state); + ret = cros_typec_retimer_set(port->retimer, port->state); + if (!ret) + ret = typec_mux_set(port->mux, &port->state); + + return ret; } static int cros_typec_enable_usb4(struct cros_typec_data *typec, @@ -597,7 +632,10 @@ static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, } else if (port->mux_flags & USB_PD_MUX_USB_ENABLED) { port->state.alt = NULL; port->state.mode = TYPEC_STATE_USB; - ret = typec_mux_set(port->mux, &port->state); + + ret = cros_typec_retimer_set(port->retimer, port->state); + if (!ret) + ret = typec_mux_set(port->mux, &port->state); } else { dev_dbg(typec->dev, "Unrecognized mode requested, mux flags: %x\n", From 6ad4194d6a1e1d11b285989cd648ef695b4a93c0 Mon Sep 17 00:00:00 2001 From: Rustam Subkhankulov Date: Sun, 14 Aug 2022 01:08:43 +0300 Subject: [PATCH 09/19] platform/chrome: fix double-free in chromeos_laptop_prepare() If chromeos_laptop_prepare_i2c_peripherals() fails after allocating memory for 'cros_laptop->i2c_peripherals', this memory is freed at 'err_out' label and nonzero value is returned. Then chromeos_laptop_destroy() is called, resulting in double-free error. Found by Linux Verification Center (linuxtesting.org) with SVACE. Signed-off-by: Rustam Subkhankulov Fixes: 5020cd29d8bf ("platform/chrome: chromeos_laptop - supply properties for ACPI devices") Reviewed-by: Dmitry Torokhov Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220813220843.2373004-1-subkhankulov@ispras.ru --- drivers/platform/chrome/chromeos_laptop.c | 24 ++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index 4e14b4d6635d..a2cdbfbaeae6 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -740,6 +740,7 @@ static int __init chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, const struct chromeos_laptop *src) { + struct i2c_peripheral *i2c_peripherals; struct i2c_peripheral *i2c_dev; struct i2c_board_info *info; int i; @@ -748,17 +749,15 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, if (!src->num_i2c_peripherals) return 0; - cros_laptop->i2c_peripherals = kmemdup(src->i2c_peripherals, - src->num_i2c_peripherals * - sizeof(*src->i2c_peripherals), - GFP_KERNEL); - if (!cros_laptop->i2c_peripherals) + i2c_peripherals = kmemdup(src->i2c_peripherals, + src->num_i2c_peripherals * + sizeof(*src->i2c_peripherals), + GFP_KERNEL); + if (!i2c_peripherals) return -ENOMEM; - cros_laptop->num_i2c_peripherals = src->num_i2c_peripherals; - - for (i = 0; i < cros_laptop->num_i2c_peripherals; i++) { - i2c_dev = &cros_laptop->i2c_peripherals[i]; + for (i = 0; i < src->num_i2c_peripherals; i++) { + i2c_dev = &i2c_peripherals[i]; info = &i2c_dev->board_info; error = chromeos_laptop_setup_irq(i2c_dev); @@ -775,16 +774,19 @@ chromeos_laptop_prepare_i2c_peripherals(struct chromeos_laptop *cros_laptop, } } + cros_laptop->i2c_peripherals = i2c_peripherals; + cros_laptop->num_i2c_peripherals = src->num_i2c_peripherals; + return 0; err_out: while (--i >= 0) { - i2c_dev = &cros_laptop->i2c_peripherals[i]; + i2c_dev = &i2c_peripherals[i]; info = &i2c_dev->board_info; if (!IS_ERR_OR_NULL(info->fwnode)) fwnode_remove_software_node(info->fwnode); } - kfree(cros_laptop->i2c_peripherals); + kfree(i2c_peripherals); return error; } From 8a07b45fd3c2dda24fad43639be5335a4595196a Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 19 Aug 2022 08:20:36 +0300 Subject: [PATCH 10/19] platform/chrome: fix memory corruption in ioctl If "s_mem.bytes" is larger than the buffer size it leads to memory corruption. Fixes: eda2e30c6684 ("mfd / platform: cros_ec: Miscellaneous character device to talk with the EC") Signed-off-by: Dan Carpenter Reviewed-by: Guenter Roeck Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/Yv8dpCFZJdbUT5ye@kili --- drivers/platform/chrome/cros_ec_chardev.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c index fd33de546aee..0de7c255254e 100644 --- a/drivers/platform/chrome/cros_ec_chardev.c +++ b/drivers/platform/chrome/cros_ec_chardev.c @@ -327,6 +327,9 @@ static long cros_ec_chardev_ioctl_readmem(struct cros_ec_dev *ec, if (copy_from_user(&s_mem, arg, sizeof(s_mem))) return -EFAULT; + if (s_mem.bytes > sizeof(s_mem.buffer)) + return -EINVAL; + num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, s_mem.buffer); if (num <= 0) From e8bf17d58a4db4b4f38617925414097f12e0d509 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Mon, 22 Aug 2022 14:40:40 -0700 Subject: [PATCH 11/19] platform/chrome: cros_ec: Expose suspend_timeout_ms in debugfs In modern Chromebooks, the embedded controller has a mechanism where it will watch a hardware-controlled line that toggles in suspend, and wake the system up if an expected sleep transition didn't occur. This can be very useful for detecting power management issues where the system appears to suspend, but doesn't actually reach its lowest expected power states. Sometimes it's useful in debug and test scenarios to be able to control the duration of that timeout, or even disable the EC timeout mechanism altogether. Add a debugfs control to set the timeout to values other than the EC-defined default, for more convenient debug and development iteration. Signed-off-by: Evan Green Reviewed-by: Prashant Malani Reviewed-by: Stephen Boyd Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20220822144026.v3.1.Idd188ff3f9caddebc17ac357a13005f93333c21f@changeid [tzungbi: fix one nit in Documentation/ABI/testing/debugfs-cros-ec.] Signed-off-by: Tzung-Bi Shih --- Documentation/ABI/testing/debugfs-cros-ec | 22 +++++++++++++++++++++ drivers/platform/chrome/cros_ec.c | 3 ++- drivers/platform/chrome/cros_ec_debugfs.c | 3 +++ include/linux/platform_data/cros_ec_proto.h | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/debugfs-cros-ec b/Documentation/ABI/testing/debugfs-cros-ec index 1fe0add99a2a..9a040c6f5e03 100644 --- a/Documentation/ABI/testing/debugfs-cros-ec +++ b/Documentation/ABI/testing/debugfs-cros-ec @@ -54,3 +54,25 @@ Description: this feature. Output will be in the format: "0x%08x\n". + +What: /sys/kernel/debug//suspend_timeout_ms +Date: August 2022 +KernelVersion: 6.1 +Description: + Some ECs have a feature where they will track transitions of + a hardware-controlled sleep line, such as Intel's SLP_S0 line, + in order to detect cases where a system failed to go into deep + sleep states. The suspend_timeout_ms file controls the amount of + time in milliseconds the EC will wait before declaring a sleep + timeout event and attempting to wake the system. + + Supply 0 to use the default value coded into EC firmware. Supply + 65535 (EC_HOST_SLEEP_TIMEOUT_INFINITE) to disable the EC sleep + failure detection mechanism. Values in between 0 and 65535 + indicate the number of milliseconds the EC should wait after a + sleep transition before declaring a timeout. This includes both + the duration after a sleep command was received but before the + hardware line changed, as well as the duration between when the + hardware line changed and the kernel sent an EC resume command. + + Output will be in the format: "%u\n". diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c index 8aace50d446d..32140a7150d0 100644 --- a/drivers/platform/chrome/cros_ec.c +++ b/drivers/platform/chrome/cros_ec.c @@ -115,7 +115,7 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) if (ec_dev->host_sleep_v1) { buf.u.req1.sleep_event = sleep_event; buf.u.req1.suspend_params.sleep_timeout_ms = - EC_HOST_SLEEP_TIMEOUT_DEFAULT; + ec_dev->suspend_timeout_ms; buf.msg.outsize = sizeof(buf.u.req1); if ((sleep_event == HOST_SLEEP_EVENT_S3_RESUME) || @@ -188,6 +188,7 @@ int cros_ec_register(struct cros_ec_device *ec_dev) ec_dev->max_passthru = 0; ec_dev->ec = NULL; ec_dev->pd = NULL; + ec_dev->suspend_timeout_ms = EC_HOST_SLEEP_TIMEOUT_DEFAULT; ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); if (!ec_dev->din) diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index 0dbceee87a4b..4e63adf083ea 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -470,6 +470,9 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) debugfs_create_x32("last_resume_result", 0444, debug_info->dir, &ec->ec_dev->last_resume_result); + debugfs_create_u16("suspend_timeout_ms", 0664, debug_info->dir, + &ec->ec_dev->suspend_timeout_ms); + ec->debug_info = debug_info; dev_set_drvdata(&pd->dev, ec); diff --git a/include/linux/platform_data/cros_ec_proto.h b/include/linux/platform_data/cros_ec_proto.h index 408b29ca4004..e43107e0bee1 100644 --- a/include/linux/platform_data/cros_ec_proto.h +++ b/include/linux/platform_data/cros_ec_proto.h @@ -169,6 +169,7 @@ struct cros_ec_device { int event_size; u32 host_event_wake_mask; u32 last_resume_result; + u16 suspend_timeout_ms; ktime_t last_event_time; struct notifier_block notifier_ready; From 1903adae0464c1e1c36b132db474cb3aff7bc727 Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Fri, 19 Aug 2022 19:08:02 +0000 Subject: [PATCH 12/19] platform/chrome: cros_ec_typec: Add bit offset for DP VDO Use the right macro while constructing the DP_PORT_VDO to ensure the Pin Assignment offsets are correct. Fixes: 1ff5d97f070c ("platform/chrome: cros_ec_typec: Register port altmodes") Signed-off-by: Prashant Malani Acked-by: Heikki Krogerus Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220819190807.1275937-2-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 3ffe3c082ea2..d4dbca5d91af 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -26,7 +26,8 @@ #define DRV_NAME "cros-ec-typec" -#define DP_PORT_VDO (BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) | DP_CAP_DFP_D) +#define DP_PORT_VDO (DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)) | \ + DP_CAP_DFP_D) /* Supported alt modes. */ enum { From 4e477663e396f48c5cfc5f2d75d4b514f409516a Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Fri, 19 Aug 2022 19:08:03 +0000 Subject: [PATCH 13/19] platform/chrome: cros_ec_typec: Correct alt mode index Alt mode indices used by USB PD (Power Delivery) start with 1, not 0. Update the alt mdoe registration code to factor this in to the alt mode descriptor. Fixes: de0f49487db3 ("platform/chrome: cros_ec_typec: Register partner altmodes") Signed-off-by: Prashant Malani Acked-by: Heikki Krogerus Reviewed-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220819190807.1275937-3-pmalani@chromium.org --- drivers/platform/chrome/cros_ec_typec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index d4dbca5d91af..00208ffbe2e7 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -734,7 +734,7 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_ for (j = 0; j < sop_disc->svids[i].mode_count; j++) { memset(&desc, 0, sizeof(desc)); desc.svid = sop_disc->svids[i].svid; - desc.mode = j; + desc.mode = j + 1; desc.vdo = sop_disc->svids[i].mode_vdo[j]; if (is_partner) From 8dab6a5939199a5dcf0731eb2dce757837575633 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 30 Aug 2022 15:58:28 -0700 Subject: [PATCH 14/19] platform/chrome: cros_typec_switch: Add missing newline on printk We need a newline here to ensure the next printk starts fresh. Cc: Prashant Malani Cc: Tzung-Bi Shih Fixes: affc804c44c8 ("platform/chrome: cros_typec_switch: Add switch driver") Signed-off-by: Stephen Boyd Acked-by: Prashant Malani Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220830225831.2362403-2-swboyd@chromium.org --- drivers/platform/chrome/cros_typec_switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index f85687adb594..383daf2c66b7 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -244,7 +244,7 @@ static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) } if (index < 0 || index >= EC_USB_PD_MAX_PORTS) { - dev_err(fwnode->dev, "Invalid port index number: %llu", index); + dev_err(fwnode->dev, "Invalid port index number: %llu\n", index); ret = -EINVAL; goto err_switch; } From bbb5fb85cf48757a18870aaeedf6936d5c1f1ead Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 30 Aug 2022 15:58:29 -0700 Subject: [PATCH 15/19] platform/chrome: cros_typec_switch: Remove impossible condition The type of 'index' is unsigned long long, which can't possibly be less than zero. Remove the impossible check. Cc: Prashant Malani Cc: Tzung-Bi Shih Signed-off-by: Stephen Boyd Acked-by: Prashant Malani Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220830225831.2362403-3-swboyd@chromium.org --- drivers/platform/chrome/cros_typec_switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 383daf2c66b7..3381d842c307 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -243,7 +243,7 @@ static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) goto err_switch; } - if (index < 0 || index >= EC_USB_PD_MAX_PORTS) { + if (index >= EC_USB_PD_MAX_PORTS) { dev_err(fwnode->dev, "Invalid port index number: %llu\n", index); ret = -EINVAL; goto err_switch; From dc22a33e35859835b09cff11f445928af0197808 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 30 Aug 2022 15:58:30 -0700 Subject: [PATCH 16/19] platform/chrome: cros_typec_switch: Use PTR_ERR_OR_ZERO() to simplify Use the standard error pointer macro to shorten the code and simplify. Cc: Prashant Malani Cc: Tzung-Bi Shih Signed-off-by: Stephen Boyd Acked-by: Prashant Malani Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220830225831.2362403-4-swboyd@chromium.org --- drivers/platform/chrome/cros_typec_switch.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 3381d842c307..09ad0d268f4b 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -185,10 +185,8 @@ static int cros_typec_register_mode_switch(struct cros_typec_port *port, }; port->mode_switch = typec_mux_register(port->sdata->dev, &mode_switch_desc); - if (IS_ERR(port->mode_switch)) - return PTR_ERR(port->mode_switch); - return 0; + return PTR_ERR_OR_ZERO(port->mode_switch); } static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwnode_handle *fwnode) @@ -201,10 +199,8 @@ static int cros_typec_register_retimer(struct cros_typec_port *port, struct fwno }; port->retimer = typec_retimer_register(port->sdata->dev, &retimer_desc); - if (IS_ERR(port->retimer)) - return PTR_ERR(port->retimer); - return 0; + return PTR_ERR_OR_ZERO(port->retimer); } static int cros_typec_register_switches(struct cros_typec_switch_data *sdata) From 20dfb74783093e863228d511f7e6a8db8ee39e18 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Tue, 30 Aug 2022 15:58:31 -0700 Subject: [PATCH 17/19] platform/chrome: cros_typec_switch: Inline DRV_NAME This macro is only used one place, let's inline it instead to save a line or two. Cc: Prashant Malani Cc: Tzung-Bi Shih Signed-off-by: Stephen Boyd Acked-by: Prashant Malani Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220830225831.2362403-5-swboyd@chromium.org --- drivers/platform/chrome/cros_typec_switch.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/platform/chrome/cros_typec_switch.c b/drivers/platform/chrome/cros_typec_switch.c index 09ad0d268f4b..a26219e97c93 100644 --- a/drivers/platform/chrome/cros_typec_switch.c +++ b/drivers/platform/chrome/cros_typec_switch.c @@ -18,8 +18,6 @@ #include #include -#define DRV_NAME "cros-typec-switch" - /* Handles and other relevant data required for each port's switches. */ struct cros_typec_port { int port_num; @@ -309,7 +307,7 @@ MODULE_DEVICE_TABLE(acpi, cros_typec_switch_acpi_id); static struct platform_driver cros_typec_switch_driver = { .driver = { - .name = DRV_NAME, + .name = "cros-typec-switch", .acpi_match_table = ACPI_PTR(cros_typec_switch_acpi_id), }, .probe = cros_typec_switch_probe, From 348a2e8c93d3ca622cf1b293cd2f597c9db74d9d Mon Sep 17 00:00:00 2001 From: Prashant Malani Date: Tue, 30 Aug 2022 20:20:18 +0000 Subject: [PATCH 18/19] platform/chrome: cros_ec_typec: Register partner PDOs The ChromeOS EC exports partner source/sink cap PDOs (Power Data Objects) to the application processor (AP). Use this information to register USB PD (Power Delivery) capabilities with the USB Type-C Power Delivery device class. Cc: Heikki Krogerus Signed-off-by: Prashant Malani Reviewed-by: Heikki Krogerus Link: https://lore.kernel.org/r/20220830202018.1884851-1-pmalani@chromium.org [pmalani: Remove extra newline nit from original patch] --- drivers/platform/chrome/cros_ec_typec.c | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 00208ffbe2e7..2a7ff14dc37e 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -73,6 +73,11 @@ struct cros_typec_port { struct ec_response_typec_discovery *disc_data; struct list_head partner_mode_list; struct list_head plug_mode_list; + + /* PDO-related structs */ + struct usb_power_delivery *partner_pd; + struct usb_power_delivery_capabilities *partner_src_caps; + struct usb_power_delivery_capabilities *partner_sink_caps; }; /* Platform-specific data for the Chrome OS EC Type C controller. */ @@ -253,6 +258,14 @@ static void cros_typec_remove_partner(struct cros_typec_data *typec, cros_typec_unregister_altmodes(typec, port_num, true); + typec_partner_set_usb_power_delivery(port->partner, NULL); + usb_power_delivery_unregister_capabilities(port->partner_sink_caps); + port->partner_sink_caps = NULL; + usb_power_delivery_unregister_capabilities(port->partner_src_caps); + port->partner_src_caps = NULL; + usb_power_delivery_unregister(port->partner_pd); + port->partner_pd = NULL; + cros_typec_usb_disconnect_state(port); port->mux_flags = USB_PD_MUX_NONE; @@ -939,6 +952,46 @@ static int cros_typec_send_clear_event(struct cros_typec_data *typec, int port_n sizeof(req), NULL, 0); } +static void cros_typec_register_partner_pdos(struct cros_typec_data *typec, + struct ec_response_typec_status *resp, int port_num) +{ + struct usb_power_delivery_capabilities_desc caps_desc = {}; + struct usb_power_delivery_desc desc = { + .revision = (le16_to_cpu(resp->sop_revision) & 0xff00) >> 4, + }; + struct cros_typec_port *port = typec->ports[port_num]; + + if (!port->partner || port->partner_pd) + return; + + /* If no caps are available, don't bother creating a device. */ + if (!resp->source_cap_count && !resp->sink_cap_count) + return; + + port->partner_pd = usb_power_delivery_register(NULL, &desc); + if (IS_ERR(port->partner_pd)) { + dev_warn(typec->dev, "Failed to register partner PD device, port: %d\n", port_num); + return; + } + + typec_partner_set_usb_power_delivery(port->partner, port->partner_pd); + + memcpy(caps_desc.pdo, resp->source_cap_pdos, sizeof(u32) * resp->source_cap_count); + caps_desc.role = TYPEC_SOURCE; + port->partner_src_caps = usb_power_delivery_register_capabilities(port->partner_pd, + &caps_desc); + if (IS_ERR(port->partner_src_caps)) + dev_warn(typec->dev, "Failed to register source caps, port: %d\n", port_num); + + memset(&caps_desc, 0, sizeof(caps_desc)); + memcpy(caps_desc.pdo, resp->sink_cap_pdos, sizeof(u32) * resp->sink_cap_count); + caps_desc.role = TYPEC_SINK; + port->partner_sink_caps = usb_power_delivery_register_capabilities(port->partner_pd, + &caps_desc); + if (IS_ERR(port->partner_sink_caps)) + dev_warn(typec->dev, "Failed to register sink caps, port: %d\n", port_num); +} + static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num) { struct ec_response_typec_status resp; @@ -986,6 +1039,8 @@ static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num } if (resp.sop_connected) typec_set_pwr_opmode(typec->ports[port_num]->port, TYPEC_PWR_MODE_PD); + + cros_typec_register_partner_pdos(typec, &resp, port_num); } if (resp.events & PD_STATUS_EVENT_SOP_PRIME_DISC_DONE && From 8edd2752b0aa498b3a61f3caee8f79f7e0567fad Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Tue, 13 Sep 2022 20:49:54 +0000 Subject: [PATCH 19/19] platform/chrome: cros_ec: Notify the PM of wake events during resume cros_ec_handle_event in the cros_ec driver can notify the PM of wake events. When a device is suspended, cros_ec_handle_event will not check MKBP events. Instead, received MKBP events are checked during resume by cros_ec_report_events_during_suspend. But cros_ec_report_events_during_suspend cannot notify the PM if received events are wake events, causing wake events to not be reported if received while the device is suspended. Update cros_ec_report_events_during_suspend to notify the PM of wake events during resume by calling pm_wakeup_event. Signed-off-by: Jameson Thies Reviewed-by: Prashant Malani Reviewed-by: Benson Leung Signed-off-by: Tzung-Bi Shih Link: https://lore.kernel.org/r/20220913204954.2931042-1-jthies@google.com --- drivers/platform/chrome/cros_ec.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c index 32140a7150d0..ec733f683f34 100644 --- a/drivers/platform/chrome/cros_ec.c +++ b/drivers/platform/chrome/cros_ec.c @@ -350,10 +350,16 @@ EXPORT_SYMBOL(cros_ec_suspend); static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev) { + bool wake_event; + while (ec_dev->mkbp_event_supported && - cros_ec_get_next_event(ec_dev, NULL, NULL) > 0) + cros_ec_get_next_event(ec_dev, &wake_event, NULL) > 0) { blocking_notifier_call_chain(&ec_dev->event_notifier, 1, ec_dev); + + if (wake_event && device_may_wakeup(ec_dev->dev)) + pm_wakeup_event(ec_dev->dev, 0); + } } /**