diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-07 09:42:16 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-07 09:42:16 -0700 |
commit | e611c0fe318c6d6827ee2bba660fbc23cf73f7dc (patch) | |
tree | 00854551e810b32cceab84157882eab147f2a98c /drivers/usb/typec | |
parent | 3b69e8b4571125bec1f77f886174fe6cab6b9d75 (diff) | |
parent | 347052e3bf1b62a25c11f7a673acfbaf554d67a1 (diff) | |
download | linux-stable-e611c0fe318c6d6827ee2bba660fbc23cf73f7dc.tar.gz linux-stable-e611c0fe318c6d6827ee2bba660fbc23cf73f7dc.tar.bz2 linux-stable-e611c0fe318c6d6827ee2bba660fbc23cf73f7dc.zip |
Merge tag 'usb-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB/PHY driver updates from Greg KH:
"Here are the large set of USB and PHY driver updates for 5.8-rc1.
Nothing huge, just lots of little things:
- USB gadget fixes and additions all over the place
- new PHY drivers
- PHY driver fixes and updates
- XHCI driver updates
- musb driver updates
- more USB-serial driver ids added
- various USB quirks added
- thunderbolt minor updates and fixes
- typec updates and additions
All of these have been in linux-next for a while with no reported
issues"
* tag 'usb-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (245 commits)
usb: dwc3: meson-g12a: fix USB2 PHY initialization on G12A and A1 SoCs
usb: dwc3: meson-g12a: fix error path when fetching the reset line fails
Revert "dt-bindings: usb: qcom,dwc3: Convert USB DWC3 bindings"
Revert "dt-bindings: usb: qcom,dwc3: Add compatible for SC7180"
Revert "dt-bindings: usb: qcom,dwc3: Introduce interconnect properties for Qualcomm DWC3 driver"
USB: serial: ch341: fix lockup of devices with limited prescaler
USB: serial: ch341: add basis for quirk detection
CDC-ACM: heed quirk also in error handling
USB: serial: option: add Telit LE910C1-EUX compositions
usb: musb: Fix runtime PM imbalance on error
usb: musb: jz4740: Prevent lockup when CONFIG_SMP is set
usb: musb: mediatek: add reset FADDR to zero in reset interrupt handle
usb: musb: use true for 'use_dma'
usb: musb: start session in resume for host port
usb: musb: return -ESHUTDOWN in urb when three-strikes error happened
USB: serial: qcserial: add DW5816e QDL support
thunderbolt: Add trivial .shutdown
usb: dwc3: keystone: Turn on USB3 PHY before controller
dt-bindings: usb: ti,keystone-dwc3.yaml: Add USB3.0 PHY property
dt-bindings: usb: convert keystone-usb.txt to YAML
...
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r-- | drivers/usb/typec/Kconfig | 3 | ||||
-rw-r--r-- | drivers/usb/typec/class.c | 36 | ||||
-rw-r--r-- | drivers/usb/typec/mux/intel_pmc_mux.c | 42 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/fusb302.c | 32 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/fusb302_reg.h | 2 | ||||
-rw-r--r-- | drivers/usb/typec/tps6598x.c | 64 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/Makefile | 4 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/psy.c | 241 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/trace.c | 10 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 41 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.h | 26 |
11 files changed, 444 insertions, 57 deletions
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index b4f2aac7ae8a..559dd06117e7 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -64,7 +64,8 @@ config TYPEC_HD3SS3220 config TYPEC_TPS6598X tristate "TI TPS6598x USB Power Delivery controller driver" depends on I2C - select REGMAP_I2C + depends on REGMAP_I2C + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH help Say Y or M here if your system has TI TPS65982 or TPS65983 USB Power Delivery controller. diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 8d894bdff77d..c9234748537a 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -917,6 +917,12 @@ EXPORT_SYMBOL_GPL(typec_unregister_cable); /* ------------------------------------------------------------------------- */ /* USB Type-C ports */ +static const char * const typec_orientations[] = { + [TYPEC_ORIENTATION_NONE] = "unknown", + [TYPEC_ORIENTATION_NORMAL] = "normal", + [TYPEC_ORIENTATION_REVERSE] = "reverse", +}; + static const char * const typec_roles[] = { [TYPEC_SINK] = "sink", [TYPEC_SOURCE] = "source", @@ -1248,18 +1254,9 @@ static ssize_t orientation_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct typec_port *p = to_typec_port(dev); - enum typec_orientation orientation = typec_get_orientation(p); - - switch (orientation) { - case TYPEC_ORIENTATION_NORMAL: - return sprintf(buf, "%s\n", "normal"); - case TYPEC_ORIENTATION_REVERSE: - return sprintf(buf, "%s\n", "reverse"); - case TYPEC_ORIENTATION_NONE: - default: - return sprintf(buf, "%s\n", "unknown"); - } + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", typec_orientations[port->orientation]); } static DEVICE_ATTR_RO(orientation); @@ -1452,6 +1449,21 @@ void typec_set_pwr_opmode(struct typec_port *port, EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); /** + * typec_find_orientation - Convert orientation string to enum typec_orientation + * @name: Orientation string + * + * This routine is used to find the typec_orientation by its string name @name. + * + * Returns the orientation value on success, otherwise negative error code. + */ +int typec_find_orientation(const char *name) +{ + return match_string(typec_orientations, ARRAY_SIZE(typec_orientations), + name); +} +EXPORT_SYMBOL_GPL(typec_find_orientation); + +/** * typec_find_port_power_role - Get the typec port power capability * @name: port power capability string * diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 1ac0a3eb7dd8..962bc69a6a59 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -92,6 +92,9 @@ struct pmc_usb_port { u8 usb2_port; u8 usb3_port; + + enum typec_orientation sbu_orientation; + enum typec_orientation hsl_orientation; }; struct pmc_usb { @@ -101,6 +104,22 @@ struct pmc_usb { struct pmc_usb_port *port; }; +static int sbu_orientation(struct pmc_usb_port *port) +{ + if (port->sbu_orientation) + return port->sbu_orientation - 1; + + return port->orientation - 1; +} + +static int hsl_orientation(struct pmc_usb_port *port) +{ + if (port->hsl_orientation) + return port->hsl_orientation - 1; + + return port->orientation - 1; +} + static int pmc_usb_command(struct pmc_usb_port *port, u8 *msg, u32 len) { u8 response[4]; @@ -152,8 +171,9 @@ pmc_usb_mux_dp(struct pmc_usb_port *port, struct typec_mux_state *state) req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; - req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_AUX_SHIFT; - req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_HSL_SHIFT; + + req.mode_data |= sbu_orientation(port) << PMC_USB_ALTMODE_ORI_AUX_SHIFT; + req.mode_data |= hsl_orientation(port) << PMC_USB_ALTMODE_ORI_HSL_SHIFT; req.mode_data |= (state->mode - TYPEC_STATE_MODAL) << PMC_USB_ALTMODE_DP_MODE_SHIFT; @@ -177,8 +197,9 @@ pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; - req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_AUX_SHIFT; - req.mode_data |= (port->orientation - 1) << PMC_USB_ALTMODE_ORI_HSL_SHIFT; + + req.mode_data |= sbu_orientation(port) << PMC_USB_ALTMODE_ORI_AUX_SHIFT; + req.mode_data |= hsl_orientation(port) << PMC_USB_ALTMODE_ORI_HSL_SHIFT; if (TBT_ADAPTER(data->device_mode) == TBT_ADAPTER_TBT3) req.mode_data |= PMC_USB_ALTMODE_TBT_TYPE; @@ -215,8 +236,8 @@ static int pmc_usb_connect(struct pmc_usb_port *port) msg[0] |= port->usb3_port << PMC_USB_MSG_USB3_PORT_SHIFT; msg[1] = port->usb2_port << PMC_USB_MSG_USB2_PORT_SHIFT; - msg[1] |= (port->orientation - 1) << PMC_USB_MSG_ORI_HSL_SHIFT; - msg[1] |= (port->orientation - 1) << PMC_USB_MSG_ORI_AUX_SHIFT; + msg[1] |= hsl_orientation(port) << PMC_USB_MSG_ORI_HSL_SHIFT; + msg[1] |= sbu_orientation(port) << PMC_USB_MSG_ORI_AUX_SHIFT; return pmc_usb_command(port, msg, sizeof(msg)); } @@ -300,6 +321,7 @@ static int pmc_usb_register_port(struct pmc_usb *pmc, int index, struct usb_role_switch_desc desc = { }; struct typec_switch_desc sw_desc = { }; struct typec_mux_desc mux_desc = { }; + const char *str; int ret; ret = fwnode_property_read_u8(fwnode, "usb2-port-number", &port->usb2_port); @@ -310,6 +332,14 @@ static int pmc_usb_register_port(struct pmc_usb *pmc, int index, if (ret) return ret; + ret = fwnode_property_read_string(fwnode, "sbu-orientation", &str); + if (!ret) + port->sbu_orientation = typec_find_orientation(str); + + ret = fwnode_property_read_string(fwnode, "hsl-orientation", &str); + if (!ret) + port->hsl_orientation = typec_find_orientation(str); + port->num = index; port->pmc = pmc; diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index b498960ff72b..b28facece43c 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -9,14 +9,13 @@ #include <linux/delay.h> #include <linux/errno.h> #include <linux/extcon.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/of_device.h> -#include <linux/of_gpio.h> #include <linux/pinctrl/consumer.h> #include <linux/proc_fs.h> #include <linux/regulator/consumer.h> @@ -83,7 +82,7 @@ struct fusb302_chip { struct work_struct irq_work; bool irq_suspended; bool irq_while_suspended; - int gpio_int_n; + struct gpio_desc *gpio_int_n; int gpio_int_n_irq; struct extcon_dev *extcon; @@ -1618,30 +1617,17 @@ done: static int init_gpio(struct fusb302_chip *chip) { - struct device_node *node; + struct device *dev = chip->dev; int ret = 0; - node = chip->dev->of_node; - chip->gpio_int_n = of_get_named_gpio(node, "fcs,int_n", 0); - if (!gpio_is_valid(chip->gpio_int_n)) { - ret = chip->gpio_int_n; - dev_err(chip->dev, "cannot get named GPIO Int_N, ret=%d", ret); - return ret; - } - ret = devm_gpio_request(chip->dev, chip->gpio_int_n, "fcs,int_n"); - if (ret < 0) { - dev_err(chip->dev, "cannot request GPIO Int_N, ret=%d", ret); - return ret; - } - ret = gpio_direction_input(chip->gpio_int_n); - if (ret < 0) { - dev_err(chip->dev, - "cannot set GPIO Int_N to input, ret=%d", ret); - return ret; + chip->gpio_int_n = devm_gpiod_get(dev, "fcs,int_n", GPIOD_IN); + if (IS_ERR(chip->gpio_int_n)) { + dev_err(dev, "failed to request gpio_int_n\n"); + return PTR_ERR(chip->gpio_int_n); } - ret = gpio_to_irq(chip->gpio_int_n); + ret = gpiod_to_irq(chip->gpio_int_n); if (ret < 0) { - dev_err(chip->dev, + dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); return ret; } diff --git a/drivers/usb/typec/tcpm/fusb302_reg.h b/drivers/usb/typec/tcpm/fusb302_reg.h index 00b39d365478..edc0e4b0f1e6 100644 --- a/drivers/usb/typec/tcpm/fusb302_reg.h +++ b/drivers/usb/typec/tcpm/fusb302_reg.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright 2016-2017 Google, Inc * diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c index 0698addd1185..b7c9fe5caabe 100644 --- a/drivers/usb/typec/tps6598x.c +++ b/drivers/usb/typec/tps6598x.c @@ -12,6 +12,7 @@ #include <linux/regmap.h> #include <linux/interrupt.h> #include <linux/usb/typec.h> +#include <linux/usb/role.h> /* Register offsets */ #define TPS_REG_VID 0x00 @@ -94,6 +95,7 @@ struct tps6598x { struct typec_port *port; struct typec_partner *partner; struct usb_pd_identity partner_identity; + struct usb_role_switch *role_sw; }; /* @@ -190,6 +192,23 @@ static int tps6598x_read_partner_identity(struct tps6598x *tps) return 0; } +static void tps6598x_set_data_role(struct tps6598x *tps, + enum typec_data_role role, bool connected) +{ + enum usb_role role_val; + + if (role == TYPEC_HOST) + role_val = USB_ROLE_HOST; + else + role_val = USB_ROLE_DEVICE; + + if (!connected) + role_val = USB_ROLE_NONE; + + usb_role_switch_set_role(tps->role_sw, role_val); + typec_set_data_role(tps->port, role); +} + static int tps6598x_connect(struct tps6598x *tps, u32 status) { struct typec_partner_desc desc; @@ -220,7 +239,7 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) typec_set_pwr_opmode(tps->port, mode); typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status)); typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status)); - typec_set_data_role(tps->port, TPS_STATUS_DATAROLE(status)); + tps6598x_set_data_role(tps, TPS_STATUS_DATAROLE(status), true); tps->partner = typec_register_partner(tps->port, &desc); if (IS_ERR(tps->partner)) @@ -240,7 +259,7 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status) typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB); typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status)); typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status)); - typec_set_data_role(tps->port, TPS_STATUS_DATAROLE(status)); + tps6598x_set_data_role(tps, TPS_STATUS_DATAROLE(status), false); } static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd, @@ -328,7 +347,7 @@ static int tps6598x_dr_set(struct typec_port *port, enum typec_data_role role) goto out_unlock; } - typec_set_data_role(tps->port, role); + tps6598x_set_data_role(tps, role, true); out_unlock: mutex_unlock(&tps->lock); @@ -452,6 +471,7 @@ static int tps6598x_probe(struct i2c_client *client) { struct typec_capability typec_cap = { }; struct tps6598x *tps; + struct fwnode_handle *fwnode; u32 status; u32 conf; u32 vid; @@ -495,11 +515,22 @@ static int tps6598x_probe(struct i2c_client *client) if (ret < 0) return ret; + fwnode = device_get_named_child_node(&client->dev, "connector"); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + tps->role_sw = fwnode_usb_role_switch_get(fwnode); + if (IS_ERR(tps->role_sw)) { + ret = PTR_ERR(tps->role_sw); + 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: @@ -525,12 +556,16 @@ static int tps6598x_probe(struct i2c_client *client) typec_cap.data = TYPEC_PORT_DFP; break; default: - return -ENODEV; + ret = -ENODEV; + goto err_role_put; } tps->port = typec_register_port(&client->dev, &typec_cap); - if (IS_ERR(tps->port)) - return PTR_ERR(tps->port); + if (IS_ERR(tps->port)) { + ret = PTR_ERR(tps->port); + goto err_role_put; + } + fwnode_handle_put(fwnode); if (status & TPS_STATUS_PLUG_PRESENT) { ret = tps6598x_connect(tps, status); @@ -545,12 +580,19 @@ static int tps6598x_probe(struct i2c_client *client) if (ret) { tps6598x_disconnect(tps, 0); typec_unregister_port(tps->port); - return ret; + goto err_role_put; } i2c_set_clientdata(client, tps); return 0; + +err_role_put: + usb_role_switch_put(tps->role_sw); +err_fwnode_put: + fwnode_handle_put(fwnode); + + return ret; } static int tps6598x_remove(struct i2c_client *client) @@ -559,10 +601,17 @@ static int tps6598x_remove(struct i2c_client *client) tps6598x_disconnect(tps, 0); typec_unregister_port(tps->port); + usb_role_switch_put(tps->role_sw); return 0; } +static const struct of_device_id tps6598x_of_match[] = { + { .compatible = "ti,tps6598x", }, + {} +}; +MODULE_DEVICE_TABLE(of, tps6598x_of_match); + static const struct i2c_device_id tps6598x_id[] = { { "tps6598x" }, { } @@ -572,6 +621,7 @@ MODULE_DEVICE_TABLE(i2c, tps6598x_id); static struct i2c_driver tps6598x_i2c_driver = { .driver = { .name = "tps6598x", + .of_match_table = tps6598x_of_match, }, .probe_new = tps6598x_probe, .remove = tps6598x_remove, diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile index b35e15a1f02c..8a8eb5cb8e0f 100644 --- a/drivers/usb/typec/ucsi/Makefile +++ b/drivers/usb/typec/ucsi/Makefile @@ -7,6 +7,10 @@ typec_ucsi-y := ucsi.o typec_ucsi-$(CONFIG_TRACING) += trace.o +ifneq ($(CONFIG_POWER_SUPPLY),) + typec_ucsi-y += psy.o +endif + ifneq ($(CONFIG_TYPEC_DP_ALTMODE),) typec_ucsi-y += displayport.o endif diff --git a/drivers/usb/typec/ucsi/psy.c b/drivers/usb/typec/ucsi/psy.c new file mode 100644 index 000000000000..26ed0b520749 --- /dev/null +++ b/drivers/usb/typec/ucsi/psy.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Power Supply for UCSI + * + * Copyright (C) 2020, Intel Corporation + * Author: K V, Abhilash <abhilash.k.v@intel.com> + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/property.h> +#include <linux/usb/pd.h> + +#include "ucsi.h" + +/* Power Supply access to expose source power information */ +enum ucsi_psy_online_states { + UCSI_PSY_OFFLINE = 0, + UCSI_PSY_FIXED_ONLINE, + UCSI_PSY_PROG_ONLINE, +}; + +static enum power_supply_property ucsi_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int ucsi_psy_get_online(struct ucsi_connector *con, + union power_supply_propval *val) +{ + val->intval = UCSI_PSY_OFFLINE; + if (con->status.flags & UCSI_CONSTAT_CONNECTED && + (con->status.flags & UCSI_CONSTAT_PWR_DIR) == TYPEC_SINK) + val->intval = UCSI_PSY_FIXED_ONLINE; + return 0; +} + +static int ucsi_psy_get_voltage_min(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u32 pdo; + + switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + pdo = con->src_pdos[0]; + val->intval = pdo_fixed_voltage(pdo) * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + val->intval = UCSI_TYPEC_VSAFE5V * 1000; + break; + default: + val->intval = 0; + break; + } + return 0; +} + +static int ucsi_psy_get_voltage_max(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u32 pdo; + + switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + if (con->num_pdos > 0) { + pdo = con->src_pdos[con->num_pdos - 1]; + val->intval = pdo_fixed_voltage(pdo) * 1000; + } else { + val->intval = 0; + } + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + val->intval = UCSI_TYPEC_VSAFE5V * 1000; + break; + default: + val->intval = 0; + break; + } + return 0; +} + +static int ucsi_psy_get_voltage_now(struct ucsi_connector *con, + union power_supply_propval *val) +{ + int index; + u32 pdo; + + switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + index = rdo_index(con->rdo); + if (index > 0) { + pdo = con->src_pdos[index - 1]; + val->intval = pdo_fixed_voltage(pdo) * 1000; + } else { + val->intval = 0; + } + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + val->intval = UCSI_TYPEC_VSAFE5V * 1000; + break; + default: + val->intval = 0; + break; + } + return 0; +} + +static int ucsi_psy_get_current_max(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u32 pdo; + + switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + if (con->num_pdos > 0) { + pdo = con->src_pdos[con->num_pdos - 1]; + val->intval = pdo_max_current(pdo) * 1000; + } else { + val->intval = 0; + } + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + val->intval = UCSI_TYPEC_1_5_CURRENT * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + val->intval = UCSI_TYPEC_3_0_CURRENT * 1000; + break; + case UCSI_CONSTAT_PWR_OPMODE_BC: + case UCSI_CONSTAT_PWR_OPMODE_DEFAULT: + /* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */ + default: + val->intval = 0; + break; + } + return 0; +} + +static int ucsi_psy_get_current_now(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u16 flags = con->status.flags; + + if (UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD) + val->intval = rdo_op_current(con->rdo) * 1000; + else + val->intval = 0; + return 0; +} + +static int ucsi_psy_get_usb_type(struct ucsi_connector *con, + union power_supply_propval *val) +{ + u16 flags = con->status.flags; + + val->intval = POWER_SUPPLY_USB_TYPE_C; + if (flags & UCSI_CONSTAT_CONNECTED && + UCSI_CONSTAT_PWR_OPMODE(flags) == UCSI_CONSTAT_PWR_OPMODE_PD) + val->intval = POWER_SUPPLY_USB_TYPE_PD; + + return 0; +} + +static int ucsi_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ucsi_connector *con = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + return ucsi_psy_get_usb_type(con, val); + case POWER_SUPPLY_PROP_ONLINE: + return ucsi_psy_get_online(con, val); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + return ucsi_psy_get_voltage_min(con, val); + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + return ucsi_psy_get_voltage_max(con, val); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return ucsi_psy_get_voltage_now(con, val); + case POWER_SUPPLY_PROP_CURRENT_MAX: + return ucsi_psy_get_current_max(con, val); + case POWER_SUPPLY_PROP_CURRENT_NOW: + return ucsi_psy_get_current_now(con, val); + default: + return -EINVAL; + } +} + +static enum power_supply_usb_type ucsi_psy_usb_types[] = { + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_PPS, +}; + +int ucsi_register_port_psy(struct ucsi_connector *con) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = con->ucsi->dev; + char *psy_name; + + psy_cfg.drv_data = con; + psy_cfg.fwnode = dev_fwnode(dev); + + psy_name = devm_kasprintf(dev, GFP_KERNEL, "ucsi-source-psy-%s%d", + dev_name(dev), con->num); + if (!psy_name) + return -ENOMEM; + + con->psy_desc.name = psy_name; + con->psy_desc.type = POWER_SUPPLY_TYPE_USB, + con->psy_desc.usb_types = ucsi_psy_usb_types; + con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types); + con->psy_desc.properties = ucsi_psy_props, + con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props), + con->psy_desc.get_property = ucsi_psy_get_prop; + + con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg); + + return PTR_ERR_OR_ZERO(con->psy); +} + +void ucsi_unregister_port_psy(struct ucsi_connector *con) +{ + if (IS_ERR_OR_NULL(con->psy)) + return; + + power_supply_unregister(con->psy); +} diff --git a/drivers/usb/typec/ucsi/trace.c b/drivers/usb/typec/ucsi/trace.c index 48ad1dc1b1b2..cb62ad835761 100644 --- a/drivers/usb/typec/ucsi/trace.c +++ b/drivers/usb/typec/ucsi/trace.c @@ -35,16 +35,16 @@ const char *ucsi_cmd_str(u64 raw_cmd) const char *ucsi_cci_str(u32 cci) { - if (cci & GENMASK(7, 0)) { - if (cci & BIT(29)) + if (UCSI_CCI_CONNECTOR(cci)) { + if (cci & UCSI_CCI_ACK_COMPLETE) return "Event pending (ACK completed)"; - if (cci & BIT(31)) + if (cci & UCSI_CCI_COMMAND_COMPLETE) return "Event pending (command completed)"; return "Connector Change"; } - if (cci & BIT(29)) + if (cci & UCSI_CCI_ACK_COMPLETE) return "ACK completed"; - if (cci & BIT(31)) + if (cci & UCSI_CCI_COMMAND_COMPLETE) return "Command completed"; return ""; diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index ddf2ad3752de..d0c63afaf345 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -492,19 +492,45 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient) } } +static void ucsi_get_pdos(struct ucsi_connector *con, int is_partner) +{ + struct ucsi *ucsi = con->ucsi; + u64 command; + int ret; + + command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num); + command |= UCSI_GET_PDOS_PARTNER_PDO(is_partner); + command |= UCSI_GET_PDOS_NUM_PDOS(UCSI_MAX_PDOS - 1); + command |= UCSI_GET_PDOS_SRC_PDOS; + ret = ucsi_run_command(ucsi, command, con->src_pdos, + sizeof(con->src_pdos)); + if (ret < 0) { + dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret); + return; + } + con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */ + if (ret == 0) + dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n"); +} + static void ucsi_pwr_opmode_change(struct ucsi_connector *con) { switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { case UCSI_CONSTAT_PWR_OPMODE_PD: + con->rdo = con->status.request_data_obj; typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD); + ucsi_get_pdos(con, 1); break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + con->rdo = 0; typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_1_5A); break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + con->rdo = 0; typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_3_0A); break; default: + con->rdo = 0; typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_USB); break; } @@ -566,6 +592,8 @@ static void ucsi_partner_change(struct ucsi_connector *con) switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { case UCSI_CONSTAT_PARTNER_TYPE_UFP: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: typec_set_data_role(con->port, TYPEC_HOST); break; case UCSI_CONSTAT_PARTNER_TYPE_DFP: @@ -611,7 +639,8 @@ static void ucsi_handle_connector_change(struct work_struct *work) role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR); - if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE) + if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE || + con->status.change & UCSI_CONSTAT_POWER_LEVEL_CHANGE) ucsi_pwr_opmode_change(con); if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) { @@ -627,6 +656,8 @@ static void ucsi_handle_connector_change(struct work_struct *work) switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { case UCSI_CONSTAT_PARTNER_TYPE_UFP: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: typec_set_data_role(con->port, TYPEC_HOST); break; case UCSI_CONSTAT_PARTNER_TYPE_DFP: @@ -905,6 +936,10 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) cap->driver_data = con; cap->ops = &ucsi_ops; + ret = ucsi_register_port_psy(con); + if (ret) + return ret; + /* Register the connector */ con->port = typec_register_port(ucsi->dev, cap); if (IS_ERR(con->port)) @@ -927,6 +962,8 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { case UCSI_CONSTAT_PARTNER_TYPE_UFP: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE: + case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: typec_set_data_role(con->port, TYPEC_HOST); break; case UCSI_CONSTAT_PARTNER_TYPE_DFP: @@ -1029,6 +1066,7 @@ err_unregister: for (con = ucsi->connector; con->port; con++) { ucsi_unregister_partner(con); ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); + ucsi_unregister_port_psy(con); typec_unregister_port(con->port); con->port = NULL; } @@ -1152,6 +1190,7 @@ void ucsi_unregister(struct ucsi *ucsi) ucsi_unregister_partner(&ucsi->connector[i]); ucsi_unregister_altmodes(&ucsi->connector[i], UCSI_RECIPIENT_CON); + ucsi_unregister_port_psy(&ucsi->connector[i]); typec_unregister_port(ucsi->connector[i].port); } diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 8e831108f481..cba6f77bea61 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -5,6 +5,7 @@ #include <linux/bitops.h> #include <linux/device.h> +#include <linux/power_supply.h> #include <linux/types.h> #include <linux/usb/typec.h> @@ -21,7 +22,7 @@ struct ucsi_altmode; #define UCSI_MESSAGE_OUT 32 /* Command Status and Connector Change Indication (CCI) bits */ -#define UCSI_CCI_CONNECTOR(_c_) (((_c_) & GENMASK(7, 0)) >> 1) +#define UCSI_CCI_CONNECTOR(_c_) (((_c_) & GENMASK(7, 1)) >> 1) #define UCSI_CCI_LENGTH(_c_) (((_c_) & GENMASK(15, 8)) >> 8) #define UCSI_CCI_NOT_SUPPORTED BIT(25) #define UCSI_CCI_CANCEL_COMPLETE BIT(26) @@ -130,6 +131,11 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_GET_ALTMODE_OFFSET(_r_) ((u64)(_r_) << 32) #define UCSI_GET_ALTMODE_NUM_ALTMODES(_r_) ((u64)(_r_) << 40) +/* GET_PDOS command bits */ +#define UCSI_GET_PDOS_PARTNER_PDO(_r_) ((u64)(_r_) << 23) +#define UCSI_GET_PDOS_NUM_PDOS(_r_) ((u64)(_r_) << 32) +#define UCSI_GET_PDOS_SRC_PDOS ((u64)1 << 34) + /* -------------------------------------------------------------------------- */ /* Error information returned by PPM in response to GET_ERROR_STATUS command. */ @@ -294,6 +300,11 @@ struct ucsi { #define UCSI_MAX_SVID 5 #define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6) +#define UCSI_MAX_PDOS (4) + +#define UCSI_TYPEC_VSAFE5V 5000 +#define UCSI_TYPEC_1_5_CURRENT 1500 +#define UCSI_TYPEC_3_0_CURRENT 3000 struct ucsi_connector { int num; @@ -313,6 +324,11 @@ struct ucsi_connector { struct ucsi_connector_status status; struct ucsi_connector_capability cap; + struct power_supply *psy; + struct power_supply_desc psy_desc; + u32 rdo; + u32 src_pdos[UCSI_MAX_PDOS]; + int num_pdos; }; int ucsi_send_command(struct ucsi *ucsi, u64 command, @@ -321,6 +337,14 @@ int ucsi_send_command(struct ucsi *ucsi, u64 command, void ucsi_altmode_update_active(struct ucsi_connector *con); int ucsi_resume(struct ucsi *ucsi); +#if IS_ENABLED(CONFIG_POWER_SUPPLY) +int ucsi_register_port_psy(struct ucsi_connector *con); +void ucsi_unregister_port_psy(struct ucsi_connector *con); +#else +static inline int ucsi_register_port_psy(struct ucsi_connector *con) { return 0; } +static inline void ucsi_unregister_port_psy(struct ucsi_connector *con) { } +#endif /* CONFIG_POWER_SUPPLY */ + #if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) struct typec_altmode * ucsi_register_displayport(struct ucsi_connector *con, |