diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 17:39:57 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 17:39:57 +0000 |
commit | dc50eab76b709d68175a358d6e23a5a3890764d3 (patch) | |
tree | c754d0390db060af0213ff994f0ac310e4cfd6e9 /drivers/usb/typec | |
parent | Adding debian version 6.6.15-2. (diff) | |
download | linux-dc50eab76b709d68175a358d6e23a5a3890764d3.tar.xz linux-dc50eab76b709d68175a358d6e23a5a3890764d3.zip |
Merging upstream version 6.7.7.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r-- | drivers/usb/typec/altmodes/displayport.c | 25 | ||||
-rw-r--r-- | drivers/usb/typec/anx7411.c | 3 | ||||
-rw-r--r-- | drivers/usb/typec/class.c | 108 | ||||
-rw-r--r-- | drivers/usb/typec/class.h | 16 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Kconfig | 10 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/typec/mux/fsa4480.c | 71 | ||||
-rw-r--r-- | drivers/usb/typec/mux/intel_pmc_mux.c | 25 | ||||
-rw-r--r-- | drivers/usb/typec/mux/ptn36502.c | 444 | ||||
-rw-r--r-- | drivers/usb/typec/pd.c | 10 | ||||
-rw-r--r-- | drivers/usb/typec/port-mapper.c | 9 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci_rt1711h.c | 46 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpm.c | 7 | ||||
-rw-r--r-- | drivers/usb/typec/tipd/core.c | 632 | ||||
-rw-r--r-- | drivers/usb/typec/tipd/tps6598x.h | 36 | ||||
-rw-r--r-- | drivers/usb/typec/tipd/trace.h | 92 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/displayport.c | 2 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 2 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi_acpi.c | 88 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi_ccg.c | 4 |
20 files changed, 1500 insertions, 131 deletions
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index 59e0218a8b..f81bec0c7b 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -86,8 +86,11 @@ static int dp_altmode_notify(struct dp_altmode *dp) static int dp_altmode_configure(struct dp_altmode *dp, u8 con) { - u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */ u8 pin_assign = 0; + u32 conf; + + /* DP Signalling */ + conf = (dp->data.conf & DP_CONF_SIGNALLING_MASK) >> DP_CONF_SIGNALLING_SHIFT; switch (con) { case DP_STATUS_CON_DISABLED: @@ -153,11 +156,11 @@ static int dp_altmode_status_update(struct dp_altmode *dp) } } } else { - if (dp->hpd != hpd) { - drm_connector_oob_hotplug_event(dp->connector_fwnode); - dp->hpd = hpd; - sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); - } + drm_connector_oob_hotplug_event(dp->connector_fwnode, + hpd ? connector_status_connected : + connector_status_disconnected); + dp->hpd = hpd; + sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); } return ret; @@ -173,7 +176,8 @@ static int dp_altmode_configured(struct dp_altmode *dp) * configuration is complete to signal HPD. */ if (dp->pending_hpd) { - drm_connector_oob_hotplug_event(dp->connector_fwnode); + drm_connector_oob_hotplug_event(dp->connector_fwnode, + connector_status_connected); sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); dp->pending_hpd = false; } @@ -305,7 +309,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt, dp->data.status = 0; dp->data.conf = 0; if (dp->hpd) { - drm_connector_oob_hotplug_event(dp->connector_fwnode); + drm_connector_oob_hotplug_event(dp->connector_fwnode, + connector_status_disconnected); dp->hpd = false; sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); } @@ -623,8 +628,8 @@ void dp_altmode_remove(struct typec_altmode *alt) cancel_work_sync(&dp->work); if (dp->connector_fwnode) { - if (dp->hpd) - drm_connector_oob_hotplug_event(dp->connector_fwnode); + drm_connector_oob_hotplug_event(dp->connector_fwnode, + connector_status_disconnected); fwnode_handle_put(dp->connector_fwnode); } diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 221604f933..b12a07edc7 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1550,8 +1550,7 @@ static void anx7411_i2c_remove(struct i2c_client *client) if (plat->workqueue) destroy_workqueue(plat->workqueue); - if (plat->spi_client) - i2c_unregister_device(plat->spi_client); + i2c_unregister_device(plat->spi_client); if (plat->typec.role_sw) usb_role_switch_put(plat->typec.role_sw); diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 41382b1e29..2da19feacd 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -13,6 +13,7 @@ #include <linux/usb/pd_vdo.h> #include <linux/usb/typec_mux.h> #include <linux/usb/typec_retimer.h> +#include <linux/usb.h> #include "bus.h" #include "class.h" @@ -684,6 +685,33 @@ const struct device_type typec_partner_dev_type = { .release = typec_partner_release, }; +static void typec_partner_link_device(struct typec_partner *partner, struct device *dev) +{ + int ret; + + ret = sysfs_create_link(&dev->kobj, &partner->dev.kobj, "typec"); + if (ret) + return; + + ret = sysfs_create_link(&partner->dev.kobj, &dev->kobj, dev_name(dev)); + if (ret) { + sysfs_remove_link(&dev->kobj, "typec"); + return; + } + + if (partner->attach) + partner->attach(partner, dev); +} + +static void typec_partner_unlink_device(struct typec_partner *partner, struct device *dev) +{ + sysfs_remove_link(&partner->dev.kobj, dev_name(dev)); + sysfs_remove_link(&dev->kobj, "typec"); + + if (partner->deattach) + partner->deattach(partner, dev); +} + /** * typec_partner_set_identity - Report result from Discover Identity command * @partner: The partner updated identity values @@ -868,6 +896,8 @@ struct typec_partner *typec_register_partner(struct typec_port *port, partner->num_altmodes = -1; partner->pd_revision = desc->pd_revision; partner->svdm_version = port->cap->svdm_version; + partner->attach = desc->attach; + partner->deattach = desc->deattach; if (desc->identity) { /* @@ -890,6 +920,11 @@ struct typec_partner *typec_register_partner(struct typec_port *port, return ERR_PTR(ret); } + if (port->usb2_dev) + typec_partner_link_device(partner, port->usb2_dev); + if (port->usb3_dev) + typec_partner_link_device(partner, port->usb3_dev); + return partner; } EXPORT_SYMBOL_GPL(typec_register_partner); @@ -902,8 +937,19 @@ EXPORT_SYMBOL_GPL(typec_register_partner); */ void typec_unregister_partner(struct typec_partner *partner) { - if (!IS_ERR_OR_NULL(partner)) - device_unregister(&partner->dev); + struct typec_port *port; + + if (IS_ERR_OR_NULL(partner)) + return; + + port = to_typec_port(partner->dev.parent); + + if (port->usb2_dev) + typec_partner_unlink_device(partner, port->usb2_dev); + if (port->usb3_dev) + typec_partner_unlink_device(partner, port->usb3_dev); + + device_unregister(&partner->dev); } EXPORT_SYMBOL_GPL(typec_unregister_partner); @@ -1778,6 +1824,50 @@ static int partner_match(struct device *dev, void *data) return is_typec_partner(dev); } +static struct typec_partner *typec_get_partner(struct typec_port *port) +{ + struct device *dev; + + dev = device_find_child(&port->dev, NULL, partner_match); + if (!dev) + return NULL; + + return to_typec_partner(dev); +} + +static void typec_partner_attach(struct typec_connector *con, struct device *dev) +{ + struct typec_port *port = container_of(con, struct typec_port, con); + struct typec_partner *partner = typec_get_partner(port); + struct usb_device *udev = to_usb_device(dev); + + if (udev->speed < USB_SPEED_SUPER) + port->usb2_dev = dev; + else + port->usb3_dev = dev; + + if (partner) { + typec_partner_link_device(partner, dev); + put_device(&partner->dev); + } +} + +static void typec_partner_deattach(struct typec_connector *con, struct device *dev) +{ + struct typec_port *port = container_of(con, struct typec_port, con); + struct typec_partner *partner = typec_get_partner(port); + + if (partner) { + typec_partner_unlink_device(partner, dev); + put_device(&partner->dev); + } + + if (port->usb2_dev == dev) + port->usb2_dev = NULL; + else if (port->usb3_dev == dev) + port->usb3_dev = NULL; +} + /** * typec_set_data_role - Report data role change * @port: The USB Type-C Port where the role was changed @@ -1787,7 +1877,7 @@ static int partner_match(struct device *dev, void *data) */ void typec_set_data_role(struct typec_port *port, enum typec_data_role role) { - struct device *partner_dev; + struct typec_partner *partner; if (port->data_role == role) return; @@ -1796,14 +1886,14 @@ void typec_set_data_role(struct typec_port *port, enum typec_data_role role) sysfs_notify(&port->dev.kobj, NULL, "data_role"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); - partner_dev = device_find_child(&port->dev, NULL, partner_match); - if (!partner_dev) + partner = typec_get_partner(port); + if (!partner) return; - if (to_typec_partner(partner_dev)->identity) - typec_product_type_notify(partner_dev); + if (partner->identity) + typec_product_type_notify(&partner->dev); - put_device(partner_dev); + put_device(&partner->dev); } EXPORT_SYMBOL_GPL(typec_set_data_role); @@ -2254,6 +2344,8 @@ struct typec_port *typec_register_port(struct device *parent, port->ops = cap->ops; port->port_type = cap->type; port->prefer_role = cap->prefer_role; + port->con.attach = typec_partner_attach; + port->con.deattach = typec_partner_deattach; device_initialize(&port->dev); port->dev.class = &typec_class; diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index 673b2952b0..c36761ba3f 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -8,6 +8,7 @@ struct typec_mux; struct typec_switch; +struct usb_device; struct typec_plug { struct device dev; @@ -35,6 +36,9 @@ struct typec_partner { enum usb_pd_svdm_ver svdm_version; struct usb_power_delivery *pd; + + void (*attach)(struct typec_partner *partner, struct device *dev); + void (*deattach)(struct typec_partner *partner, struct device *dev); }; struct typec_port { @@ -59,6 +63,18 @@ struct typec_port { const struct typec_capability *cap; const struct typec_operations *ops; + + struct typec_connector con; + + /* + * REVISIT: Only USB devices for now. If there are others, these need to + * be converted into a list. + * + * NOTE: These may be registered first before the typec_partner, so they + * will always have to be kept here instead of struct typec_partner. + */ + struct device *usb2_dev; + struct device *usb3_dev; }; #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index 65da61150b..816b9bd083 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -46,4 +46,14 @@ config TYPEC_MUX_NB7VPQ904M Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C redriver chip found on some devices with a Type-C port. +config TYPEC_MUX_PTN36502 + tristate "NXP PTN36502 Type-C redriver driver" + depends on I2C + depends on DRM || DRM=n + select DRM_PANEL_BRIDGE if DRM + select REGMAP_I2C + help + Say Y or M if your system has a NXP PTN36502 Type-C redriver chip + found on some devices with a Type-C port. + endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 76196096ef..9d6a5557b0 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_TYPEC_MUX_GPIO_SBU) += gpio-sbu-mux.o 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 diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c index e0ee1f621a..cb7cdf90cb 100644 --- a/drivers/usb/typec/mux/fsa4480.c +++ b/drivers/usb/typec/mux/fsa4480.c @@ -60,6 +60,7 @@ struct fsa4480 { unsigned int svid; u8 cur_enable; + bool swap_sbu_lanes; }; static const struct regmap_config fsa4480_regmap_config = { @@ -76,6 +77,9 @@ static int fsa4480_set(struct fsa4480 *fsa) u8 enable = FSA4480_ENABLE_DEVICE; u8 sel = 0; + if (fsa->swap_sbu_lanes) + reverse = !reverse; + /* USB Mode */ if (fsa->mode < TYPEC_STATE_MODAL || (!fsa->svid && (fsa->mode == TYPEC_MODE_USB2 || @@ -179,12 +183,75 @@ static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *st return ret; } +enum { + NORMAL_LANE_MAPPING, + INVERT_LANE_MAPPING, +}; + +#define DATA_LANES_COUNT 2 + +static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = { + [NORMAL_LANE_MAPPING] = { 0, 1 }, + [INVERT_LANE_MAPPING] = { 1, 0 }, +}; + +static int fsa4480_parse_data_lanes_mapping(struct fsa4480 *fsa) +{ + struct fwnode_handle *ep; + u32 data_lanes[DATA_LANES_COUNT]; + int ret, i, j; + + ep = fwnode_graph_get_next_endpoint(dev_fwnode(&fsa->client->dev), NULL); + if (!ep) + return 0; + + ret = fwnode_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT); + if (ret == -EINVAL) + /* Property isn't here, consider default mapping */ + goto out_done; + if (ret) { + dev_err(&fsa->client->dev, "invalid data-lanes property: %d\n", ret); + goto out_error; + } + + for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) { + for (j = 0; j < DATA_LANES_COUNT; j++) { + if (data_lanes[j] != supported_data_lane_mapping[i][j]) + break; + } + + if (j == DATA_LANES_COUNT) + break; + } + + switch (i) { + case NORMAL_LANE_MAPPING: + break; + case INVERT_LANE_MAPPING: + fsa->swap_sbu_lanes = true; + break; + default: + dev_err(&fsa->client->dev, "invalid data-lanes mapping\n"); + ret = -EINVAL; + goto out_error; + } + +out_done: + ret = 0; + +out_error: + fwnode_handle_put(ep); + + return ret; +} + static int fsa4480_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct typec_switch_desc sw_desc = { }; struct typec_mux_desc mux_desc = { }; struct fsa4480 *fsa; + int ret; fsa = devm_kzalloc(dev, sizeof(*fsa), GFP_KERNEL); if (!fsa) @@ -193,6 +260,10 @@ static int fsa4480_probe(struct i2c_client *client) fsa->client = client; mutex_init(&fsa->lock); + ret = fsa4480_parse_data_lanes_mapping(fsa); + if (ret) + return ret; + fsa->regmap = devm_regmap_init_i2c(client, &fsa4480_regmap_config); if (IS_ERR(fsa->regmap)) return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n"); diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 60ed1f8091..56989a0d0f 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -191,6 +191,12 @@ static int hsl_orientation(struct pmc_usb_port *port) return port->orientation - 1; } +static bool is_pmc_mux_tbt(struct acpi_device *adev) +{ + return acpi_dev_hid_uid_match(adev, "INTC1072", NULL) || + acpi_dev_hid_uid_match(adev, "INTC1079", NULL); +} + static int pmc_usb_send_command(struct intel_scu_ipc_dev *ipc, u8 *msg, u32 len) { u8 response[4]; @@ -293,6 +299,24 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state) req.mode_data |= (state->mode - TYPEC_STATE_MODAL) << PMC_USB_ALTMODE_DP_MODE_SHIFT; + if (!is_pmc_mux_tbt(port->pmc->iom_adev)) { + u8 cable_speed = (data->conf & DP_CONF_SIGNALLING_MASK) >> + DP_CONF_SIGNALLING_SHIFT; + + u8 cable_type = (data->conf & DP_CONF_CABLE_TYPE_MASK) >> + DP_CONF_CABLE_TYPE_SHIFT; + + req.mode_data |= PMC_USB_ALTMODE_CABLE_SPD(cable_speed); + + if (cable_type == DP_CONF_CABLE_TYPE_OPTICAL) + req.mode_data |= PMC_USB_ALTMODE_CABLE_TYPE; + else if (cable_type == DP_CONF_CABLE_TYPE_RE_TIMER) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE | + PMC_USB_ALTMODE_RETIMER_CABLE; + else if (cable_type == DP_CONF_CABLE_TYPE_RE_DRIVER) + req.mode_data |= PMC_USB_ALTMODE_ACTIVE_CABLE; + } + ret = pmc_usb_command(port, (void *)&req, sizeof(req)); if (ret) return ret; @@ -599,6 +623,7 @@ static int pmc_usb_register_port(struct pmc_usb *pmc, int index, desc.driver_data = port; desc.name = fwnode_get_name(fwnode); desc.set = pmc_usb_set_role; + desc.allow_userspace_control = true; port->usb_sw = usb_role_switch_register(pmc->dev, &desc); if (IS_ERR(port->usb_sw)) { diff --git a/drivers/usb/typec/mux/ptn36502.c b/drivers/usb/typec/mux/ptn36502.c new file mode 100644 index 0000000000..72ae38a1b2 --- /dev/null +++ b/drivers/usb/typec/mux/ptn36502.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NXP PTN36502 Type-C driver + * + * Copyright (C) 2023 Luca Weiss <luca.weiss@fairphone.com> + * + * Based on NB7VPQ904M driver: + * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org> + */ + +#include <drm/drm_bridge.h> +#include <linux/bitfield.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> +#include <linux/usb/typec_retimer.h> + +#define PTN36502_CHIP_ID_REG 0x00 +#define PTN36502_CHIP_ID 0x02 + +#define PTN36502_CHIP_REVISION_REG 0x01 +#define PTN36502_CHIP_REVISION_BASE_MASK GENMASK(7, 4) +#define PTN36502_CHIP_REVISION_METAL_MASK GENMASK(3, 0) + +#define PTN36502_DP_LINK_CTRL_REG 0x06 +#define PTN36502_DP_LINK_CTRL_LANES_MASK GENMASK(3, 2) +#define PTN36502_DP_LINK_CTRL_LANES_2 (2) +#define PTN36502_DP_LINK_CTRL_LANES_4 (3) +#define PTN36502_DP_LINK_CTRL_LINK_RATE_MASK GENMASK(1, 0) +#define PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS (2) + +/* Registers for lane 0 (0x07) to lane 3 (0x0a) have the same layout */ +#define PTN36502_DP_LANE_CTRL_REG(n) (0x07 + (n)) +#define PTN36502_DP_LANE_CTRL_RX_GAIN_MASK GENMASK(6, 4) +#define PTN36502_DP_LANE_CTRL_RX_GAIN_3DB (2) +#define PTN36502_DP_LANE_CTRL_TX_SWING_MASK GENMASK(3, 2) +#define PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD (2) +#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK GENMASK(1, 0) +#define PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB (1) + +#define PTN36502_MODE_CTRL1_REG 0x0b +#define PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK GENMASK(5, 5) +#define PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE (1) +#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK GENMASK(3, 3) +#define PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON (1) +#define PTN36502_MODE_CTRL1_MODE_MASK GENMASK(2, 0) +#define PTN36502_MODE_CTRL1_MODE_OFF (0) +#define PTN36502_MODE_CTRL1_MODE_USB_ONLY (1) +#define PTN36502_MODE_CTRL1_MODE_USB_DP (2) +#define PTN36502_MODE_CTRL1_MODE_DP (3) + +#define PTN36502_DEVICE_CTRL_REG 0x0d +#define PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK GENMASK(7, 7) +#define PTN36502_DEVICE_CTRL_AUX_MONITORING_EN (1) + +struct ptn36502 { + struct i2c_client *client; + struct regulator *vdd18_supply; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_retimer *retimer; + + struct typec_switch *typec_switch; + + struct drm_bridge bridge; + + struct mutex lock; /* protect non-concurrent retimer & switch */ + + enum typec_orientation orientation; + unsigned long mode; + unsigned int svid; +}; + +static int ptn36502_set(struct ptn36502 *ptn) +{ + bool reverse = (ptn->orientation == TYPEC_ORIENTATION_REVERSE); + unsigned int ctrl1_val = 0; + unsigned int lane_ctrl_val = 0; + unsigned int link_ctrl_val = 0; + + switch (ptn->mode) { + case TYPEC_STATE_SAFE: + /* Deep power saving state */ + regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, + FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_OFF)); + return 0; + + case TYPEC_STATE_USB: + /* + * Normal Orientation (CC1) + * A -> USB RX + * B -> USB TX + * C -> X + * D -> X + * Flipped Orientation (CC2) + * A -> X + * B -> X + * C -> USB TX + * D -> USB RX + */ + + /* USB 3.1 Gen 1 only */ + ctrl1_val = FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_USB_ONLY); + if (reverse) + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK, + PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE); + + regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val); + return 0; + + default: + if (ptn->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + break; + } + + /* DP Altmode Setup */ + + switch (ptn->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + /* + * Normal Orientation (CC1) + * A -> DP3 + * B -> DP2 + * C -> DP1 + * D -> DP0 + * Flipped Orientation (CC2) + * A -> DP0 + * B -> DP1 + * C -> DP2 + * D -> DP3 + */ + + /* 4-lane DP */ + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_DP); + link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK, + PTN36502_DP_LINK_CTRL_LANES_4); + break; + + case TYPEC_DP_STATE_D: + case TYPEC_DP_STATE_F: /* State F is deprecated */ + /* + * Normal Orientation (CC1) + * A -> USB RX + * B -> USB TX + * C -> DP1 + * D -> DP0 + * Flipped Orientation (CC2) + * A -> DP0 + * B -> DP1 + * C -> USB TX + * D -> USB RX + */ + + /* USB 3.1 Gen 1 and 2-lane DP */ + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_MODE_MASK, + PTN36502_MODE_CTRL1_MODE_USB_DP); + link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LANES_MASK, + PTN36502_DP_LINK_CTRL_LANES_2); + break; + + default: + return -EOPNOTSUPP; + } + + /* Enable AUX monitoring */ + regmap_write(ptn->regmap, PTN36502_DEVICE_CTRL_REG, + FIELD_PREP(PTN36502_DEVICE_CTRL_AUX_MONITORING_MASK, + PTN36502_DEVICE_CTRL_AUX_MONITORING_EN)); + + /* Enable AUX switch path */ + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_AUX_CROSSBAR_MASK, + PTN36502_MODE_CTRL1_AUX_CROSSBAR_SW_ON); + if (reverse) + ctrl1_val |= FIELD_PREP(PTN36502_MODE_CTRL1_PLUG_ORIENT_MASK, + PTN36502_MODE_CTRL1_PLUG_ORIENT_REVERSE); + regmap_write(ptn->regmap, PTN36502_MODE_CTRL1_REG, ctrl1_val); + + /* DP Link rate: 5.4 Gbps (HBR2) */ + link_ctrl_val |= FIELD_PREP(PTN36502_DP_LINK_CTRL_LINK_RATE_MASK, + PTN36502_DP_LINK_CTRL_LINK_RATE_5_4GBPS); + regmap_write(ptn->regmap, PTN36502_DP_LINK_CTRL_REG, link_ctrl_val); + + /* + * For all lanes: + * - Rx equivalization gain: 3 dB + * - TX output swing control: 800 mVppd + * - Pre-emphasis control: 3.5 dB + */ + lane_ctrl_val = FIELD_PREP(PTN36502_DP_LANE_CTRL_RX_GAIN_MASK, + PTN36502_DP_LANE_CTRL_RX_GAIN_3DB) | + FIELD_PREP(PTN36502_DP_LANE_CTRL_TX_SWING_MASK, + PTN36502_DP_LANE_CTRL_TX_SWING_800MVPPD) | + FIELD_PREP(PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_MASK, + PTN36502_DP_LANE_CTRL_PRE_EMPHASIS_3_5DB); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(0), lane_ctrl_val); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(1), lane_ctrl_val); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(2), lane_ctrl_val); + regmap_write(ptn->regmap, PTN36502_DP_LANE_CTRL_REG(3), lane_ctrl_val); + + return 0; +} + +static int ptn36502_sw_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct ptn36502 *ptn = typec_switch_get_drvdata(sw); + int ret; + + ret = typec_switch_set(ptn->typec_switch, orientation); + if (ret) + return ret; + + mutex_lock(&ptn->lock); + + if (ptn->orientation != orientation) { + ptn->orientation = orientation; + + ret = ptn36502_set(ptn); + } + + mutex_unlock(&ptn->lock); + + return ret; +} + +static int ptn36502_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state) +{ + struct ptn36502 *ptn = typec_retimer_get_drvdata(retimer); + int ret = 0; + + mutex_lock(&ptn->lock); + + if (ptn->mode != state->mode) { + ptn->mode = state->mode; + + if (state->alt) + ptn->svid = state->alt->svid; + else + ptn->svid = 0; // No SVID + + ret = ptn36502_set(ptn); + } + + mutex_unlock(&ptn->lock); + + return ret; +} + +static int ptn36502_detect(struct ptn36502 *ptn) +{ + struct device *dev = &ptn->client->dev; + unsigned int reg_val; + int ret; + + ret = regmap_read(ptn->regmap, PTN36502_CHIP_ID_REG, + ®_val); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read chip ID\n"); + + if (reg_val != PTN36502_CHIP_ID) + return dev_err_probe(dev, -ENODEV, "Unexpected chip ID: %x\n", reg_val); + + ret = regmap_read(ptn->regmap, PTN36502_CHIP_REVISION_REG, + ®_val); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to read chip revision\n"); + + dev_dbg(dev, "Chip revision: base layer version %lx, metal layer version %lx\n", + FIELD_GET(PTN36502_CHIP_REVISION_BASE_MASK, reg_val), + FIELD_GET(PTN36502_CHIP_REVISION_METAL_MASK, reg_val)); + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_DRM_PANEL_BRIDGE) +static int ptn36502_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct ptn36502 *ptn = container_of(bridge, struct ptn36502, bridge); + struct drm_bridge *next_bridge; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) + return -EINVAL; + + next_bridge = devm_drm_of_get_bridge(&ptn->client->dev, ptn->client->dev.of_node, 0, 0); + if (IS_ERR(next_bridge)) { + dev_err(&ptn->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 ptn36502_bridge_funcs = { + .attach = ptn36502_bridge_attach, +}; + +static int ptn36502_register_bridge(struct ptn36502 *ptn) +{ + ptn->bridge.funcs = &ptn36502_bridge_funcs; + ptn->bridge.of_node = ptn->client->dev.of_node; + + return devm_drm_bridge_add(&ptn->client->dev, &ptn->bridge); +} +#else +static int ptn36502_register_bridge(struct ptn36502 *ptn) +{ + return 0; +} +#endif + +static const struct regmap_config ptn36502_regmap = { + .max_register = 0x0d, + .reg_bits = 8, + .val_bits = 8, +}; + +static int ptn36502_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct typec_switch_desc sw_desc = { }; + struct typec_retimer_desc retimer_desc = { }; + struct ptn36502 *ptn; + int ret; + + ptn = devm_kzalloc(dev, sizeof(*ptn), GFP_KERNEL); + if (!ptn) + return -ENOMEM; + + ptn->client = client; + + ptn->regmap = devm_regmap_init_i2c(client, &ptn36502_regmap); + if (IS_ERR(ptn->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(ptn->regmap); + } + + ptn->mode = TYPEC_STATE_SAFE; + ptn->orientation = TYPEC_ORIENTATION_NONE; + + mutex_init(&ptn->lock); + + ptn->vdd18_supply = devm_regulator_get_optional(dev, "vdd18"); + if (IS_ERR(ptn->vdd18_supply)) + return PTR_ERR(ptn->vdd18_supply); + + ptn->typec_switch = fwnode_typec_switch_get(dev->fwnode); + if (IS_ERR(ptn->typec_switch)) + return dev_err_probe(dev, PTR_ERR(ptn->typec_switch), + "Failed to acquire orientation-switch\n"); + + ret = regulator_enable(ptn->vdd18_supply); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable vdd18\n"); + + ret = ptn36502_detect(ptn); + if (ret) + goto err_disable_regulator; + + ret = ptn36502_register_bridge(ptn); + if (ret) + goto err_disable_regulator; + + sw_desc.drvdata = ptn; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = ptn36502_sw_set; + + ptn->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(ptn->sw)) { + ret = dev_err_probe(dev, PTR_ERR(ptn->sw), + "Failed to register typec switch\n"); + goto err_disable_regulator; + } + + retimer_desc.drvdata = ptn; + retimer_desc.fwnode = dev->fwnode; + retimer_desc.set = ptn36502_retimer_set; + + ptn->retimer = typec_retimer_register(dev, &retimer_desc); + if (IS_ERR(ptn->retimer)) { + ret = dev_err_probe(dev, PTR_ERR(ptn->retimer), + "Failed to register typec retimer\n"); + goto err_switch_unregister; + } + + return 0; + +err_switch_unregister: + typec_switch_unregister(ptn->sw); + +err_disable_regulator: + regulator_disable(ptn->vdd18_supply); + + return ret; +} + +static void ptn36502_remove(struct i2c_client *client) +{ + struct ptn36502 *ptn = i2c_get_clientdata(client); + + typec_retimer_unregister(ptn->retimer); + typec_switch_unregister(ptn->sw); + + regulator_disable(ptn->vdd18_supply); +} + +static const struct i2c_device_id ptn36502_table[] = { + { "ptn36502" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ptn36502_table); + +static const struct of_device_id ptn36502_of_table[] = { + { .compatible = "nxp,ptn36502" }, + { } +}; +MODULE_DEVICE_TABLE(of, ptn36502_of_table); + +static struct i2c_driver ptn36502_driver = { + .driver = { + .name = "ptn36502", + .of_match_table = ptn36502_of_table, + }, + .probe = ptn36502_probe, + .remove = ptn36502_remove, + .id_table = ptn36502_table, +}; +module_i2c_driver(ptn36502_driver); + +MODULE_AUTHOR("Luca Weiss <luca.weiss@fairphone.com>"); +MODULE_DESCRIPTION("NXP PTN36502 Type-C driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c index 8cc66e4467..85d015cdbe 100644 --- a/drivers/usb/typec/pd.c +++ b/drivers/usb/typec/pd.c @@ -83,14 +83,12 @@ unchunked_extended_messages_supported_show(struct device *dev, } static DEVICE_ATTR_RO(unchunked_extended_messages_supported); -/* - * REVISIT: Peak Current requires access also to the RDO. static ssize_t peak_current_show(struct device *dev, struct device_attribute *attr, char *buf) { - ... + return sysfs_emit(buf, "%u\n", (to_pdo(dev)->pdo >> PDO_FIXED_PEAK_CURR_SHIFT) & 3); } -*/ +static DEVICE_ATTR_RO(peak_current); static ssize_t fast_role_swap_current_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -135,7 +133,7 @@ static struct attribute *source_fixed_supply_attrs[] = { &dev_attr_usb_communication_capable.attr, &dev_attr_dual_role_data.attr, &dev_attr_unchunked_extended_messages_supported.attr, - /*&dev_attr_peak_current.attr,*/ + &dev_attr_peak_current.attr, &dev_attr_voltage.attr, &maximum_current_attr.attr, NULL @@ -144,7 +142,7 @@ static struct attribute *source_fixed_supply_attrs[] = { static umode_t fixed_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { if (to_pdo(kobj_to_dev(kobj))->object_position && - /*attr != &dev_attr_peak_current.attr &&*/ + attr != &dev_attr_peak_current.attr && attr != &dev_attr_voltage.attr && attr != &maximum_current_attr.attr && attr != &operational_current_attr.attr) diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c index a929e000d0..d42da5720a 100644 --- a/drivers/usb/typec/port-mapper.c +++ b/drivers/usb/typec/port-mapper.c @@ -8,17 +8,22 @@ #include <linux/acpi.h> #include <linux/component.h> +#include <linux/usb.h> #include "class.h" static int typec_aggregate_bind(struct device *dev) { - return component_bind_all(dev, NULL); + struct typec_port *port = to_typec_port(dev); + + return component_bind_all(dev, &port->con); } static void typec_aggregate_unbind(struct device *dev) { - component_unbind_all(dev, NULL); + struct typec_port *port = to_typec_port(dev); + + component_unbind_all(dev, &port->con); } static const struct component_master_ops typec_aggregate_ops = { diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c index 17ebc5fb68..67422d45eb 100644 --- a/drivers/usb/typec/tcpm/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpm/tcpci_rt1711h.c @@ -7,6 +7,7 @@ #include <linux/bits.h> #include <linux/kernel.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/interrupt.h> @@ -51,13 +52,19 @@ /* 1b0 as fixed rx threshold of rd/rp 0.55V, 1b1 depends on RTCRTL4[0] */ #define BMCIO_RXDZEN BIT(0) +struct rt1711h_chip_info { + u32 rxdz_sel; + u16 did; + bool enable_pd30_extended_message; +}; + struct rt1711h_chip { struct tcpci_data data; struct tcpci *tcpci; struct device *dev; struct regulator *vbus; + const struct rt1711h_chip_info *info; bool src_en; - u16 did; }; static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val) @@ -105,7 +112,7 @@ static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata) return ret; /* Enable PD30 extended message for RT1715 */ - if (chip->did == RT1715_DID) { + if (chip->info->enable_pd30_extended_message) { ret = regmap_update_bits(regmap, RT1711H_RTCTRL8, RT1711H_ENEXTMSG, RT1711H_ENEXTMSG); if (ret < 0) @@ -200,10 +207,7 @@ static inline int rt1711h_init_cc_params(struct rt1711h_chip *chip, u8 status) if ((cc1 >= TYPEC_CC_RP_1_5 && cc2 < TYPEC_CC_RP_DEF) || (cc2 >= TYPEC_CC_RP_1_5 && cc1 < TYPEC_CC_RP_DEF)) { rxdz_en = BMCIO_RXDZEN; - if (chip->did == RT1715_DID) - rxdz_sel = RT1711H_BMCIO_RXDZSEL; - else - rxdz_sel = 0; + rxdz_sel = chip->info->rxdz_sel; } else { rxdz_en = 0; rxdz_sel = RT1711H_BMCIO_RXDZSEL; @@ -319,7 +323,7 @@ static int rt1711h_check_revision(struct i2c_client *i2c, struct rt1711h_chip *c ret = i2c_smbus_read_word_data(i2c, TCPC_BCD_DEV); if (ret < 0) return ret; - if (ret != chip->did) { + if (ret != chip->info->did) { dev_err(&i2c->dev, "did is not correct, 0x%04x\n", ret); return -ENODEV; } @@ -336,7 +340,7 @@ static int rt1711h_probe(struct i2c_client *client) if (!chip) return -ENOMEM; - chip->did = (size_t)device_get_match_data(&client->dev); + chip->info = i2c_get_match_data(client); ret = rt1711h_check_revision(client, chip); if (ret < 0) { @@ -391,26 +395,34 @@ static void rt1711h_remove(struct i2c_client *client) tcpci_unregister_port(chip->tcpci); } +static const struct rt1711h_chip_info rt1711h = { + .did = RT1711H_DID, +}; + +static const struct rt1711h_chip_info rt1715 = { + .rxdz_sel = RT1711H_BMCIO_RXDZSEL, + .did = RT1715_DID, + .enable_pd30_extended_message = true, +}; + static const struct i2c_device_id rt1711h_id[] = { - { "rt1711h", 0 }, - { "rt1715", 0 }, - { } + { "rt1711h", (kernel_ulong_t)&rt1711h }, + { "rt1715", (kernel_ulong_t)&rt1715 }, + {} }; MODULE_DEVICE_TABLE(i2c, rt1711h_id); -#ifdef CONFIG_OF static const struct of_device_id rt1711h_of_match[] = { - { .compatible = "richtek,rt1711h", .data = (void *)RT1711H_DID }, - { .compatible = "richtek,rt1715", .data = (void *)RT1715_DID }, - {}, + { .compatible = "richtek,rt1711h", .data = &rt1711h }, + { .compatible = "richtek,rt1715", .data = &rt1715 }, + {} }; MODULE_DEVICE_TABLE(of, rt1711h_of_match); -#endif static struct i2c_driver rt1711h_i2c_driver = { .driver = { .name = "rt1711h", - .of_match_table = of_match_ptr(rt1711h_of_match), + .of_match_table = rt1711h_of_match, }, .probe = rt1711h_probe, .remove = rt1711h_remove, diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 6d455ca761..a8e03cfde9 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -517,9 +517,9 @@ static const char * const pd_rev[] = { ((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \ (cc) == TYPEC_CC_RP_3_0) +/* As long as cc is pulled up, we can consider it as sink. */ #define tcpm_port_is_sink(port) \ - ((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \ - (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1))) + (tcpm_cc_is_sink((port)->cc1) || tcpm_cc_is_sink((port)->cc2)) #define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD) #define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA) @@ -3730,9 +3730,6 @@ static void tcpm_detach(struct tcpm_port *port) if (tcpm_port_is_disconnected(port)) port->hard_reset_count = 0; - port->try_src_count = 0; - port->try_snk_count = 0; - if (!port->attached) return; diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 37b56ce75f..196535ad99 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -17,6 +17,7 @@ #include <linux/usb/typec_altmode.h> #include <linux/usb/role.h> #include <linux/workqueue.h> +#include <linux/firmware.h> #include "tps6598x.h" #include "trace.h" @@ -36,13 +37,33 @@ #define TPS_REG_STATUS 0x1a #define TPS_REG_SYSTEM_CONF 0x28 #define TPS_REG_CTRL_CONF 0x29 +#define TPS_REG_BOOT_STATUS 0x2D #define TPS_REG_POWER_STATUS 0x3f +#define TPS_REG_PD_STATUS 0x40 #define TPS_REG_RX_IDENTITY_SOP 0x48 #define TPS_REG_DATA_STATUS 0x5f +#define TPS_REG_SLEEP_CONF 0x70 /* TPS_REG_SYSTEM_CONF bits */ #define TPS_SYSCONF_PORTINFO(c) ((c) & 7) +/* + * BPMs task timeout, recommended 5 seconds + * pg.48 TPS2575 Host Interface Technical Reference + * Manual (Rev. A) + * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf + */ +#define TPS_BUNDLE_TIMEOUT 0x32 + +/* BPMs return code */ +#define TPS_TASK_BPMS_INVALID_BUNDLE_SIZE 0x4 +#define TPS_TASK_BPMS_INVALID_SLAVE_ADDR 0x5 +#define TPS_TASK_BPMS_INVALID_TIMEOUT 0x6 + +/* PBMc data out */ +#define TPS_PBMC_RC 0 /* Return code */ +#define TPS_PBMC_DPCS 2 /* device patch complete status */ + enum { TPS_PORTINFO_SINK, TPS_PORTINFO_SINK_ACCESSORY, @@ -68,6 +89,7 @@ enum { TPS_MODE_BOOT, TPS_MODE_BIST, TPS_MODE_DISC, + TPS_MODE_PTCH, }; static const char *const modes[] = { @@ -75,11 +97,22 @@ static const char *const modes[] = { [TPS_MODE_BOOT] = "BOOT", [TPS_MODE_BIST] = "BIST", [TPS_MODE_DISC] = "DISC", + [TPS_MODE_PTCH] = "PTCH", }; /* Unrecognized commands will be replaced with "!CMD" */ #define INVALID_CMD(_cmd_) (_cmd_ == 0x444d4321) +struct tps6598x; + +struct tipd_data { + irq_handler_t irq_handler; + int (*register_port)(struct tps6598x *tps, struct fwnode_handle *node); + void (*trace_power_status)(u16 status); + void (*trace_status)(u32 status); + int (*apply_patch)(struct tps6598x *tps); +}; + struct tps6598x { struct device *dev; struct regmap *regmap; @@ -97,9 +130,11 @@ struct tps6598x { enum power_supply_usb_type usb_type; int wakeup; + u32 status; /* status reg */ u16 pwr_status; struct delayed_work wq_poll; - irq_handler_t irq_handler; + + const struct tipd_data *data; }; static enum power_supply_property tps6598x_psy_props[] = { @@ -180,6 +215,11 @@ static inline int tps6598x_read64(struct tps6598x *tps, u8 reg, u64 *val) return tps6598x_block_read(tps, reg, val, sizeof(u64)); } +static inline int tps6598x_write8(struct tps6598x *tps, u8 reg, u8 val) +{ + return tps6598x_block_write(tps, reg, &val, sizeof(u8)); +} + static inline int tps6598x_write64(struct tps6598x *tps, u8 reg, u64 val) { return tps6598x_block_write(tps, reg, &val, sizeof(u64)); @@ -282,9 +322,10 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status) power_supply_changed(tps->psy); } -static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, +static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd, size_t in_len, u8 *in_data, - size_t out_len, u8 *out_data) + size_t out_len, u8 *out_data, + u32 cmd_timeout_ms, u32 res_delay_ms) { unsigned long timeout; u32 val; @@ -307,8 +348,7 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, if (ret < 0) return ret; - /* XXX: Using 1s for now, but it may not be enough for every command. */ - timeout = jiffies + msecs_to_jiffies(1000); + timeout = jiffies + msecs_to_jiffies(cmd_timeout_ms); do { ret = tps6598x_read32(tps, TPS_REG_CMD1, &val); @@ -321,6 +361,9 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, return -ETIMEDOUT; } while (val); + /* some commands require delay for the result to be available */ + mdelay(res_delay_ms); + if (out_len) { ret = tps6598x_block_read(tps, TPS_REG_DATA1, out_data, out_len); @@ -345,6 +388,14 @@ static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, return 0; } +static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, + size_t in_len, u8 *in_data, + size_t out_len, u8 *out_data) +{ + return tps6598x_exec_cmd_tmo(tps, cmd, in_len, in_data, + out_len, out_data, 1000, 0); +} + static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role) { const char *cmd = (role == TYPEC_DEVICE) ? "SWUF" : "SWDF"; @@ -419,7 +470,9 @@ static bool tps6598x_read_status(struct tps6598x *tps, u32 *status) dev_err(tps->dev, "%s: failed to read status\n", __func__); return false; } - trace_tps6598x_status(*status); + + if (tps->data->trace_status) + tps->data->trace_status(*status); return true; } @@ -450,7 +503,9 @@ static bool tps6598x_read_power_status(struct tps6598x *tps) return false; } tps->pwr_status = pwr_status; - trace_tps6598x_power_status(pwr_status); + + if (tps->data->trace_power_status) + tps->data->trace_power_status(pwr_status); return true; } @@ -513,6 +568,65 @@ err_unlock: return IRQ_NONE; } +static bool tps6598x_has_role_changed(struct tps6598x *tps, u32 status) +{ + status ^= tps->status; + + return status & (TPS_STATUS_PORTROLE | TPS_STATUS_DATAROLE); +} + +static irqreturn_t tps25750_interrupt(int irq, void *data) +{ + struct tps6598x *tps = data; + u64 event[2] = { }; + u32 status; + int ret; + + mutex_lock(&tps->lock); + + ret = tps6598x_block_read(tps, TPS_REG_INT_EVENT1, event, 11); + if (ret) { + dev_err(tps->dev, "%s: failed to read events\n", __func__); + goto err_unlock; + } + trace_tps25750_irq(event[0]); + + if (!(event[0] | event[1])) + goto err_unlock; + + if (!tps6598x_read_status(tps, &status)) + goto err_clear_ints; + + if ((event[0] | event[1]) & TPS_REG_INT_POWER_STATUS_UPDATE) + if (!tps6598x_read_power_status(tps)) + goto err_clear_ints; + + if ((event[0] | event[1]) & TPS_REG_INT_DATA_STATUS_UPDATE) + if (!tps6598x_read_data_status(tps)) + goto err_clear_ints; + + /* + * data/port roles could be updated independently after + * a plug event. Therefore, we need to check + * for pr/dr status change to set TypeC dr/pr accordingly. + */ + if ((event[0] | event[1]) & TPS_REG_INT_PLUG_EVENT || + tps6598x_has_role_changed(tps, status)) + tps6598x_handle_plug_event(tps, status); + + tps->status = status; + +err_clear_ints: + tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event, 11); + +err_unlock: + mutex_unlock(&tps->lock); + + if (event[0] | event[1]) + return IRQ_HANDLED; + return IRQ_NONE; +} + static irqreturn_t tps6598x_interrupt(int irq, void *data) { struct tps6598x *tps = data; @@ -568,7 +682,7 @@ static void tps6598x_poll_work(struct work_struct *work) struct tps6598x *tps = container_of(to_delayed_work(work), struct tps6598x, wq_poll); - tps->irq_handler(0, tps); + tps->data->irq_handler(0, tps); queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, msecs_to_jiffies(POLL_INTERVAL)); } @@ -582,12 +696,15 @@ static int tps6598x_check_mode(struct tps6598x *tps) if (ret) return ret; - switch (match_string(modes, ARRAY_SIZE(modes), mode)) { + ret = match_string(modes, ARRAY_SIZE(modes), mode); + + switch (ret) { case TPS_MODE_APP: - return 0; + case TPS_MODE_PTCH: + return ret; case TPS_MODE_BOOT: dev_warn(tps->dev, "dead-battery condition\n"); - return 0; + return ret; case TPS_MODE_BIST: case TPS_MODE_DISC: default: @@ -697,18 +814,375 @@ static int devm_tps6598_psy_register(struct tps6598x *tps) return PTR_ERR_OR_ZERO(tps->psy); } +static int +tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) +{ + int ret; + u32 conf; + struct typec_capability typec_cap = { }; + + ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf); + if (ret) + return ret; + + typec_cap.revision = USB_TYPEC_REV_1_2; + typec_cap.pd_revision = 0x200; + typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + typec_cap.driver_data = tps; + typec_cap.ops = &tps6598x_ops; + typec_cap.fwnode = fwnode; + + switch (TPS_SYSCONF_PORTINFO(conf)) { + case TPS_PORTINFO_SINK_ACCESSORY: + case TPS_PORTINFO_SINK: + typec_cap.type = TYPEC_PORT_SNK; + typec_cap.data = TYPEC_PORT_UFP; + break; + case TPS_PORTINFO_DRP_UFP_DRD: + case TPS_PORTINFO_DRP_DFP_DRD: + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_DRD; + break; + case TPS_PORTINFO_DRP_UFP: + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_UFP; + break; + case TPS_PORTINFO_DRP_DFP: + typec_cap.type = TYPEC_PORT_DRP; + typec_cap.data = TYPEC_PORT_DFP; + break; + case TPS_PORTINFO_SOURCE: + typec_cap.type = TYPEC_PORT_SRC; + typec_cap.data = TYPEC_PORT_DFP; + break; + default: + return -ENODEV; + } + + tps->port = typec_register_port(tps->dev, &typec_cap); + if (IS_ERR(tps->port)) + return PTR_ERR(tps->port); + + return 0; +} + +static int +tps25750_write_firmware(struct tps6598x *tps, + u8 bpms_addr, const u8 *data, size_t len) +{ + struct i2c_client *client = to_i2c_client(tps->dev); + int ret; + u8 slave_addr; + int timeout; + + slave_addr = client->addr; + timeout = client->adapter->timeout; + + /* + * binary configuration size is around ~16Kbytes + * which might take some time to finish writing it + */ + client->adapter->timeout = msecs_to_jiffies(5000); + client->addr = bpms_addr; + + ret = regmap_raw_write(tps->regmap, data[0], &data[1], len - 1); + + client->addr = slave_addr; + client->adapter->timeout = timeout; + + return ret; +} + +static int +tps25750_exec_pbms(struct tps6598x *tps, u8 *in_data, size_t in_len) +{ + int ret; + u8 rc; + + ret = tps6598x_exec_cmd_tmo(tps, "PBMs", in_len, in_data, + sizeof(rc), &rc, 4000, 0); + if (ret) + return ret; + + switch (rc) { + case TPS_TASK_BPMS_INVALID_BUNDLE_SIZE: + dev_err(tps->dev, "%s: invalid fw size\n", __func__); + return -EINVAL; + case TPS_TASK_BPMS_INVALID_SLAVE_ADDR: + dev_err(tps->dev, "%s: invalid slave address\n", __func__); + return -EINVAL; + case TPS_TASK_BPMS_INVALID_TIMEOUT: + dev_err(tps->dev, "%s: timed out\n", __func__); + return -ETIMEDOUT; + default: + break; + } + + return 0; +} + +static int tps25750_abort_patch_process(struct tps6598x *tps) +{ + int ret; + + ret = tps6598x_exec_cmd(tps, "PBMe", 0, NULL, 0, NULL); + if (ret) + return ret; + + ret = tps6598x_check_mode(tps); + if (ret != TPS_MODE_PTCH) + dev_err(tps->dev, "failed to switch to \"PTCH\" mode\n"); + + return ret; +} + +static int tps25750_start_patch_burst_mode(struct tps6598x *tps) +{ + int ret; + const struct firmware *fw; + const char *firmware_name; + struct { + u32 fw_size; + u8 addr; + u8 timeout; + } __packed bpms_data; + u32 addr; + struct device_node *np = tps->dev->of_node; + + 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) { + ret = -EINVAL; + goto release_fw; + } + + ret = of_property_match_string(np, "reg-names", "patch-address"); + if (ret < 0) { + dev_err(tps->dev, "failed to get patch-address %d\n", ret); + goto release_fw; + } + + ret = of_property_read_u32_index(np, "reg", ret, &addr); + if (ret) + goto release_fw; + + if (addr == 0 || (addr >= 0x20 && addr <= 0x23)) { + dev_err(tps->dev, "wrong patch address %u\n", addr); + ret = -EINVAL; + goto release_fw; + } + + bpms_data.addr = (u8)addr; + bpms_data.fw_size = fw->size; + bpms_data.timeout = TPS_BUNDLE_TIMEOUT; + + ret = tps25750_exec_pbms(tps, (u8 *)&bpms_data, sizeof(bpms_data)); + if (ret) + goto release_fw; + + ret = tps25750_write_firmware(tps, bpms_data.addr, fw->data, fw->size); + if (ret) { + dev_err(tps->dev, "Failed to write patch %s of %zu bytes\n", + firmware_name, fw->size); + goto release_fw; + } + + /* + * A delay of 500us is required after the firmware is written + * based on pg.62 in tps6598x Host Interface Technical + * Reference Manual + * https://www.ti.com/lit/ug/slvuc05a/slvuc05a.pdf + */ + udelay(500); + +release_fw: + release_firmware(fw); + + return ret; +} + +static int tps25750_complete_patch_process(struct tps6598x *tps) +{ + int ret; + u8 out_data[40]; + u8 dummy[2] = { }; + + /* + * Without writing something to DATA_IN, this command would + * return an error + */ + ret = tps6598x_exec_cmd_tmo(tps, "PBMc", sizeof(dummy), dummy, + sizeof(out_data), out_data, 2000, 20); + if (ret) + return ret; + + if (out_data[TPS_PBMC_RC]) { + dev_err(tps->dev, + "%s: pbmc failed: %u\n", __func__, + out_data[TPS_PBMC_RC]); + return -EIO; + } + + if (out_data[TPS_PBMC_DPCS]) { + dev_err(tps->dev, + "%s: failed device patch complete status: %u\n", + __func__, out_data[TPS_PBMC_DPCS]); + return -EIO; + } + + return 0; +} + +static int tps25750_apply_patch(struct tps6598x *tps) +{ + int ret; + unsigned long timeout; + u64 status = 0; + + ret = tps6598x_block_read(tps, TPS_REG_BOOT_STATUS, &status, 5); + if (ret) + return ret; + /* + * Nothing to be done if the configuration + * is being loaded from EERPOM + */ + if (status & TPS_BOOT_STATUS_I2C_EEPROM_PRESENT) + goto wait_for_app; + + ret = tps25750_start_patch_burst_mode(tps); + if (ret) { + tps25750_abort_patch_process(tps); + return ret; + } + + ret = tps25750_complete_patch_process(tps); + if (ret) + return ret; + +wait_for_app: + timeout = jiffies + msecs_to_jiffies(1000); + + do { + ret = tps6598x_check_mode(tps); + if (ret < 0) + return ret; + + if (time_is_before_jiffies(timeout)) + return -ETIMEDOUT; + + } while (ret != TPS_MODE_APP); + + /* + * The dead battery flag may be triggered when the controller + * port is connected to a device that can source power and + * attempts to power up both the controller and the board it is on. + * To restore controller functionality, it is necessary to clear + * this flag + */ + if (status & TPS_BOOT_STATUS_DEAD_BATTERY_FLAG) { + ret = tps6598x_exec_cmd(tps, "DBfg", 0, NULL, 0, NULL); + if (ret) { + dev_err(tps->dev, "failed to clear dead battery %d\n", ret); + return ret; + } + } + + dev_info(tps->dev, "controller switched to \"APP\" mode\n"); + + return 0; +}; + +static int tps25750_init(struct tps6598x *tps) +{ + int ret; + + ret = tps->data->apply_patch(tps); + if (ret) + return ret; + + ret = tps6598x_write8(tps, TPS_REG_SLEEP_CONF, + TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED); + if (ret) + dev_warn(tps->dev, + "%s: failed to enable sleep mode: %d\n", + __func__, ret); + + return 0; +} + +static int +tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) +{ + struct typec_capability typec_cap = { }; + const char *data_role; + u8 pd_status; + int ret; + + ret = tps6598x_read8(tps, TPS_REG_PD_STATUS, &pd_status); + if (ret) + return ret; + + ret = fwnode_property_read_string(fwnode, "data-role", &data_role); + if (ret) { + dev_err(tps->dev, "data-role not found: %d\n", ret); + return ret; + } + + ret = typec_find_port_data_role(data_role); + if (ret < 0) { + dev_err(tps->dev, "unknown data-role: %s\n", data_role); + return ret; + } + + typec_cap.data = ret; + typec_cap.revision = USB_TYPEC_REV_1_3; + typec_cap.pd_revision = 0x300; + typec_cap.driver_data = tps; + typec_cap.ops = &tps6598x_ops; + typec_cap.fwnode = fwnode; + typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; + + switch (TPS_PD_STATUS_PORT_TYPE(pd_status)) { + case TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE: + case TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK: + typec_cap.type = TYPEC_PORT_DRP; + break; + case TPS_PD_STATUS_PORT_TYPE_SINK: + typec_cap.type = TYPEC_PORT_SNK; + break; + case TPS_PD_STATUS_PORT_TYPE_SOURCE: + typec_cap.type = TYPEC_PORT_SRC; + break; + default: + return -ENODEV; + } + + tps->port = typec_register_port(tps->dev, &typec_cap); + if (IS_ERR(tps->port)) + return PTR_ERR(tps->port); + + return 0; +} + static int tps6598x_probe(struct i2c_client *client) { - irq_handler_t irq_handler = tps6598x_interrupt; struct device_node *np = client->dev.of_node; - struct typec_capability typec_cap = { }; struct tps6598x *tps; struct fwnode_handle *fwnode; u32 status; - u32 conf; u32 vid; int ret; u64 mask1; + bool is_tps25750; tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL); if (!tps) @@ -721,9 +1195,12 @@ static int tps6598x_probe(struct i2c_client *client) if (IS_ERR(tps->regmap)) return PTR_ERR(tps->regmap); - ret = tps6598x_read32(tps, TPS_REG_VID, &vid); - if (ret < 0 || !vid) - return -ENODEV; + is_tps25750 = device_is_compatible(tps->dev, "ti,tps25750"); + if (!is_tps25750) { + ret = tps6598x_read32(tps, TPS_REG_VID, &vid); + if (ret < 0 || !vid) + return -ENODEV; + } /* * Checking can the adapter handle SMBus protocol. If it can not, the @@ -743,7 +1220,6 @@ static int tps6598x_probe(struct i2c_client *client) APPLE_CD_REG_INT_DATA_STATUS_UPDATE | APPLE_CD_REG_INT_PLUG_EVENT; - irq_handler = cd321x_interrupt; } else { /* Enable power status, data status and plug event interrupts */ mask1 = TPS_REG_INT_POWER_STATUS_UPDATE | @@ -751,24 +1227,32 @@ static int tps6598x_probe(struct i2c_client *client) TPS_REG_INT_PLUG_EVENT; } - tps->irq_handler = irq_handler; + if (dev_fwnode(tps->dev)) + tps->data = device_get_match_data(tps->dev); + else + tps->data = i2c_get_match_data(client); + if (!tps->data) + return -EINVAL; + /* Make sure the controller has application firmware running */ ret = tps6598x_check_mode(tps); - if (ret) + if (ret < 0) return ret; + if (is_tps25750 && ret == TPS_MODE_PTCH) { + ret = tps25750_init(tps); + if (ret) + return ret; + } + ret = tps6598x_write64(tps, TPS_REG_INT_MASK1, mask1); if (ret) - return ret; - - ret = tps6598x_read32(tps, TPS_REG_STATUS, &status); - if (ret < 0) - goto err_clear_mask; - trace_tps6598x_status(status); + goto err_reset_controller; - ret = tps6598x_read32(tps, TPS_REG_SYSTEM_CONF, &conf); - if (ret < 0) + if (!tps6598x_read_status(tps, &status)) { + ret = -ENODEV; goto err_clear_mask; + } /* * This fwnode has a "compatible" property, but is never populated as a @@ -787,50 +1271,13 @@ static int tps6598x_probe(struct i2c_client *client) goto err_fwnode_put; } - typec_cap.revision = USB_TYPEC_REV_1_2; - typec_cap.pd_revision = 0x200; - typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; - typec_cap.driver_data = tps; - typec_cap.ops = &tps6598x_ops; - typec_cap.fwnode = fwnode; - - switch (TPS_SYSCONF_PORTINFO(conf)) { - case TPS_PORTINFO_SINK_ACCESSORY: - case TPS_PORTINFO_SINK: - typec_cap.type = TYPEC_PORT_SNK; - typec_cap.data = TYPEC_PORT_UFP; - break; - case TPS_PORTINFO_DRP_UFP_DRD: - case TPS_PORTINFO_DRP_DFP_DRD: - typec_cap.type = TYPEC_PORT_DRP; - typec_cap.data = TYPEC_PORT_DRD; - break; - case TPS_PORTINFO_DRP_UFP: - typec_cap.type = TYPEC_PORT_DRP; - typec_cap.data = TYPEC_PORT_UFP; - break; - case TPS_PORTINFO_DRP_DFP: - typec_cap.type = TYPEC_PORT_DRP; - typec_cap.data = TYPEC_PORT_DFP; - break; - case TPS_PORTINFO_SOURCE: - typec_cap.type = TYPEC_PORT_SRC; - typec_cap.data = TYPEC_PORT_DFP; - break; - default: - ret = -ENODEV; - goto err_role_put; - } - ret = devm_tps6598_psy_register(tps); if (ret) goto err_role_put; - tps->port = typec_register_port(&client->dev, &typec_cap); - if (IS_ERR(tps->port)) { - ret = PTR_ERR(tps->port); + ret = tps->data->register_port(tps, fwnode); + if (ret) goto err_role_put; - } if (status & TPS_STATUS_PLUG_PRESENT) { ret = tps6598x_read16(tps, TPS_REG_POWER_STATUS, &tps->pwr_status); @@ -845,7 +1292,7 @@ static int tps6598x_probe(struct i2c_client *client) if (client->irq) { ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, - irq_handler, + tps->data->irq_handler, IRQF_SHARED | IRQF_ONESHOT, dev_name(&client->dev), tps); } else { @@ -879,6 +1326,10 @@ err_fwnode_put: fwnode_handle_put(fwnode); 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); return ret; } @@ -889,9 +1340,14 @@ static void tps6598x_remove(struct i2c_client *client) if (!client->irq) cancel_delayed_work_sync(&tps->wq_poll); + devm_free_irq(tps->dev, client->irq, tps); tps6598x_disconnect(tps, 0); typec_unregister_port(tps->port); 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); } static int __maybe_unused tps6598x_suspend(struct device *dev) @@ -914,6 +1370,17 @@ static int __maybe_unused tps6598x_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct tps6598x *tps = i2c_get_clientdata(client); + int ret; + + ret = tps6598x_check_mode(tps); + if (ret < 0) + return ret; + + if (device_is_compatible(tps->dev, "ti,tps25750") && ret == TPS_MODE_PTCH) { + ret = tps25750_init(tps); + if (ret) + return ret; + } if (tps->wakeup) { disable_irq_wake(client->irq); @@ -931,15 +1398,38 @@ static const struct dev_pm_ops tps6598x_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(tps6598x_suspend, tps6598x_resume) }; +static const struct tipd_data cd321x_data = { + .irq_handler = cd321x_interrupt, + .register_port = tps6598x_register_port, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, +}; + +static const struct tipd_data tps6598x_data = { + .irq_handler = tps6598x_interrupt, + .register_port = tps6598x_register_port, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, +}; + +static const struct tipd_data tps25750_data = { + .irq_handler = tps25750_interrupt, + .register_port = tps25750_register_port, + .trace_power_status = trace_tps25750_power_status, + .trace_status = trace_tps25750_status, + .apply_patch = tps25750_apply_patch, +}; + static const struct of_device_id tps6598x_of_match[] = { - { .compatible = "ti,tps6598x", }, - { .compatible = "apple,cd321x", }, + { .compatible = "ti,tps6598x", &tps6598x_data}, + { .compatible = "apple,cd321x", &cd321x_data}, + { .compatible = "ti,tps25750", &tps25750_data}, {} }; MODULE_DEVICE_TABLE(of, tps6598x_of_match); static const struct i2c_device_id tps6598x_id[] = { - { "tps6598x" }, + { "tps6598x", (kernel_ulong_t)&tps6598x_data }, { } }; MODULE_DEVICE_TABLE(i2c, tps6598x_id); diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 527857549d..01609bf509 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -161,6 +161,25 @@ #define TPS_POWER_STATUS_BC12_STATUS_CDP 2 #define TPS_POWER_STATUS_BC12_STATUS_DCP 3 +/* TPS25750_REG_POWER_STATUS bits */ +#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK GENMASK(7, 4) +#define TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(p) \ + TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS_MASK, (p)) +#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK GENMASK(9, 8) +#define TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS(p) \ + TPS_FIELD_GET(TPS25750_POWER_STATUS_CHARGER_ADVERTISE_STATUS_MASK, (p)) + +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED 0 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS 1 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE 2 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD 3 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD 4 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD 5 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP 6 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP 7 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP 8 +#define TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP 9 + /* TPS_REG_DATA_STATUS bits */ #define TPS_DATA_STATUS_DATA_CONNECTION BIT(0) #define TPS_DATA_STATUS_UPSIDE_DOWN BIT(1) @@ -199,4 +218,21 @@ #define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_A BIT(2) #define TPS_DATA_STATUS_DP_SPEC_PIN_ASSIGNMENT_B (BIT(2) | BIT(1)) +/* BOOT STATUS REG*/ +#define TPS_BOOT_STATUS_DEAD_BATTERY_FLAG BIT(2) +#define TPS_BOOT_STATUS_I2C_EEPROM_PRESENT BIT(3) + +/* PD STATUS REG */ +#define TPS_REG_PD_STATUS_PORT_TYPE_MASK GENMASK(5, 4) +#define TPS_PD_STATUS_PORT_TYPE(x) \ + TPS_FIELD_GET(TPS_REG_PD_STATUS_PORT_TYPE_MASK, x) + +#define TPS_PD_STATUS_PORT_TYPE_SINK_SOURCE 0 +#define TPS_PD_STATUS_PORT_TYPE_SINK 1 +#define TPS_PD_STATUS_PORT_TYPE_SOURCE 2 +#define TPS_PD_STATUS_PORT_TYPE_SOURCE_SINK 3 + +/* SLEEP CONF REG */ +#define TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED BIT(0) + #endif /* __TPS6598X_H__ */ diff --git a/drivers/usb/typec/tipd/trace.h b/drivers/usb/typec/tipd/trace.h index 12cad1bde7..0669cca12e 100644 --- a/drivers/usb/typec/tipd/trace.h +++ b/drivers/usb/typec/tipd/trace.h @@ -74,6 +74,13 @@ { APPLE_CD_REG_INT_DATA_STATUS_UPDATE, "DATA_STATUS_UPDATE" }, \ { APPLE_CD_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" }) +#define show_tps25750_irq_flags(flags) \ + __print_flags_u64(flags, "|", \ + { TPS_REG_INT_PLUG_EVENT, "PLUG_EVENT" }, \ + { TPS_REG_INT_POWER_STATUS_UPDATE, "POWER_STATUS_UPDATE" }, \ + { TPS_REG_INT_STATUS_UPDATE, "STATUS_UPDATE" }, \ + { TPS_REG_INT_PD_STATUS_UPDATE, "PD_STATUS_UPDATE" }) + #define TPS6598X_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \ TPS_STATUS_PP_5V0_SWITCH_MASK | \ TPS_STATUS_PP_HV_SWITCH_MASK | \ @@ -84,6 +91,14 @@ TPS_STATUS_USB_HOST_PRESENT_MASK | \ TPS_STATUS_LEGACY_MASK)) +#define TPS25750_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_STATUS_CONN_STATE_MASK | \ + GENMASK(19, 7) | \ + TPS_STATUS_VBUS_STATUS_MASK | \ + TPS_STATUS_USB_HOST_PRESENT_MASK | \ + TPS_STATUS_LEGACY_MASK | \ + BIT(26) | \ + GENMASK(31, 28))) + #define show_status_conn_state(status) \ __print_symbolic(TPS_STATUS_CONN_STATE((status)), \ { TPS_STATUS_CONN_STATE_CONN_WITH_R_A, "conn-Ra" }, \ @@ -141,6 +156,14 @@ { TPS_STATUS_HIGH_VOLAGE_WARNING, "HIGH_VOLAGE_WARNING" }, \ { TPS_STATUS_HIGH_LOW_VOLTAGE_WARNING, "HIGH_LOW_VOLTAGE_WARNING" }) +#define show_tps25750_status_flags(flags) \ + __print_flags((flags & TPS25750_STATUS_FLAGS_MASK), "|", \ + { TPS_STATUS_PLUG_PRESENT, "PLUG_PRESENT" }, \ + { TPS_STATUS_PLUG_UPSIDE_DOWN, "UPSIDE_DOWN" }, \ + { TPS_STATUS_PORTROLE, "PORTROLE" }, \ + { TPS_STATUS_DATAROLE, "DATAROLE" }, \ + { TPS_STATUS_BIST, "BIST" }) + #define show_power_status_source_sink(power_status) \ __print_symbolic(TPS_POWER_STATUS_SOURCESINK(power_status), \ { 1, "sink" }, \ @@ -159,6 +182,19 @@ { TPS_POWER_STATUS_BC12_STATUS_CDP, "cdp" }, \ { TPS_POWER_STATUS_BC12_STATUS_SDP, "sdp" }) +#define show_tps25750_power_status_charger_detect_status(power_status) \ + __print_symbolic(TPS25750_POWER_STATUS_CHARGER_DETECT_STATUS(power_status), \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DISABLED, "disabled"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_IN_PROGRESS, "in progress"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_NONE, "none"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_SPD, "spd"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_CPD, "cpd"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_BC_1_2_DPD, "dpd"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_1_DCP, "divider 1 dcp"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_2_DCP, "divider 2 dcp"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_DIV_3_DCP, "divider 3 dpc"}, \ + { TPS25750_POWER_STATUS_CHARGER_DET_STATUS_1_2V_DCP, "1.2V dpc"}) + #define TPS_DATA_STATUS_FLAGS_MASK (GENMASK(31, 0) ^ (TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK | \ TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK | \ TPS_DATA_STATUS_TBT_CABLE_GEN_MASK)) @@ -230,6 +266,21 @@ TRACE_EVENT(cd321x_irq, show_cd321x_irq_flags(__entry->event)) ); +TRACE_EVENT(tps25750_irq, + TP_PROTO(u64 event), + TP_ARGS(event), + + TP_STRUCT__entry( + __field(u64, event) + ), + + TP_fast_assign( + __entry->event = event; + ), + + TP_printk("event=%s", show_tps25750_irq_flags(__entry->event)) +); + TRACE_EVENT(tps6598x_status, TP_PROTO(u32 status), TP_ARGS(status), @@ -257,6 +308,27 @@ TRACE_EVENT(tps6598x_status, ) ); +TRACE_EVENT(tps25750_status, + TP_PROTO(u32 status), + TP_ARGS(status), + + TP_STRUCT__entry( + __field(u32, status) + ), + + TP_fast_assign( + __entry->status = status; + ), + + TP_printk("conn: %s, vbus: %s, usb-host: %s, legacy: %s, flags: %s", + show_status_conn_state(__entry->status), + show_status_vbus_status(__entry->status), + show_status_usb_host_present(__entry->status), + show_status_legacy(__entry->status), + show_tps25750_status_flags(__entry->status) + ) +); + TRACE_EVENT(tps6598x_power_status, TP_PROTO(u16 power_status), TP_ARGS(power_status), @@ -277,6 +349,26 @@ TRACE_EVENT(tps6598x_power_status, ) ); +TRACE_EVENT(tps25750_power_status, + TP_PROTO(u16 power_status), + TP_ARGS(power_status), + + TP_STRUCT__entry( + __field(u16, power_status) + ), + + TP_fast_assign( + __entry->power_status = power_status; + ), + + TP_printk("conn: %d, pwr-role: %s, typec: %s, charger detect: %s", + !!TPS_POWER_STATUS_CONNECTION(__entry->power_status), + show_power_status_source_sink(__entry->power_status), + show_power_status_typec_status(__entry->power_status), + show_tps25750_power_status_charger_detect_status(__entry->power_status) + ) +); + TRACE_EVENT(tps6598x_data_status, TP_PROTO(u32 data_status), TP_ARGS(data_status), diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 73cd5bf350..d9d3c91125 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -315,7 +315,7 @@ struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con, struct ucsi_dp *dp; /* We can't rely on the firmware with the capabilities. */ - desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE; + desc->vdo |= DP_CAP_DP_SIGNALLING(0) | DP_CAP_RECEPTACLE; /* Claiming that we support all pin assignments */ desc->vdo |= all_assignments << 8; diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 61b64558f9..8f9dff993b 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -935,7 +935,9 @@ static void ucsi_handle_connector_change(struct work_struct *work) clear_bit(EVENT_PENDING, &con->ucsi->flags); + mutex_lock(&ucsi->ppm_lock); ret = ucsi_acknowledge_connector_change(ucsi); + mutex_unlock(&ucsi->ppm_lock); if (ret) dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index 6bbf490ac4..928eacbeb2 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -25,6 +25,8 @@ struct ucsi_acpi { unsigned long flags; guid_t guid; u64 cmd; + bool dell_quirk_probed; + bool dell_quirk_active; }; static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) @@ -73,9 +75,13 @@ static int ucsi_acpi_sync_write(struct ucsi *ucsi, unsigned int offset, const void *val, size_t val_len) { struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI; int ret; - set_bit(COMMAND_PENDING, &ua->flags); + if (ack) + set_bit(ACK_PENDING, &ua->flags); + else + set_bit(COMMAND_PENDING, &ua->flags); ret = ucsi_acpi_async_write(ucsi, offset, val, val_len); if (ret) @@ -85,7 +91,10 @@ static int ucsi_acpi_sync_write(struct ucsi *ucsi, unsigned int offset, ret = -ETIMEDOUT; out_clear_bit: - clear_bit(COMMAND_PENDING, &ua->flags); + if (ack) + clear_bit(ACK_PENDING, &ua->flags); + else + clear_bit(COMMAND_PENDING, &ua->flags); return ret; } @@ -119,12 +128,73 @@ static const struct ucsi_operations ucsi_zenbook_ops = { .async_write = ucsi_acpi_async_write }; -static const struct dmi_system_id zenbook_dmi_id[] = { +/* + * Some Dell laptops expect that an ACK command with the + * UCSI_ACK_CONNECTOR_CHANGE bit set is followed by a (separate) + * ACK command that only has the UCSI_ACK_COMMAND_COMPLETE bit set. + * If this is not done events are not delivered to OSPM and + * subsequent commands will timeout. + */ +static int +ucsi_dell_sync_write(struct ucsi *ucsi, unsigned int offset, + const void *val, size_t val_len) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + u64 cmd = *(u64 *)val, ack = 0; + int ret; + + if (UCSI_COMMAND(cmd) == UCSI_ACK_CC_CI && + cmd & UCSI_ACK_CONNECTOR_CHANGE) + ack = UCSI_ACK_CC_CI | UCSI_ACK_COMMAND_COMPLETE; + + ret = ucsi_acpi_sync_write(ucsi, offset, val, val_len); + if (ret != 0) + return ret; + if (ack == 0) + return ret; + + if (!ua->dell_quirk_probed) { + ua->dell_quirk_probed = true; + + cmd = UCSI_GET_CAPABILITY; + ret = ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &cmd, + sizeof(cmd)); + if (ret == 0) + return ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, + &ack, sizeof(ack)); + if (ret != -ETIMEDOUT) + return ret; + + ua->dell_quirk_active = true; + dev_err(ua->dev, "Firmware bug: Additional ACK required after ACKing a connector change.\n"); + dev_err(ua->dev, "Firmware bug: Enabling workaround\n"); + } + + if (!ua->dell_quirk_active) + return ret; + + return ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &ack, sizeof(ack)); +} + +static const struct ucsi_operations ucsi_dell_ops = { + .read = ucsi_acpi_read, + .sync_write = ucsi_dell_sync_write, + .async_write = ucsi_acpi_async_write +}; + +static const struct dmi_system_id ucsi_acpi_quirks[] = { { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UA_UM325UA"), }, + .driver_data = (void *)&ucsi_zenbook_ops, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + }, + .driver_data = (void *)&ucsi_dell_ops, }, { } }; @@ -142,8 +212,10 @@ static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) if (UCSI_CCI_CONNECTOR(cci)) ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci)); - if (test_bit(COMMAND_PENDING, &ua->flags) && - cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) + if (cci & UCSI_CCI_ACK_COMPLETE && test_bit(ACK_PENDING, &ua->flags)) + complete(&ua->complete); + if (cci & UCSI_CCI_COMMAND_COMPLETE && + test_bit(COMMAND_PENDING, &ua->flags)) complete(&ua->complete); } @@ -151,6 +223,7 @@ static int ucsi_acpi_probe(struct platform_device *pdev) { struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); const struct ucsi_operations *ops = &ucsi_acpi_ops; + const struct dmi_system_id *id; struct ucsi_acpi *ua; struct resource *res; acpi_status status; @@ -180,8 +253,9 @@ static int ucsi_acpi_probe(struct platform_device *pdev) init_completion(&ua->complete); ua->dev = &pdev->dev; - if (dmi_check_system(zenbook_dmi_id)) - ops = &ucsi_zenbook_ops; + id = dmi_first_match(ucsi_acpi_quirks); + if (id) + ops = id->driver_data; ua->ucsi = ucsi_create(&pdev->dev, ops); if (IS_ERR(ua->ucsi)) diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 607061a37e..449c125f6f 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -501,8 +501,8 @@ static void ucsi_ccg_nvidia_altmode(struct ucsi_ccg *uc, case NVIDIA_FTB_DP_OFFSET: if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DBG_VDO) alt[0].mid = USB_TYPEC_NVIDIA_VLINK_DP_VDO | - DP_CAP_DP_SIGNALING | DP_CAP_USB | - DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E)); + DP_CAP_DP_SIGNALLING(0) | DP_CAP_USB | + DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_E)); break; case NVIDIA_FTB_DBG_OFFSET: if (alt[0].mid == USB_TYPEC_NVIDIA_VLINK_DP_VDO) |