diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 18:50:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 18:50:36 +0000 |
commit | 50ba0232fd5312410f1b65247e774244f89a628e (patch) | |
tree | fd8f2fc78e9e548af0ff9590276602ee6125be00 /drivers/usb/typec | |
parent | Releasing progress-linux version 6.7.12-1~progress7.99u1. (diff) | |
download | linux-50ba0232fd5312410f1b65247e774244f89a628e.tar.xz linux-50ba0232fd5312410f1b65247e774244f89a628e.zip |
Merging upstream version 6.8.9.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r-- | drivers/usb/typec/class.c | 13 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Kconfig | 12 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/typec/mux/nb7vpq904m.c | 44 | ||||
-rw-r--r-- | drivers/usb/typec/mux/wcd939x-usbss.c | 779 | ||||
-rw-r--r-- | drivers/usb/typec/pd.c | 6 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/Kconfig | 1 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c | 41 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci.c | 1 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci_maxim_core.c | 20 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpm.c | 420 | ||||
-rw-r--r-- | drivers/usb/typec/tipd/core.c | 177 | ||||
-rw-r--r-- | drivers/usb/typec/tipd/tps6598x.h | 18 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 29 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.h | 14 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi_glink.c | 15 |
16 files changed, 1386 insertions, 205 deletions
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 2373cd39b4..e6a100376f 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -478,7 +478,7 @@ static int altmode_id_get(struct device *dev) else ids = &to_typec_port(dev)->mode_ids; - return ida_simple_get(ids, 0, 0, GFP_KERNEL); + return ida_alloc(ids, GFP_KERNEL); } static void altmode_id_remove(struct device *dev, int id) @@ -492,7 +492,7 @@ static void altmode_id_remove(struct device *dev, int id) else ids = &to_typec_port(dev)->mode_ids; - ida_simple_remove(ids, id); + ida_free(ids, id); } static void typec_altmode_release(struct device *dev) @@ -1805,7 +1805,7 @@ static void typec_release(struct device *dev) { struct typec_port *port = to_typec_port(dev); - ida_simple_remove(&typec_index_ida, port->id); + ida_free(&typec_index_ida, port->id); ida_destroy(&port->mode_ids); typec_switch_put(port->sw); typec_mux_put(port->mux); @@ -2238,7 +2238,8 @@ void typec_port_register_altmodes(struct typec_port *port, struct typec_altmode_desc desc; struct typec_altmode *alt; size_t index = 0; - u32 svid, vdo; + u16 svid; + u32 vdo; int ret; altmodes_node = device_get_named_child_node(&port->dev, "altmodes"); @@ -2246,7 +2247,7 @@ void typec_port_register_altmodes(struct typec_port *port, return; /* No altmodes specified */ fwnode_for_each_child_node(altmodes_node, child) { - ret = fwnode_property_read_u32(child, "svid", &svid); + ret = fwnode_property_read_u16(child, "svid", &svid); if (ret) { dev_err(&port->dev, "Error reading svid for altmode %s\n", fwnode_get_name(child)); @@ -2304,7 +2305,7 @@ struct typec_port *typec_register_port(struct device *parent, if (!port) return ERR_PTR(-ENOMEM); - id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); + id = ida_alloc(&typec_index_ida, GFP_KERNEL); if (id < 0) { kfree(port); return ERR_PTR(id); diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index 816b9bd083..d2cb5e733e 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -40,7 +40,7 @@ config TYPEC_MUX_NB7VPQ904M tristate "On Semiconductor NB7VPQ904M Type-C redriver driver" depends on I2C depends on DRM || DRM=n - select DRM_PANEL_BRIDGE if DRM + select DRM_AUX_BRIDGE if DRM_BRIDGE && OF select REGMAP_I2C help Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C @@ -56,4 +56,14 @@ config TYPEC_MUX_PTN36502 Say Y or M if your system has a NXP PTN36502 Type-C redriver chip found on some devices with a Type-C port. +config TYPEC_MUX_WCD939X_USBSS + tristate "Qualcomm WCD939x USBSS Analog Audio Switch driver" + depends on I2C + select REGMAP_I2C + help + Driver for the Qualcomm WCD939x Audio Codec USBSS domain which + provides support for muxing analog audio and sideband signals on a + common USB Type-C connector. + If compiled as a module, the module will be named wcd939x-usbss. + endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 9d6a5557b0..57dc9ac6f8 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M) += nb7vpq904m.o obj-$(CONFIG_TYPEC_MUX_PTN36502) += ptn36502.o +obj-$(CONFIG_TYPEC_MUX_WCD939X_USBSS) += wcd939x-usbss.o diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c index cda206cf0c..b178267137 100644 --- a/drivers/usb/typec/mux/nb7vpq904m.c +++ b/drivers/usb/typec/mux/nb7vpq904m.c @@ -11,7 +11,7 @@ #include <linux/regmap.h> #include <linux/bitfield.h> #include <linux/of_graph.h> -#include <drm/drm_bridge.h> +#include <drm/bridge/aux-bridge.h> #include <linux/usb/typec_dp.h> #include <linux/usb/typec_mux.h> #include <linux/usb/typec_retimer.h> @@ -70,8 +70,6 @@ struct nb7vpq904m { bool swap_data_lanes; struct typec_switch *typec_switch; - struct drm_bridge bridge; - struct mutex lock; /* protect non-concurrent retimer & switch */ enum typec_orientation orientation; @@ -297,44 +295,6 @@ static int nb7vpq904m_retimer_set(struct typec_retimer *retimer, struct typec_re return ret; } -#if IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL_BRIDGE) -static int nb7vpq904m_bridge_attach(struct drm_bridge *bridge, - enum drm_bridge_attach_flags flags) -{ - struct nb7vpq904m *nb7 = container_of(bridge, struct nb7vpq904m, bridge); - struct drm_bridge *next_bridge; - - if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) - return -EINVAL; - - next_bridge = devm_drm_of_get_bridge(&nb7->client->dev, nb7->client->dev.of_node, 0, 0); - if (IS_ERR(next_bridge)) { - dev_err(&nb7->client->dev, "failed to acquire drm_bridge: %pe\n", next_bridge); - return PTR_ERR(next_bridge); - } - - return drm_bridge_attach(bridge->encoder, next_bridge, bridge, - DRM_BRIDGE_ATTACH_NO_CONNECTOR); -} - -static const struct drm_bridge_funcs nb7vpq904m_bridge_funcs = { - .attach = nb7vpq904m_bridge_attach, -}; - -static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7) -{ - nb7->bridge.funcs = &nb7vpq904m_bridge_funcs; - nb7->bridge.of_node = nb7->client->dev.of_node; - - return devm_drm_bridge_add(&nb7->client->dev, &nb7->bridge); -} -#else -static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7) -{ - return 0; -} -#endif - static const struct regmap_config nb7_regmap = { .max_register = 0x1f, .reg_bits = 8, @@ -461,7 +421,7 @@ static int nb7vpq904m_probe(struct i2c_client *client) gpiod_set_value(nb7->enable_gpio, 1); - ret = nb7vpq904m_register_bridge(nb7); + ret = drm_aux_bridge_register(dev); if (ret) goto err_disable_gpio; diff --git a/drivers/usb/typec/mux/wcd939x-usbss.c b/drivers/usb/typec/mux/wcd939x-usbss.c new file mode 100644 index 0000000000..d46c353dfa --- /dev/null +++ b/drivers/usb/typec/mux/wcd939x-usbss.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (C) 2023 Linaro Ltd. + */ + +#include <linux/bits.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/bitfield.h> +#include <linux/gpio/consumer.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> + +#define WCD_USBSS_PMP_OUT1 0x2 + +#define WCD_USBSS_DP_DN_MISC1 0x20 + +#define WCD_USBSS_DP_DN_MISC1_DP_PCOMP_2X_DYN_BST_ON_EN BIT(3) +#define WCD_USBSS_DP_DN_MISC1_DN_PCOMP_2X_DYN_BST_ON_EN BIT(0) + +#define WCD_USBSS_MG1_EN 0x24 + +#define WCD_USBSS_MG1_EN_CT_SNS_EN BIT(1) + +#define WCD_USBSS_MG1_BIAS 0x25 + +#define WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN BIT(3) + +#define WCD_USBSS_MG1_MISC 0x27 + +#define WCD_USBSS_MG1_MISC_PCOMP_2X_DYN_BST_ON_EN BIT(5) + +#define WCD_USBSS_MG2_EN 0x28 + +#define WCD_USBSS_MG2_EN_CT_SNS_EN BIT(1) + +#define WCD_USBSS_MG2_BIAS 0x29 + +#define WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN BIT(3) + +#define WCD_USBSS_MG2_MISC 0x30 + +#define WCD_USBSS_MG2_MISC_PCOMP_2X_DYN_BST_ON_EN BIT(5) + +#define WCD_USBSS_DISP_AUXP_THRESH 0x80 + +#define WCD_USBSS_DISP_AUXP_THRESH_DISP_AUXP_OVPON_CM GENMASK(7, 5) + +#define WCD_USBSS_DISP_AUXP_CTL 0x81 + +#define WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF GENMASK(2, 0) + +#define WCD_USBSS_CPLDO_CTL2 0xa1 + +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE 0x403 + +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DEVICE_ENABLE BIT(7) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES BIT(6) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES BIT(5) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES BIT(4) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES BIT(3) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES BIT(2) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES BIT(1) +#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES BIT(0) + +#define WCD_USBSS_SWITCH_SELECT0 0x404 + +#define WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES BIT(7) /* 1-> MG2 */ +#define WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES BIT(6) /* 1-> MG2 */ +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES GENMASK(5, 4) +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES GENMASK(3, 2) +#define WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES BIT(1) /* 1-> SBU2 */ +#define WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES BIT(0) /* 1-> MG2 */ + +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_L 0 +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN 1 +#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN2 2 + +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_R 0 +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DP 1 +#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DR2 2 + +#define WCD_USBSS_SWITCH_SELECT1 0x405 + +#define WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES BIT(0) /* 1-> MG2 */ + +#define WCD_USBSS_DELAY_R_SW 0x40d +#define WCD_USBSS_DELAY_MIC_SW 0x40e +#define WCD_USBSS_DELAY_SENSE_SW 0x40f +#define WCD_USBSS_DELAY_GND_SW 0x410 +#define WCD_USBSS_DELAY_L_SW 0x411 + +#define WCD_USBSS_FUNCTION_ENABLE 0x413 + +#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT GENMASK(1, 0) + +#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_MANUAL 1 +#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_AUDIO_FSM 2 + +#define WCD_USBSS_EQUALIZER1 0x415 + +#define WCD_USBSS_EQUALIZER1_EQ_EN BIT(7) +#define WCD_USBSS_EQUALIZER1_BW_SETTINGS GENMASK(6, 3) + +#define WCD_USBSS_USB_SS_CNTL 0x419 + +#define WCD_USBSS_USB_SS_CNTL_STANDBY_STATE BIT(4) +#define WCD_USBSS_USB_SS_CNTL_RCO_EN BIT(3) +#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE GENMASK(2, 0) + +#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_AATC 2 +#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_USB 5 + +#define WCD_USBSS_AUDIO_FSM_START 0x433 + +#define WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG BIT(0) + +#define WCD_USBSS_RATIO_SPKR_REXT_L_LSB 0x461 +#define WCD_USBSS_RATIO_SPKR_REXT_L_MSB 0x462 +#define WCD_USBSS_RATIO_SPKR_REXT_R_LSB 0x463 +#define WCD_USBSS_RATIO_SPKR_REXT_R_MSB 0x464 +#define WCD_USBSS_AUD_COEF_L_K0_0 0x475 +#define WCD_USBSS_AUD_COEF_L_K0_1 0x476 +#define WCD_USBSS_AUD_COEF_L_K0_2 0x477 +#define WCD_USBSS_AUD_COEF_L_K1_0 0x478 +#define WCD_USBSS_AUD_COEF_L_K1_1 0x479 +#define WCD_USBSS_AUD_COEF_L_K2_0 0x47a +#define WCD_USBSS_AUD_COEF_L_K2_1 0x47b +#define WCD_USBSS_AUD_COEF_L_K3_0 0x47c +#define WCD_USBSS_AUD_COEF_L_K3_1 0x47d +#define WCD_USBSS_AUD_COEF_L_K4_0 0x47e +#define WCD_USBSS_AUD_COEF_L_K4_1 0x47f +#define WCD_USBSS_AUD_COEF_L_K5_0 0x480 +#define WCD_USBSS_AUD_COEF_L_K5_1 0x481 +#define WCD_USBSS_AUD_COEF_R_K0_0 0x482 +#define WCD_USBSS_AUD_COEF_R_K0_1 0x483 +#define WCD_USBSS_AUD_COEF_R_K0_2 0x484 +#define WCD_USBSS_AUD_COEF_R_K1_0 0x485 +#define WCD_USBSS_AUD_COEF_R_K1_1 0x486 +#define WCD_USBSS_AUD_COEF_R_K2_0 0x487 +#define WCD_USBSS_AUD_COEF_R_K2_1 0x488 +#define WCD_USBSS_AUD_COEF_R_K3_0 0x489 +#define WCD_USBSS_AUD_COEF_R_K3_1 0x48a +#define WCD_USBSS_AUD_COEF_R_K4_0 0x48b +#define WCD_USBSS_AUD_COEF_R_K4_1 0x48c +#define WCD_USBSS_AUD_COEF_R_K5_0 0x48d +#define WCD_USBSS_AUD_COEF_R_K5_1 0x48e +#define WCD_USBSS_GND_COEF_L_K0_0 0x48f +#define WCD_USBSS_GND_COEF_L_K0_1 0x490 +#define WCD_USBSS_GND_COEF_L_K0_2 0x491 +#define WCD_USBSS_GND_COEF_L_K1_0 0x492 +#define WCD_USBSS_GND_COEF_L_K1_1 0x493 +#define WCD_USBSS_GND_COEF_L_K2_0 0x494 +#define WCD_USBSS_GND_COEF_L_K2_1 0x495 +#define WCD_USBSS_GND_COEF_L_K3_0 0x496 +#define WCD_USBSS_GND_COEF_L_K3_1 0x497 +#define WCD_USBSS_GND_COEF_L_K4_0 0x498 +#define WCD_USBSS_GND_COEF_L_K4_1 0x499 +#define WCD_USBSS_GND_COEF_L_K5_0 0x49a +#define WCD_USBSS_GND_COEF_L_K5_1 0x49b +#define WCD_USBSS_GND_COEF_R_K0_0 0x49c +#define WCD_USBSS_GND_COEF_R_K0_1 0x49d +#define WCD_USBSS_GND_COEF_R_K0_2 0x49e +#define WCD_USBSS_GND_COEF_R_K1_0 0x49f +#define WCD_USBSS_GND_COEF_R_K1_1 0x4a0 +#define WCD_USBSS_GND_COEF_R_K2_0 0x4a1 +#define WCD_USBSS_GND_COEF_R_K2_1 0x4a2 +#define WCD_USBSS_GND_COEF_R_K3_0 0x4a3 +#define WCD_USBSS_GND_COEF_R_K3_1 0x4a4 +#define WCD_USBSS_GND_COEF_R_K4_0 0x4a5 +#define WCD_USBSS_GND_COEF_R_K4_1 0x4a6 +#define WCD_USBSS_GND_COEF_R_K5_0 0x4a7 +#define WCD_USBSS_GND_COEF_R_K5_1 0x4a8 + +#define WCD_USBSS_MAX_REGISTER 0x4c1 + +struct wcd939x_usbss { + struct i2c_client *client; + struct gpio_desc *reset_gpio; + struct regulator *vdd_supply; + + /* used to serialize concurrent change requests */ + struct mutex lock; + + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + + struct regmap *regmap; + + struct typec_mux *codec; + struct typec_switch *codec_switch; + + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; +}; + +static const struct regmap_range_cfg wcd939x_usbss_ranges[] = { + { + .range_min = 0, + .range_max = WCD_USBSS_MAX_REGISTER, + .selector_reg = 0x0, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config wcd939x_usbss_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = WCD_USBSS_MAX_REGISTER, + .ranges = wcd939x_usbss_ranges, + .num_ranges = ARRAY_SIZE(wcd939x_usbss_ranges), +}; + +/* Linearlizer coefficients for 32ohm load */ +static const struct { + unsigned int offset; + unsigned int mask; + unsigned int value; +} wcd939x_usbss_coeff_init[] = { + { WCD_USBSS_AUD_COEF_L_K5_0, GENMASK(7, 0), 0x39 }, + { WCD_USBSS_AUD_COEF_R_K5_0, GENMASK(7, 0), 0x39 }, + { WCD_USBSS_GND_COEF_L_K2_0, GENMASK(7, 0), 0xe8 }, + { WCD_USBSS_GND_COEF_L_K4_0, GENMASK(7, 0), 0x73 }, + { WCD_USBSS_GND_COEF_R_K2_0, GENMASK(7, 0), 0xe8 }, + { WCD_USBSS_GND_COEF_R_K4_0, GENMASK(7, 0), 0x73 }, + { WCD_USBSS_RATIO_SPKR_REXT_L_LSB, GENMASK(7, 0), 0x00 }, + { WCD_USBSS_RATIO_SPKR_REXT_L_MSB, GENMASK(6, 0), 0x04 }, + { WCD_USBSS_RATIO_SPKR_REXT_R_LSB, GENMASK(7, 0), 0x00 }, + { WCD_USBSS_RATIO_SPKR_REXT_R_MSB, GENMASK(6, 0), 0x04 }, +}; + +static int wcd939x_usbss_set(struct wcd939x_usbss *usbss) +{ + bool reverse = (usbss->orientation == TYPEC_ORIENTATION_REVERSE); + bool enable_audio = false; + bool enable_usb = false; + bool enable_dp = false; + int ret; + + /* USB Mode */ + if (usbss->mode < TYPEC_STATE_MODAL || + (!usbss->svid && (usbss->mode == TYPEC_MODE_USB2 || + usbss->mode == TYPEC_MODE_USB3))) { + enable_usb = true; + } else if (usbss->svid) { + switch (usbss->mode) { + /* DP Only */ + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + enable_dp = true; + break; + + /* DP + USB */ + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: + enable_usb = true; + enable_dp = true; + break; + + default: + return -EOPNOTSUPP; + } + } else if (usbss->mode == TYPEC_MODE_AUDIO) { + enable_audio = true; + } else { + return -EOPNOTSUPP; + } + + /* Disable all switches */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES); + if (ret) + return ret; + + /* Clear switches */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES); + if (ret) + return ret; + + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1, + WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES); + if (ret) + return ret; + + /* Enable OVP_MG1_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG1_BIAS, + WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN); + if (ret) + return ret; + + /* Enable OVP_MG2_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG2_BIAS, + WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN); + if (ret) + return ret; + + /* Disable Equalizer in safe mode */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_EQUALIZER1, + WCD_USBSS_EQUALIZER1_EQ_EN); + if (ret) + return ret; + + /* Start FSM with all disabled, force write */ + ret = regmap_write_bits(usbss->regmap, WCD_USBSS_AUDIO_FSM_START, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG); + + /* 35us to allow the SBU switch to turn off */ + usleep_range(35, 1000); + + /* Setup Audio Accessory mux/switch */ + if (enable_audio) { + int i; + + /* + * AATC switch configuration: + * "Normal": + * - R: DNR + * - L: DNL + * - Sense: GSBU2 + * - Mic: MG1 + * - AGND: MG2 + * "Swapped": + * - R: DNR + * - L: DNL + * - Sense: GSBU1 + * - Mic: MG2 + * - AGND: MG1 + * Swapped information is given by the codec MBHC logic + */ + + /* Set AATC mode */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + FIELD_PREP(WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_AATC)); + if (ret) + return ret; + + /* Select L for DNL_SWITCHES and R for DPR_SWITCHES */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_L) | + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_R)); + if (ret) + return ret; + + if (reverse) + /* Select MG2 for MIC, SBU1 for Sense */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES); + else + /* Select MG1 for MIC, SBU2 for Sense */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES); + if (ret) + return ret; + + if (reverse) + /* Disable OVP_MG1_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_MG1_BIAS, + WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN); + else + /* Disable OVP_MG2_BIAS PCOMP_DYN_BST_EN */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_MG2_BIAS, + WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN); + if (ret) + return ret; + + /* Enable SENSE, MIC switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES); + if (ret) + return ret; + + if (reverse) + /* Select MG1 for AGND_SWITCHES */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1, + WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES); + else + /* Select MG2 for AGND_SWITCHES */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1, + WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES); + if (ret) + return ret; + + /* Enable AGND switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES); + if (ret) + return ret; + + /* Enable DPR, DNL switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES); + if (ret) + return ret; + + /* Setup FSM delays */ + ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_L_SW, 0x02); + if (ret) + return ret; + + ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_R_SW, 0x02); + if (ret) + return ret; + + ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_MIC_SW, 0x01); + if (ret) + return ret; + + /* Start FSM, force write */ + ret = regmap_write_bits(usbss->regmap, WCD_USBSS_AUDIO_FSM_START, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG, + WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG); + if (ret) + return ret; + + /* Default Linearlizer coefficients */ + for (i = 0; i < ARRAY_SIZE(wcd939x_usbss_coeff_init); ++i) + regmap_update_bits(usbss->regmap, + wcd939x_usbss_coeff_init[i].offset, + wcd939x_usbss_coeff_init[i].mask, + wcd939x_usbss_coeff_init[i].value); + + return 0; + } + + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + FIELD_PREP(WCD_USBSS_USB_SS_CNTL_USB_SS_MODE, + WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_USB)); + if (ret) + return ret; + + /* Enable USB muxes */ + if (enable_usb) { + /* Do not enable Equalizer in safe mode */ + if (usbss->mode != TYPEC_STATE_SAFE) { + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_EQUALIZER1, + WCD_USBSS_EQUALIZER1_EQ_EN); + if (ret) + return ret; + } + + /* Select DN for DNL_SWITCHES and DP for DPR_SWITCHES */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN) | + FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DP)); + if (ret) + return ret; + + /* Enable DNL_SWITCHES and DPR_SWITCHES */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES); + if (ret) + return ret; + } + + /* Enable DP AUX muxes */ + if (enable_dp) { + /* Update Leakage Canceller Coefficient for AUXP pins */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_DISP_AUXP_CTL, + WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF, + FIELD_PREP(WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF, + 5)); + if (ret) + return ret; + + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_DISP_AUXP_THRESH, + WCD_USBSS_DISP_AUXP_THRESH_DISP_AUXP_OVPON_CM); + if (ret) + return ret; + + if (reverse) + /* Select MG2 for AUXP and MG1 for AUXM */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES); + else + /* Select MG1 for AUXP and MG2 for AUXM */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0, + WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES | + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES, + WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES); + if (ret) + return ret; + + /* Enable DP_AUXP_TO_MGX and DP_AUXM_TO_MGX switches */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES | + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES); + + /* 15us to allow the SBU switch to turn on again */ + usleep_range(15, 1000); + } + + return 0; +} + +static int wcd939x_usbss_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct wcd939x_usbss *usbss = typec_switch_get_drvdata(sw); + int ret = 0; + + mutex_lock(&usbss->lock); + + if (usbss->orientation != orientation) { + usbss->orientation = orientation; + + ret = wcd939x_usbss_set(usbss); + } + + mutex_unlock(&usbss->lock); + + if (ret) + return ret; + + /* Report orientation to codec after switch has been done */ + return typec_switch_set(usbss->codec_switch, orientation); +} + +static int wcd939x_usbss_mux_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct wcd939x_usbss *usbss = typec_mux_get_drvdata(mux); + int ret = 0; + + mutex_lock(&usbss->lock); + + if (usbss->mode != state->mode) { + usbss->mode = state->mode; + + if (state->alt) + usbss->svid = state->alt->svid; + else + usbss->svid = 0; // No SVID + + ret = wcd939x_usbss_set(usbss); + } + + mutex_unlock(&usbss->lock); + + if (ret) + return ret; + + /* Report event to codec after switch has been done */ + return typec_mux_set(usbss->codec, state); +} + +static int wcd939x_usbss_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct wcd939x_usbss *usbss; + int ret; + + usbss = devm_kzalloc(dev, sizeof(*usbss), GFP_KERNEL); + if (!usbss) + return -ENOMEM; + + usbss->client = client; + mutex_init(&usbss->lock); + + usbss->regmap = devm_regmap_init_i2c(client, &wcd939x_usbss_regmap_config); + if (IS_ERR(usbss->regmap)) + return dev_err_probe(dev, PTR_ERR(usbss->regmap), "failed to initialize regmap\n"); + + usbss->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(usbss->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(usbss->reset_gpio), + "unable to acquire reset gpio\n"); + + usbss->vdd_supply = devm_regulator_get_optional(dev, "vdd"); + if (IS_ERR(usbss->vdd_supply)) + return PTR_ERR(usbss->vdd_supply); + + /* Get Codec's MUX & Switch devices */ + usbss->codec = fwnode_typec_mux_get(dev->fwnode); + if (IS_ERR(usbss->codec)) + return dev_err_probe(dev, PTR_ERR(usbss->codec), + "failed to acquire codec mode-switch\n"); + + usbss->codec_switch = fwnode_typec_switch_get(dev->fwnode); + if (IS_ERR(usbss->codec_switch)) { + ret = dev_err_probe(dev, PTR_ERR(usbss->codec_switch), + "failed to acquire codec orientation-switch\n"); + goto err_mux_put; + } + + usbss->mode = TYPEC_STATE_SAFE; + usbss->orientation = TYPEC_ORIENTATION_NONE; + + gpiod_set_value(usbss->reset_gpio, 1); + + ret = regulator_enable(usbss->vdd_supply); + if (ret) { + dev_err(dev, "Failed to enable vdd: %d\n", ret); + goto err_mux_switch; + } + + msleep(20); + + gpiod_set_value(usbss->reset_gpio, 0); + + msleep(20); + + /* Disable standby */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_STANDBY_STATE); + if (ret) + goto err_regulator_disable; + + /* Set manual mode by default */ + ret = regmap_update_bits(usbss->regmap, WCD_USBSS_FUNCTION_ENABLE, + WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT, + FIELD_PREP(WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT, + WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_MANUAL)); + if (ret) + goto err_regulator_disable; + + /* Enable dynamic boosting for DP and DN */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_DP_DN_MISC1, + WCD_USBSS_DP_DN_MISC1_DP_PCOMP_2X_DYN_BST_ON_EN | + WCD_USBSS_DP_DN_MISC1_DN_PCOMP_2X_DYN_BST_ON_EN); + if (ret) + goto err_regulator_disable; + + /* Enable dynamic boosting for MG1 OVP */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG1_MISC, + WCD_USBSS_MG1_MISC_PCOMP_2X_DYN_BST_ON_EN); + if (ret) + goto err_regulator_disable; + + /* Enable dynamic boosting for MG2 OVP */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG2_MISC, + WCD_USBSS_MG2_MISC_PCOMP_2X_DYN_BST_ON_EN); + if (ret) + goto err_regulator_disable; + + /* Write 0xFF to WCD_USBSS_CPLDO_CTL2 */ + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_CPLDO_CTL2, 0xff); + if (ret) + goto err_regulator_disable; + + /* Set RCO_EN: WCD_USBSS_USB_SS_CNTL Bit<3> --> 0x0 --> 0x1 */ + ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_RCO_EN); + if (ret) + goto err_regulator_disable; + + ret = regmap_set_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL, + WCD_USBSS_USB_SS_CNTL_RCO_EN); + if (ret) + goto err_regulator_disable; + + /* Disable all switches but enable the mux */ + ret = regmap_write(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE, + WCD_USBSS_SWITCH_SETTINGS_ENABLE_DEVICE_ENABLE); + if (ret) + goto err_regulator_disable; + + /* Setup in SAFE mode */ + ret = wcd939x_usbss_set(usbss); + if (ret) + goto err_regulator_disable; + + sw_desc.drvdata = usbss; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = wcd939x_usbss_switch_set; + + usbss->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(usbss->sw)) { + ret = dev_err_probe(dev, PTR_ERR(usbss->sw), "failed to register typec switch\n"); + goto err_regulator_disable; + } + + mux_desc.drvdata = usbss; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = wcd939x_usbss_mux_set; + + usbss->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(usbss->mux)) { + ret = dev_err_probe(dev, PTR_ERR(usbss->mux), "failed to register typec mux\n"); + goto err_switch_unregister; + } + + i2c_set_clientdata(client, usbss); + + return 0; + +err_switch_unregister: + typec_switch_unregister(usbss->sw); + +err_regulator_disable: + regulator_disable(usbss->vdd_supply); + +err_mux_switch: + typec_switch_put(usbss->codec_switch); + +err_mux_put: + typec_mux_put(usbss->codec); + + return ret; +} + +static void wcd939x_usbss_remove(struct i2c_client *client) +{ + struct wcd939x_usbss *usbss = i2c_get_clientdata(client); + + typec_mux_unregister(usbss->mux); + typec_switch_unregister(usbss->sw); + + regulator_disable(usbss->vdd_supply); + + typec_switch_put(usbss->codec_switch); + typec_mux_put(usbss->codec); +} + +static const struct i2c_device_id wcd939x_usbss_table[] = { + { "wcd9390-usbss" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wcd939x_usbss_table); + +static const struct of_device_id wcd939x_usbss_of_table[] = { + { .compatible = "qcom,wcd9390-usbss" }, + { } +}; +MODULE_DEVICE_TABLE(of, wcd939x_usbss_of_table); + +static struct i2c_driver wcd939x_usbss_driver = { + .driver = { + .name = "wcd939x-usbss", + .of_match_table = wcd939x_usbss_of_table, + }, + .probe = wcd939x_usbss_probe, + .remove = wcd939x_usbss_remove, + .id_table = wcd939x_usbss_table, +}; +module_i2c_driver(wcd939x_usbss_driver); + +MODULE_DESCRIPTION("Qualcomm WCD939x USBSS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c index 85d015cdbe..b9cca2be76 100644 --- a/drivers/usb/typec/pd.c +++ b/drivers/usb/typec/pd.c @@ -468,7 +468,7 @@ static struct device_type pd_capabilities_type = { /** * usb_power_delivery_register_capabilities - Register a set of capabilities. * @pd: The USB PD instance that the capabilities belong to. - * @desc: Description of the Capablities Message. + * @desc: Description of the Capabilities Message. * * This function registers a Capabilities Message described in @desc. The * capabilities will have their own sub-directory under @pd in sysfs. @@ -571,7 +571,7 @@ static void pd_release(struct device *dev) { struct usb_power_delivery *pd = to_usb_power_delivery(dev); - ida_simple_remove(&pd_ida, pd->id); + ida_free(&pd_ida, pd->id); kfree(pd); } @@ -616,7 +616,7 @@ usb_power_delivery_register(struct device *parent, struct usb_power_delivery_des if (!pd) return ERR_PTR(-ENOMEM); - ret = ida_simple_get(&pd_ida, 0, 0, GFP_KERNEL); + ret = ida_alloc(&pd_ida, GFP_KERNEL); if (ret < 0) { kfree(pd); return ERR_PTR(ret); diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig index 0b2993fef5..8cdd84ca5d 100644 --- a/drivers/usb/typec/tcpm/Kconfig +++ b/drivers/usb/typec/tcpm/Kconfig @@ -80,6 +80,7 @@ config TYPEC_QCOM_PMIC tristate "Qualcomm PMIC USB Type-C Port Controller Manager driver" depends on ARCH_QCOM || COMPILE_TEST depends on DRM || DRM=n + select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF help A Type-C port and Power Delivery driver which aggregates two discrete pieces of silicon in the PM8150b PMIC block: the diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c index 581199d37b..1a2b4bddaa 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c @@ -18,7 +18,7 @@ #include <linux/usb/tcpm.h> #include <linux/usb/typec_mux.h> -#include <drm/drm_bridge.h> +#include <drm/bridge/aux-bridge.h> #include "qcom_pmic_typec_pdphy.h" #include "qcom_pmic_typec_port.h" @@ -36,7 +36,6 @@ struct pmic_typec { struct pmic_typec_port *pmic_typec_port; bool vbus_enabled; struct mutex lock; /* VBUS state serialization */ - struct drm_bridge bridge; }; #define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) @@ -150,35 +149,6 @@ static int qcom_pmic_typec_init(struct tcpc_dev *tcpc) return 0; } -#if IS_ENABLED(CONFIG_DRM) -static int qcom_pmic_typec_attach(struct drm_bridge *bridge, - enum drm_bridge_attach_flags flags) -{ - return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL; -} - -static const struct drm_bridge_funcs qcom_pmic_typec_bridge_funcs = { - .attach = qcom_pmic_typec_attach, -}; - -static int qcom_pmic_typec_init_drm(struct pmic_typec *tcpm) -{ - tcpm->bridge.funcs = &qcom_pmic_typec_bridge_funcs; -#ifdef CONFIG_OF - tcpm->bridge.of_node = of_get_child_by_name(tcpm->dev->of_node, "connector"); -#endif - tcpm->bridge.ops = DRM_BRIDGE_OP_HPD; - tcpm->bridge.type = DRM_MODE_CONNECTOR_DisplayPort; - - return devm_drm_bridge_add(tcpm->dev, &tcpm->bridge); -} -#else -static int qcom_pmic_typec_init_drm(struct pmic_typec *tcpm) -{ - return 0; -} -#endif - static int qcom_pmic_typec_probe(struct platform_device *pdev) { struct pmic_typec *tcpm; @@ -186,6 +156,7 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) struct device_node *np = dev->of_node; const struct pmic_typec_resources *res; struct regmap *regmap; + struct device *bridge_dev; u32 base[2]; int ret; @@ -241,14 +212,14 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) mutex_init(&tcpm->lock); platform_set_drvdata(pdev, tcpm); - ret = qcom_pmic_typec_init_drm(tcpm); - if (ret) - return ret; - tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); if (!tcpm->tcpc.fwnode) return -EINVAL; + bridge_dev = drm_dp_hpd_bridge_register(tcpm->dev, to_of_node(tcpm->tcpc.fwnode)); + if (IS_ERR(bridge_dev)) + return PTR_ERR(bridge_dev); + tcpm->tcpm_port = tcpm_register_port(tcpm->dev, &tcpm->tcpc); if (IS_ERR(tcpm->tcpm_port)) { ret = PTR_ERR(tcpm->tcpm_port); diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 0ee3e6e29b..7118551827 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -889,6 +889,7 @@ MODULE_DEVICE_TABLE(i2c, tcpci_id); #ifdef CONFIG_OF static const struct of_device_id tcpci_of_match[] = { { .compatible = "nxp,ptn5110", }, + { .compatible = "tcpci", }, {}, }; MODULE_DEVICE_TABLE(of, tcpci_of_match); diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 9454b12a07..7fb966fd63 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -92,11 +92,16 @@ static void max_tcpci_init_regs(struct max_tcpci_chip *chip) return; } + /* Vconn Over Current Protection */ + ret = max_tcpci_write8(chip, TCPC_FAULT_STATUS_MASK, TCPC_FAULT_STATUS_MASK_VCONN_OC); + if (ret < 0) + return; + alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED | TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS | TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS | /* Enable Extended alert for detecting Fast Role Swap Signal */ - TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS; + TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS | TCPC_ALERT_FAULT; ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask); if (ret < 0) { @@ -295,6 +300,19 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) } } + if (status & TCPC_ALERT_FAULT) { + ret = max_tcpci_read8(chip, TCPC_FAULT_STATUS, ®_status); + if (ret < 0) + return ret; + + ret = max_tcpci_write8(chip, TCPC_FAULT_STATUS, reg_status); + if (ret < 0) + return ret; + + if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) + tcpm_port_error_recovery(chip->port); + } + if (status & TCPC_ALERT_EXTND) { ret = max_tcpci_read8(chip, TCPC_ALERT_EXTENDED, ®_status); if (ret < 0) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index c5776b3c96..df9a5d6760 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -251,6 +251,7 @@ enum frs_typec_current { #define TCPM_FRS_EVENT BIT(3) #define TCPM_SOURCING_VBUS BIT(4) #define TCPM_PORT_CLEAN BIT(5) +#define TCPM_PORT_ERROR BIT(6) #define LOG_BUFFER_ENTRIES 1024 #define LOG_BUFFER_ENTRY_SIZE 128 @@ -296,6 +297,15 @@ struct pd_pps_data { bool active; }; +struct pd_data { + struct usb_power_delivery *pd; + struct usb_power_delivery_capabilities *source_cap; + struct usb_power_delivery_capabilities_desc source_desc; + struct usb_power_delivery_capabilities *sink_cap; + struct usb_power_delivery_capabilities_desc sink_desc; + unsigned int operating_snk_mw; +}; + struct tcpm_port { struct device *dev; @@ -397,12 +407,14 @@ struct tcpm_port { unsigned int rx_msgid; /* USB PD objects */ - struct usb_power_delivery *pd; + struct usb_power_delivery **pds; + struct pd_data **pd_list; struct usb_power_delivery_capabilities *port_source_caps; struct usb_power_delivery_capabilities *port_sink_caps; struct usb_power_delivery *partner_pd; struct usb_power_delivery_capabilities *partner_source_caps; struct usb_power_delivery_capabilities *partner_sink_caps; + struct usb_power_delivery *selected_pd; /* Partner capabilities/requests */ u32 sink_request; @@ -412,6 +424,7 @@ struct tcpm_port { unsigned int nr_sink_caps; /* Local capabilities */ + unsigned int pd_count; u32 src_pdo[PDO_MAX_OBJECTS]; unsigned int nr_src_pdo; u32 snk_pdo[PDO_MAX_OBJECTS]; @@ -2847,7 +2860,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); } else { - if (port->send_discover) { + if (port->send_discover && port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -2863,7 +2876,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); } else { - if (port->send_discover) { + if (port->send_discover && port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -2872,7 +2885,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, } break; case PD_CTRL_VCONN_SWAP: - if (port->send_discover) { + if (port->send_discover && port->negotiated_rev < PD_REV30) { tcpm_queue_message(port, PD_MSG_CTRL_WAIT); break; } @@ -4398,7 +4411,8 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000); tcpm_swap_complete(port, 0); tcpm_typec_connect(port); - mod_enable_frs_delayed_work(port, 0); + if (port->pd_capable && port->source_caps[0] & PDO_FIXED_DUAL_ROLE) + mod_enable_frs_delayed_work(port, 0); tcpm_pps_complete(port, port->pps_status); if (port->ams != NONE_AMS) @@ -5487,6 +5501,10 @@ static void tcpm_pd_event_handler(struct kthread_work *work) tcpm_set_state(port, tcpm_default_state(port), 0); } } + if (events & TCPM_PORT_ERROR) { + tcpm_log(port, "port triggering error recovery"); + tcpm_set_state(port, ERROR_RECOVERY, 0); + } spin_lock(&port->pd_event_lock); } @@ -5554,6 +5572,15 @@ bool tcpm_port_is_toggling(struct tcpm_port *port) } EXPORT_SYMBOL_GPL(tcpm_port_is_toggling); +void tcpm_port_error_recovery(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_PORT_ERROR; + spin_unlock(&port->pd_event_lock); + kthread_queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_port_error_recovery); + static void tcpm_enable_frs_work(struct kthread_work *work) { struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs); @@ -6045,12 +6072,116 @@ port_unlock: return 0; } +static struct pd_data *tcpm_find_pd_data(struct tcpm_port *port, struct usb_power_delivery *pd) +{ + int i; + + for (i = 0; port->pd_list[i]; i++) { + if (port->pd_list[i]->pd == pd) + return port->pd_list[i]; + } + + return ERR_PTR(-ENODATA); +} + +static struct usb_power_delivery **tcpm_pd_get(struct typec_port *p) +{ + struct tcpm_port *port = typec_get_drvdata(p); + + return port->pds; +} + +static int tcpm_pd_set(struct typec_port *p, struct usb_power_delivery *pd) +{ + struct tcpm_port *port = typec_get_drvdata(p); + struct pd_data *data; + int i, ret = 0; + + mutex_lock(&port->lock); + + if (port->selected_pd == pd) + goto unlock; + + data = tcpm_find_pd_data(port, pd); + if (IS_ERR(data)) { + ret = PTR_ERR(data); + goto unlock; + } + + if (data->sink_desc.pdo[0]) { + for (i = 0; i < PDO_MAX_OBJECTS && data->sink_desc.pdo[i]; i++) + port->snk_pdo[i] = data->sink_desc.pdo[i]; + port->nr_snk_pdo = i; + port->operating_snk_mw = data->operating_snk_mw; + } + + if (data->source_desc.pdo[0]) { + for (i = 0; i < PDO_MAX_OBJECTS && data->source_desc.pdo[i]; i++) + port->src_pdo[i] = data->source_desc.pdo[i]; + port->nr_src_pdo = i; + } + + switch (port->state) { + case SRC_UNATTACHED: + case SRC_ATTACH_WAIT: + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + break; + case SRC_SEND_CAPABILITIES: + case SRC_SEND_CAPABILITIES_TIMEOUT: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + port->caps_count = 0; + port->upcoming_state = SRC_SEND_CAPABILITIES; + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto unlock; + } + break; + case SNK_NEGOTIATE_CAPABILITIES: + case SNK_NEGOTIATE_PPS_CAPABILITIES: + case SNK_READY: + case SNK_TRANSITION_SINK: + case SNK_TRANSITION_SINK_VBUS: + if (port->pps_data.active) + port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES; + else if (port->pd_capable) + port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES; + else + break; + + port->update_sink_caps = true; + + ret = tcpm_ams_start(port, POWER_NEGOTIATION); + if (ret == -EAGAIN) { + port->upcoming_state = INVALID_STATE; + goto unlock; + } + break; + default: + break; + } + + port->port_source_caps = data->source_cap; + port->port_sink_caps = data->sink_cap; + typec_port_set_usb_power_delivery(p, NULL); + port->selected_pd = pd; + typec_port_set_usb_power_delivery(p, port->selected_pd); +unlock: + mutex_unlock(&port->lock); + return ret; +} + static const struct typec_operations tcpm_ops = { .try_role = tcpm_try_role, .dr_set = tcpm_dr_set, .pr_set = tcpm_pr_set, .vconn_set = tcpm_vconn_set, - .port_type_set = tcpm_port_type_set + .port_type_set = tcpm_port_type_set, + .pd_get = tcpm_pd_get, + .pd_set = tcpm_pd_set }; void tcpm_tcpc_reset(struct tcpm_port *port) @@ -6064,58 +6195,61 @@ EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); static void tcpm_port_unregister_pd(struct tcpm_port *port) { - usb_power_delivery_unregister_capabilities(port->port_sink_caps); + int i; + port->port_sink_caps = NULL; - usb_power_delivery_unregister_capabilities(port->port_source_caps); port->port_source_caps = NULL; - usb_power_delivery_unregister(port->pd); - port->pd = NULL; + for (i = 0; i < port->pd_count; i++) { + usb_power_delivery_unregister_capabilities(port->pd_list[i]->sink_cap); + usb_power_delivery_unregister_capabilities(port->pd_list[i]->source_cap); + devm_kfree(port->dev, port->pd_list[i]); + port->pd_list[i] = NULL; + usb_power_delivery_unregister(port->pds[i]); + port->pds[i] = NULL; + } } static int tcpm_port_register_pd(struct tcpm_port *port) { struct usb_power_delivery_desc desc = { port->typec_caps.pd_revision }; - struct usb_power_delivery_capabilities_desc caps = { }; struct usb_power_delivery_capabilities *cap; - int ret; + int ret, i; if (!port->nr_src_pdo && !port->nr_snk_pdo) return 0; - port->pd = usb_power_delivery_register(port->dev, &desc); - if (IS_ERR(port->pd)) { - ret = PTR_ERR(port->pd); - goto err_unregister; - } - - if (port->nr_src_pdo) { - memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->src_pdo, - port->nr_src_pdo * sizeof(u32), 0); - caps.role = TYPEC_SOURCE; - - cap = usb_power_delivery_register_capabilities(port->pd, &caps); - if (IS_ERR(cap)) { - ret = PTR_ERR(cap); + for (i = 0; i < port->pd_count; i++) { + port->pds[i] = usb_power_delivery_register(port->dev, &desc); + if (IS_ERR(port->pds[i])) { + ret = PTR_ERR(port->pds[i]); goto err_unregister; } - - port->port_source_caps = cap; - } - - if (port->nr_snk_pdo) { - memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->snk_pdo, - port->nr_snk_pdo * sizeof(u32), 0); - caps.role = TYPEC_SINK; - - cap = usb_power_delivery_register_capabilities(port->pd, &caps); - if (IS_ERR(cap)) { - ret = PTR_ERR(cap); - goto err_unregister; + port->pd_list[i]->pd = port->pds[i]; + + if (port->pd_list[i]->source_desc.pdo[0]) { + cap = usb_power_delivery_register_capabilities(port->pds[i], + &port->pd_list[i]->source_desc); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + port->pd_list[i]->source_cap = cap; } - port->port_sink_caps = cap; + if (port->pd_list[i]->sink_desc.pdo[0]) { + cap = usb_power_delivery_register_capabilities(port->pds[i], + &port->pd_list[i]->sink_desc); + if (IS_ERR(cap)) { + ret = PTR_ERR(cap); + goto err_unregister; + } + port->pd_list[i]->sink_cap = cap; + } } + port->port_source_caps = port->pd_list[0]->source_cap; + port->port_sink_caps = port->pd_list[0]->sink_cap; + port->selected_pd = port->pds[0]; return 0; err_unregister: @@ -6124,12 +6258,15 @@ err_unregister: return ret; } -static int tcpm_fw_get_caps(struct tcpm_port *port, - struct fwnode_handle *fwnode) +static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode) { + struct fwnode_handle *capabilities, *child, *caps = NULL; + unsigned int nr_src_pdo, nr_snk_pdo; const char *opmode_str; - int ret; - u32 mw, frs_current; + u32 *src_pdo, *snk_pdo; + u32 uw, frs_current; + int ret = 0, i; + int mode; if (!fwnode) return -EINVAL; @@ -6147,30 +6284,20 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, if (ret < 0) return ret; + mode = 0; + + if (fwnode_property_read_bool(fwnode, "accessory-mode-audio")) + port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_AUDIO; + + if (fwnode_property_read_bool(fwnode, "accessory-mode-debug")) + port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_DEBUG; + port->port_type = port->typec_caps.type; port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable"); - port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop"); - if (port->port_type == TYPEC_PORT_SNK) - goto sink; - - /* Get Source PDOs for the PD port or Source Rp value for the non-PD port */ - if (port->pd_supported) { - ret = fwnode_property_count_u32(fwnode, "source-pdos"); - if (ret == 0) - return -EINVAL; - else if (ret < 0) - return ret; + port->self_powered = fwnode_property_read_bool(fwnode, "self-powered"); - port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS); - ret = fwnode_property_read_u32_array(fwnode, "source-pdos", - port->src_pdo, port->nr_src_pdo); - if (ret) - return ret; - ret = tcpm_validate_caps(port, port->src_pdo, port->nr_src_pdo); - if (ret) - return ret; - } else { + if (!port->pd_supported) { ret = fwnode_property_read_string(fwnode, "typec-power-opmode", &opmode_str); if (ret) return ret; @@ -6178,45 +6305,150 @@ static int tcpm_fw_get_caps(struct tcpm_port *port, if (ret < 0) return ret; port->src_rp = tcpm_pwr_opmode_to_rp(ret); - } - - if (port->port_type == TYPEC_PORT_SRC) return 0; + } -sink: - port->self_powered = fwnode_property_read_bool(fwnode, "self-powered"); - - if (!port->pd_supported) - return 0; - - /* Get sink pdos */ - ret = fwnode_property_count_u32(fwnode, "sink-pdos"); - if (ret <= 0) - return -EINVAL; - - port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS); - ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", - port->snk_pdo, port->nr_snk_pdo); - if ((ret < 0) || tcpm_validate_caps(port, port->snk_pdo, - port->nr_snk_pdo)) - return -EINVAL; - - if (fwnode_property_read_u32(fwnode, "op-sink-microwatt", &mw) < 0) - return -EINVAL; - port->operating_snk_mw = mw / 1000; + /* The following code are applicable to pd-capable ports, i.e. pd_supported is true. */ /* FRS can only be supported by DRP ports */ if (port->port_type == TYPEC_PORT_DRP) { ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current", &frs_current); - if (ret >= 0 && frs_current <= FRS_5V_3A) + if (!ret && frs_current <= FRS_5V_3A) port->new_source_frs_current = frs_current; + + if (ret) + ret = 0; } + /* For the backward compatibility, "capabilities" node is optional. */ + capabilities = fwnode_get_named_child_node(fwnode, "capabilities"); + if (!capabilities) { + port->pd_count = 1; + } else { + fwnode_for_each_child_node(capabilities, child) + port->pd_count++; + + if (!port->pd_count) { + ret = -ENODATA; + goto put_capabilities; + } + } + + port->pds = devm_kcalloc(port->dev, port->pd_count, sizeof(struct usb_power_delivery *), + GFP_KERNEL); + if (!port->pds) { + ret = -ENOMEM; + goto put_capabilities; + } + + port->pd_list = devm_kcalloc(port->dev, port->pd_count, sizeof(struct pd_data *), + GFP_KERNEL); + if (!port->pd_list) { + ret = -ENOMEM; + goto put_capabilities; + } + + for (i = 0; i < port->pd_count; i++) { + port->pd_list[i] = devm_kzalloc(port->dev, sizeof(struct pd_data), GFP_KERNEL); + if (!port->pd_list[i]) { + ret = -ENOMEM; + goto put_capabilities; + } + + src_pdo = port->pd_list[i]->source_desc.pdo; + port->pd_list[i]->source_desc.role = TYPEC_SOURCE; + snk_pdo = port->pd_list[i]->sink_desc.pdo; + port->pd_list[i]->sink_desc.role = TYPEC_SINK; + + /* If "capabilities" is NULL, fall back to single pd cap population. */ + if (!capabilities) + caps = fwnode; + else + caps = fwnode_get_next_child_node(capabilities, caps); + + if (port->port_type != TYPEC_PORT_SNK) { + ret = fwnode_property_count_u32(caps, "source-pdos"); + if (ret == 0) { + ret = -EINVAL; + goto put_caps; + } + if (ret < 0) + goto put_caps; + + nr_src_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(caps, "source-pdos", src_pdo, + nr_src_pdo); + if (ret) + goto put_caps; + + ret = tcpm_validate_caps(port, src_pdo, nr_src_pdo); + if (ret) + goto put_caps; + + if (i == 0) { + port->nr_src_pdo = nr_src_pdo; + memcpy_and_pad(port->src_pdo, sizeof(u32) * PDO_MAX_OBJECTS, + port->pd_list[0]->source_desc.pdo, + sizeof(u32) * nr_src_pdo, + 0); + } + } + + if (port->port_type != TYPEC_PORT_SRC) { + ret = fwnode_property_count_u32(caps, "sink-pdos"); + if (ret == 0) { + ret = -EINVAL; + goto put_caps; + } + + if (ret < 0) + goto put_caps; + + nr_snk_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(caps, "sink-pdos", snk_pdo, + nr_snk_pdo); + if (ret) + goto put_caps; + + ret = tcpm_validate_caps(port, snk_pdo, nr_snk_pdo); + if (ret) + goto put_caps; + + if (fwnode_property_read_u32(caps, "op-sink-microwatt", &uw) < 0) { + ret = -EINVAL; + goto put_caps; + } + + port->pd_list[i]->operating_snk_mw = uw / 1000; + + if (i == 0) { + port->nr_snk_pdo = nr_snk_pdo; + memcpy_and_pad(port->snk_pdo, sizeof(u32) * PDO_MAX_OBJECTS, + port->pd_list[0]->sink_desc.pdo, + sizeof(u32) * nr_snk_pdo, + 0); + port->operating_snk_mw = port->pd_list[0]->operating_snk_mw; + } + } + } + +put_caps: + if (caps != fwnode) + fwnode_handle_put(caps); +put_capabilities: + fwnode_handle_put(capabilities); + return ret; +} + +static int tcpm_fw_get_snk_vdos(struct tcpm_port *port, struct fwnode_handle *fwnode) +{ + int ret; + /* sink-vdos is optional */ ret = fwnode_property_count_u32(fwnode, "sink-vdos"); if (ret < 0) - ret = 0; + return 0; port->nr_snk_vdo = min(ret, VDO_MAX_OBJECTS); if (port->nr_snk_vdo) { @@ -6584,10 +6816,12 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) err = tcpm_fw_get_caps(port, tcpc->fwnode); if (err < 0) goto out_destroy_wq; + err = tcpm_fw_get_snk_vdos(port, tcpc->fwnode); + if (err < 0) + goto out_destroy_wq; port->try_role = port->typec_caps.prefer_role; - port->typec_caps.fwnode = tcpc->fwnode; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ port->typec_caps.svdm_version = SVDM_VER_2_0; @@ -6596,7 +6830,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.orientation_aware = 1; port->partner_desc.identity = &port->partner_ident; - port->port_type = port->typec_caps.type; port->role_sw = usb_role_switch_get(port->dev); if (!port->role_sw) @@ -6615,7 +6848,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) if (err) goto out_role_sw_put; - port->typec_caps.pd = port->pd; + if (port->pds) + port->typec_caps.pd = port->pds[0]; port->typec_port = typec_register_port(port->dev, &port->typec_caps); if (IS_ERR(port->typec_port)) { diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 196535ad99..0717cfcd9f 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -8,6 +8,7 @@ #include <linux/i2c.h> #include <linux/acpi.h> +#include <linux/gpio/consumer.h> #include <linux/module.h> #include <linux/of.h> #include <linux/power_supply.h> @@ -64,6 +65,9 @@ #define TPS_PBMC_RC 0 /* Return code */ #define TPS_PBMC_DPCS 2 /* device patch complete status */ +/* reset de-assertion to ready for operation */ +#define TPS_SETUP_MS 1000 + enum { TPS_PORTINFO_SINK, TPS_PORTINFO_SINK_ACCESSORY, @@ -111,6 +115,8 @@ struct tipd_data { void (*trace_power_status)(u16 status); void (*trace_status)(u32 status); int (*apply_patch)(struct tps6598x *tps); + int (*init)(struct tps6598x *tps); + int (*reset)(struct tps6598x *tps); }; struct tps6598x { @@ -119,6 +125,7 @@ struct tps6598x { struct mutex lock; /* device lock */ u8 i2c_protocol:1; + struct gpio_desc *reset; struct typec_port *port; struct typec_partner *partner; struct usb_pd_identity partner_identity; @@ -323,7 +330,7 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status) } static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd, - size_t in_len, u8 *in_data, + size_t in_len, const u8 *in_data, size_t out_len, u8 *out_data, u32 cmd_timeout_ms, u32 res_delay_ms) { @@ -389,7 +396,7 @@ static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd, } static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, - size_t in_len, u8 *in_data, + size_t in_len, const u8 *in_data, size_t out_len, u8 *out_data) { return tps6598x_exec_cmd_tmo(tps, cmd, in_len, in_data, @@ -866,6 +873,30 @@ tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) return 0; } +static int tps_request_firmware(struct tps6598x *tps, const struct firmware **fw) +{ + const char *firmware_name; + int ret; + + ret = device_property_read_string(tps->dev, "firmware-name", + &firmware_name); + if (ret) + return ret; + + ret = request_firmware(fw, firmware_name, tps->dev); + if (ret) { + dev_err(tps->dev, "failed to retrieve \"%s\"\n", firmware_name); + return ret; + } + + if ((*fw)->size == 0) { + release_firmware(*fw); + ret = -EINVAL; + } + + return ret; +} + static int tps25750_write_firmware(struct tps6598x *tps, u8 bpms_addr, const u8 *data, size_t len) @@ -954,16 +985,9 @@ static int tps25750_start_patch_burst_mode(struct tps6598x *tps) if (ret) return ret; - ret = request_firmware(&fw, firmware_name, tps->dev); - if (ret) { - dev_err(tps->dev, "failed to retrieve \"%s\"\n", firmware_name); + ret = tps_request_firmware(tps, &fw); + if (ret) return ret; - } - - if (fw->size == 0) { - ret = -EINVAL; - goto release_fw; - } ret = of_property_match_string(np, "reg-names", "patch-address"); if (ret < 0) { @@ -1101,6 +1125,76 @@ wait_for_app: return 0; }; +static int tps6598x_apply_patch(struct tps6598x *tps) +{ + u8 in = TPS_PTCS_CONTENT_DEV | TPS_PTCS_CONTENT_APP; + u8 out[TPS_MAX_LEN] = {0}; + size_t in_len = sizeof(in); + size_t copied_bytes = 0; + size_t bytes_left; + const struct firmware *fw; + const char *firmware_name; + int ret; + + ret = device_property_read_string(tps->dev, "firmware-name", + &firmware_name); + if (ret) + return ret; + + ret = tps_request_firmware(tps, &fw); + if (ret) + return ret; + + ret = tps6598x_exec_cmd(tps, "PTCs", in_len, &in, + TPS_PTCS_OUT_BYTES, out); + if (ret || out[TPS_PTCS_STATUS] == TPS_PTCS_STATUS_FAIL) { + if (!ret) + ret = -EBUSY; + dev_err(tps->dev, "Update start failed (%d)\n", ret); + goto release_fw; + } + + bytes_left = fw->size; + while (bytes_left) { + if (bytes_left < TPS_MAX_LEN) + in_len = bytes_left; + else + in_len = TPS_MAX_LEN; + ret = tps6598x_exec_cmd(tps, "PTCd", in_len, + fw->data + copied_bytes, + TPS_PTCD_OUT_BYTES, out); + if (ret || out[TPS_PTCD_TRANSFER_STATUS] || + out[TPS_PTCD_LOADING_STATE] == TPS_PTCD_LOAD_ERR) { + if (!ret) + ret = -EBUSY; + dev_err(tps->dev, "Patch download failed (%d)\n", ret); + goto release_fw; + } + copied_bytes += in_len; + bytes_left -= in_len; + } + + ret = tps6598x_exec_cmd(tps, "PTCc", 0, NULL, TPS_PTCC_OUT_BYTES, out); + if (ret || out[TPS_PTCC_DEV] || out[TPS_PTCC_APP]) { + if (!ret) + ret = -EBUSY; + dev_err(tps->dev, "Update completion failed (%d)\n", ret); + goto release_fw; + } + msleep(TPS_SETUP_MS); + dev_info(tps->dev, "Firmware update succeeded\n"); + +release_fw: + release_firmware(fw); + + return ret; +}; + +static int cd321x_init(struct tps6598x *tps) +{ + return 0; +} + static int tps25750_init(struct tps6598x *tps) { int ret; @@ -1119,6 +1213,26 @@ static int tps25750_init(struct tps6598x *tps) return 0; } +static int tps6598x_init(struct tps6598x *tps) +{ + return tps->data->apply_patch(tps); +} + +static int cd321x_reset(struct tps6598x *tps) +{ + return 0; +} + +static int tps25750_reset(struct tps6598x *tps) +{ + return tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0); +} + +static int tps6598x_reset(struct tps6598x *tps) +{ + return 0; +} + static int tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) { @@ -1182,7 +1296,6 @@ static int tps6598x_probe(struct i2c_client *client) u32 vid; int ret; u64 mask1; - bool is_tps25750; tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); if (!tps) @@ -1191,12 +1304,18 @@ static int tps6598x_probe(struct i2c_client *client) mutex_init(&tps->lock); tps->dev = &client->dev; + tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(tps->reset)) + return dev_err_probe(tps->dev, PTR_ERR(tps->reset), + "failed to get reset GPIO\n"); + if (tps->reset) + msleep(TPS_SETUP_MS); + tps->regmap = devm_regmap_init_i2c(client, &tps6598x_regmap_config); if (IS_ERR(tps->regmap)) return PTR_ERR(tps->regmap); - is_tps25750 = device_is_compatible(tps->dev, "ti,tps25750"); - if (!is_tps25750) { + if (!device_is_compatible(tps->dev, "ti,tps25750")) { ret = tps6598x_read32(tps, TPS_REG_VID, &vid); if (ret < 0 || !vid) return -ENODEV; @@ -1239,8 +1358,8 @@ static int tps6598x_probe(struct i2c_client *client) if (ret < 0) return ret; - if (is_tps25750 && ret == TPS_MODE_PTCH) { - ret = tps25750_init(tps); + if (ret == TPS_MODE_PTCH) { + ret = tps->data->init(tps); if (ret) return ret; } @@ -1328,8 +1447,8 @@ err_clear_mask: tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); err_reset_controller: /* Reset PD controller to remove any applied patch */ - if (is_tps25750) - tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0); + tps->data->reset(tps); + return ret; } @@ -1346,8 +1465,10 @@ static void tps6598x_remove(struct i2c_client *client) usb_role_switch_put(tps->role_sw); /* Reset PD controller to remove any applied patch */ - if (device_is_compatible(tps->dev, "ti,tps25750")) - tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0); + tps->data->reset(tps); + + if (tps->reset) + gpiod_set_value_cansleep(tps->reset, 1); } static int __maybe_unused tps6598x_suspend(struct device *dev) @@ -1358,6 +1479,8 @@ static int __maybe_unused tps6598x_suspend(struct device *dev) if (tps->wakeup) { disable_irq(client->irq); enable_irq_wake(client->irq); + } else if (tps->reset) { + gpiod_set_value_cansleep(tps->reset, 1); } if (!client->irq) @@ -1376,8 +1499,8 @@ static int __maybe_unused tps6598x_resume(struct device *dev) if (ret < 0) return ret; - if (device_is_compatible(tps->dev, "ti,tps25750") && ret == TPS_MODE_PTCH) { - ret = tps25750_init(tps); + if (ret == TPS_MODE_PTCH) { + ret = tps->data->init(tps); if (ret) return ret; } @@ -1385,6 +1508,9 @@ static int __maybe_unused tps6598x_resume(struct device *dev) if (tps->wakeup) { disable_irq_wake(client->irq); enable_irq(client->irq); + } else if (tps->reset) { + gpiod_set_value_cansleep(tps->reset, 0); + msleep(TPS_SETUP_MS); } if (!client->irq) @@ -1403,6 +1529,8 @@ static const struct tipd_data cd321x_data = { .register_port = tps6598x_register_port, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, + .init = cd321x_init, + .reset = cd321x_reset, }; static const struct tipd_data tps6598x_data = { @@ -1410,6 +1538,9 @@ static const struct tipd_data tps6598x_data = { .register_port = tps6598x_register_port, .trace_power_status = trace_tps6598x_power_status, .trace_status = trace_tps6598x_status, + .apply_patch = tps6598x_apply_patch, + .init = tps6598x_init, + .reset = tps6598x_reset, }; static const struct tipd_data tps25750_data = { @@ -1418,6 +1549,8 @@ static const struct tipd_data tps25750_data = { .trace_power_status = trace_tps25750_power_status, .trace_status = trace_tps25750_status, .apply_patch = tps25750_apply_patch, + .init = tps25750_init, + .reset = tps25750_reset, }; static const struct of_device_id tps6598x_of_match[] = { diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 01609bf509..89b2451946 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -235,4 +235,22 @@ /* SLEEP CONF REG */ #define TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED BIT(0) +/* Start Patch Download Sequence */ +#define TPS_PTCS_CONTENT_APP BIT(0) +#define TPS_PTCS_CONTENT_DEV BIT(1) +#define TPS_PTCS_OUT_BYTES 4 +#define TPS_PTCS_STATUS 1 + +#define TPS_PTCS_STATUS_FAIL 0x80 +/* Patch Download */ +#define TPS_PTCD_OUT_BYTES 10 +#define TPS_PTCD_TRANSFER_STATUS 1 +#define TPS_PTCD_LOADING_STATE 2 + +#define TPS_PTCD_LOAD_ERR 0x09 +/* Patch Download Complete */ +#define TPS_PTCC_OUT_BYTES 4 +#define TPS_PTCC_DEV 2 +#define TPS_PTCC_APP 3 + #endif /* __TPS6598X_H__ */ diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 70d9f4eebf..cd415565b6 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -36,6 +36,19 @@ */ #define UCSI_SWAP_TIMEOUT_MS 5000 +static int ucsi_read_message_in(struct ucsi *ucsi, void *buf, + size_t buf_size) +{ + /* + * Below UCSI 2.0, MESSAGE_IN was limited to 16 bytes. Truncate the + * reads here. + */ + if (ucsi->version <= UCSI_VERSION_1_2) + buf_size = clamp(buf_size, 0, 16); + + return ucsi->ops->read(ucsi, UCSI_MESSAGE_IN, buf, buf_size); +} + static int ucsi_acknowledge_command(struct ucsi *ucsi) { u64 ctrl; @@ -72,7 +85,7 @@ static int ucsi_read_error(struct ucsi *ucsi) if (ret < 0) return ret; - ret = ucsi->ops->read(ucsi, UCSI_MESSAGE_IN, &error, sizeof(error)); + ret = ucsi_read_message_in(ucsi, &error, sizeof(error)); if (ret) return ret; @@ -174,7 +187,7 @@ int ucsi_send_command(struct ucsi *ucsi, u64 command, length = ret; if (data) { - ret = ucsi->ops->read(ucsi, UCSI_MESSAGE_IN, data, size); + ret = ucsi_read_message_in(ucsi, data, size); if (ret) goto out; } @@ -582,6 +595,9 @@ static int ucsi_read_pdos(struct ucsi_connector *con, u64 command; int ret; + if (ucsi->quirks & UCSI_NO_PARTNER_PDOS) + return 0; + command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num); command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner); command |= UCSI_GET_PDOS_PDO_OFFSET(offset); @@ -1593,6 +1609,15 @@ int ucsi_register(struct ucsi *ucsi) if (!ucsi->version) return -ENODEV; + /* + * Version format is JJ.M.N (JJ = Major version, M = Minor version, + * N = sub-minor version). + */ + dev_dbg(ucsi->dev, "Registered UCSI interface with version %x.%x.%x", + UCSI_BCD_GET_MAJOR(ucsi->version), + UCSI_BCD_GET_MINOR(ucsi->version), + UCSI_BCD_GET_SUBMINOR(ucsi->version)); + queue_delayed_work(system_long_wq, &ucsi->work, 0); ucsi_debugfs_register(ucsi); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 13ec976b1c..ede9876488 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -23,6 +23,17 @@ struct dentry; #define UCSI_CONTROL 8 #define UCSI_MESSAGE_IN 16 #define UCSI_MESSAGE_OUT 32 +#define UCSIv2_MESSAGE_OUT 272 + +/* UCSI versions */ +#define UCSI_VERSION_1_2 0x0120 +#define UCSI_VERSION_2_0 0x0200 +#define UCSI_VERSION_2_1 0x0210 +#define UCSI_VERSION_3_0 0x0300 + +#define UCSI_BCD_GET_MAJOR(_v_) (((_v_) >> 8) & 0xFF) +#define UCSI_BCD_GET_MINOR(_v_) (((_v_) >> 4) & 0x0F) +#define UCSI_BCD_GET_SUBMINOR(_v_) ((_v_) & 0x0F) /* Command Status and Connector Change Indication (CCI) bits */ #define UCSI_CCI_CONNECTOR(_c_) (((_c_) & GENMASK(7, 1)) >> 1) @@ -317,6 +328,9 @@ struct ucsi { #define EVENT_PENDING 0 #define COMMAND_PENDING 1 #define ACK_PENDING 2 + + unsigned long quirks; +#define UCSI_NO_PARTNER_PDOS BIT(0) /* Don't read partner's PDOs */ }; #define UCSI_MAX_SVID 5 diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 894622b655..ce08eb33e5 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -6,6 +6,7 @@ #include <linux/auxiliary_bus.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/of_device.h> #include <linux/property.h> #include <linux/soc/qcom/pdr.h> #include <linux/usb/typec_mux.h> @@ -310,11 +311,21 @@ static void pmic_glink_ucsi_destroy(void *data) mutex_unlock(&ucsi->lock); } +static const struct of_device_id pmic_glink_ucsi_of_quirks[] = { + { .compatible = "qcom,qcm6490-pmic-glink", .data = (void *)UCSI_NO_PARTNER_PDOS, }, + { .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)UCSI_NO_PARTNER_PDOS, }, + { .compatible = "qcom,sc8280xp-pmic-glink", .data = (void *)UCSI_NO_PARTNER_PDOS, }, + { .compatible = "qcom,sm8350-pmic-glink", .data = (void *)UCSI_NO_PARTNER_PDOS, }, + { .compatible = "qcom,sm8550-pmic-glink", .data = (void *)UCSI_NO_PARTNER_PDOS, }, + {} +}; + static int pmic_glink_ucsi_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id) { struct pmic_glink_ucsi *ucsi; struct device *dev = &adev->dev; + const struct of_device_id *match; struct fwnode_handle *fwnode; int ret; @@ -341,6 +352,10 @@ static int pmic_glink_ucsi_probe(struct auxiliary_device *adev, if (ret) return ret; + match = of_match_device(pmic_glink_ucsi_of_quirks, dev->parent); + if (match) + ucsi->ucsi->quirks = (unsigned long)match->data; + ucsi_set_drvdata(ucsi->ucsi, ucsi); device_for_each_child_node(dev, fwnode) { |