diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/usb/typec/Kconfig | 18 | ||||
-rw-r--r-- | drivers/usb/typec/Makefile | 5 | ||||
-rw-r--r-- | drivers/usb/typec/altmodes/Kconfig | 14 | ||||
-rw-r--r-- | drivers/usb/typec/altmodes/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/typec/altmodes/displayport.c | 580 | ||||
-rw-r--r-- | drivers/usb/typec/bus.c | 402 | ||||
-rw-r--r-- | drivers/usb/typec/bus.h | 38 | ||||
-rw-r--r-- | drivers/usb/typec/class.c | 544 | ||||
-rw-r--r-- | drivers/usb/typec/fusb302/fusb302.c | 12 | ||||
-rw-r--r-- | drivers/usb/typec/mux.c | 6 | ||||
-rw-r--r-- | drivers/usb/typec/mux/pi3usb30532.c | 13 | ||||
-rw-r--r-- | drivers/usb/typec/tcpci.c (renamed from drivers/staging/typec/tcpci.c) | 66 | ||||
-rw-r--r-- | drivers/usb/typec/tcpci.h (renamed from drivers/staging/typec/tcpci.h) | 0 | ||||
-rw-r--r-- | drivers/usb/typec/tcpci_rt1711h.c (renamed from drivers/staging/typec/tcpci_rt1711h.c) | 0 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm.c | 347 | ||||
-rw-r--r-- | drivers/usb/typec/tps6598x.c | 11 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 13 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi_acpi.c | 5 |
18 files changed, 1787 insertions, 289 deletions
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 2c8eab11a493..00878c386dd0 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -56,6 +56,22 @@ config TYPEC_TCPM if TYPEC_TCPM +config TYPEC_TCPCI + tristate "Type-C Port Controller Interface driver" + depends on I2C + select REGMAP_I2C + help + Type-C Port Controller driver for TCPCI-compliant controller. + +config TYPEC_RT1711H + tristate "Richtek RT1711H Type-C chip driver" + depends on I2C + select TYPEC_TCPCI + help + Richtek RT1711H Type-C chip driver that works with + Type-C Port Controller Manager to provide USB PD and USB + Type-C functionalities. + source "drivers/usb/typec/fusb302/Kconfig" config TYPEC_WCOVE @@ -88,4 +104,6 @@ config TYPEC_TPS6598X source "drivers/usb/typec/mux/Kconfig" +source "drivers/usb/typec/altmodes/Kconfig" + endif # TYPEC diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 1f599a6c30cc..45b0aef428a8 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,9 +1,12 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC) += typec.o -typec-y := class.o mux.o +typec-y := class.o mux.o bus.o +obj-$(CONFIG_TYPEC) += altmodes/ obj-$(CONFIG_TYPEC_TCPM) += tcpm.o obj-y += fusb302/ obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o obj-$(CONFIG_TYPEC_UCSI) += ucsi/ obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o obj-$(CONFIG_TYPEC) += mux/ +obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o +obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig new file mode 100644 index 000000000000..efef2a64bc51 --- /dev/null +++ b/drivers/usb/typec/altmodes/Kconfig @@ -0,0 +1,14 @@ + +menu "USB Type-C Alternate Mode drivers" + +config TYPEC_DP_ALTMODE + tristate "DisplayPort Alternate Mode driver" + help + DisplayPort USB Type-C Alternate Mode allows DisplayPort + displays and adapters to be attached to the USB Type-C + connectors on the system. + + To compile this driver as a module, choose M here: the + module will be called typec_displayport. + +endmenu diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile new file mode 100644 index 000000000000..5caf094ef71a --- /dev/null +++ b/drivers/usb/typec/altmodes/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o +typec_displayport-y := displayport.o diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c new file mode 100644 index 000000000000..3f06e94771a7 --- /dev/null +++ b/drivers/usb/typec/altmodes/displayport.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * USB Typec-C DisplayPort Alternate Mode driver + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + * + * DisplayPort is trademark of VESA (www.vesa.org) + */ + +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/usb/pd_vdo.h> +#include <linux/usb/typec_dp.h> + +#define DP_HEADER(cmd) (VDO(USB_TYPEC_DP_SID, 1, cmd) | \ + VDO_OPOS(USB_TYPEC_DP_MODE)) + +enum { + DP_CONF_USB, + DP_CONF_DFP_D, + DP_CONF_UFP_D, + DP_CONF_DUAL_D, +}; + +/* Helper for setting/getting the pin assignement value to the configuration */ +#define DP_CONF_SET_PIN_ASSIGN(_a_) ((_a_) << 8) +#define DP_CONF_GET_PIN_ASSIGN(_conf_) (((_conf_) & GENMASK(15, 8)) >> 8) + +/* Pin assignments that use USB3.1 Gen2 signaling to carry DP protocol */ +#define DP_PIN_ASSIGN_GEN2_BR_MASK (BIT(DP_PIN_ASSIGN_A) | \ + BIT(DP_PIN_ASSIGN_B)) + +/* Pin assignments that use DP v1.3 signaling to carry DP protocol */ +#define DP_PIN_ASSIGN_DP_BR_MASK (BIT(DP_PIN_ASSIGN_C) | \ + BIT(DP_PIN_ASSIGN_D) | \ + BIT(DP_PIN_ASSIGN_E) | \ + BIT(DP_PIN_ASSIGN_F)) + +/* DP only pin assignments */ +#define DP_PIN_ASSIGN_DP_ONLY_MASK (BIT(DP_PIN_ASSIGN_A) | \ + BIT(DP_PIN_ASSIGN_C) | \ + BIT(DP_PIN_ASSIGN_E)) + +/* Pin assignments where one channel is for USB */ +#define DP_PIN_ASSIGN_MULTI_FUNC_MASK (BIT(DP_PIN_ASSIGN_B) | \ + BIT(DP_PIN_ASSIGN_D) | \ + BIT(DP_PIN_ASSIGN_F)) + +enum dp_state { + DP_STATE_IDLE, + DP_STATE_ENTER, + DP_STATE_UPDATE, + DP_STATE_CONFIGURE, + DP_STATE_EXIT, +}; + +struct dp_altmode { + struct typec_displayport_data data; + + enum dp_state state; + + struct mutex lock; /* device lock */ + struct work_struct work; + struct typec_altmode *alt; + const struct typec_altmode *port; +}; + +static int dp_altmode_notify(struct dp_altmode *dp) +{ + u8 state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf)); + + return typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state), + &dp->data); +} + +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; + + switch (con) { + case DP_STATUS_CON_DISABLED: + return 0; + case DP_STATUS_CON_DFP_D: + conf |= DP_CONF_UFP_U_AS_DFP_D; + pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) & + DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo); + break; + case DP_STATUS_CON_UFP_D: + case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */ + conf |= DP_CONF_UFP_U_AS_UFP_D; + pin_assign = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo) & + DP_CAP_UFP_D_PIN_ASSIGN(dp->port->vdo); + break; + default: + break; + } + + /* Determining the initial pin assignment. */ + if (!DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) { + /* Is USB together with DP preferred */ + if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC && + pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK) + pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK; + else + pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK; + + if (!pin_assign) + return -EINVAL; + + conf |= DP_CONF_SET_PIN_ASSIGN(pin_assign); + } + + dp->data.conf = conf; + + return 0; +} + +static int dp_altmode_status_update(struct dp_altmode *dp) +{ + bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf); + u8 con = DP_STATUS_CONNECTION(dp->data.status); + int ret = 0; + + if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) { + dp->data.conf = 0; + dp->state = DP_STATE_CONFIGURE; + } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) { + dp->state = DP_STATE_EXIT; + } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) { + ret = dp_altmode_configure(dp, con); + if (!ret) + dp->state = DP_STATE_CONFIGURE; + } + + return ret; +} + +static int dp_altmode_configured(struct dp_altmode *dp) +{ + int ret; + + sysfs_notify(&dp->alt->dev.kobj, "displayport", "configuration"); + + if (!dp->data.conf) + return typec_altmode_notify(dp->alt, TYPEC_STATE_USB, + &dp->data); + + ret = dp_altmode_notify(dp); + if (ret) + return ret; + + sysfs_notify(&dp->alt->dev.kobj, "displayport", "pin_assignment"); + + return 0; +} + +static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf) +{ + u32 header = DP_HEADER(DP_CMD_CONFIGURE); + int ret; + + ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE, &dp->data); + if (ret) { + dev_err(&dp->alt->dev, + "unable to put to connector to safe mode\n"); + return ret; + } + + ret = typec_altmode_vdm(dp->alt, header, &conf, 2); + if (ret) { + if (DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) + dp_altmode_notify(dp); + else + typec_altmode_notify(dp->alt, TYPEC_STATE_USB, + &dp->data); + } + + return ret; +} + +static void dp_altmode_work(struct work_struct *work) +{ + struct dp_altmode *dp = container_of(work, struct dp_altmode, work); + u32 header; + u32 vdo; + int ret; + + mutex_lock(&dp->lock); + + switch (dp->state) { + case DP_STATE_ENTER: + ret = typec_altmode_enter(dp->alt); + if (ret) + dev_err(&dp->alt->dev, "failed to enter mode\n"); + break; + case DP_STATE_UPDATE: + header = DP_HEADER(DP_CMD_STATUS_UPDATE); + vdo = 1; + ret = typec_altmode_vdm(dp->alt, header, &vdo, 2); + if (ret) + dev_err(&dp->alt->dev, + "unable to send Status Update command (%d)\n", + ret); + break; + case DP_STATE_CONFIGURE: + ret = dp_altmode_configure_vdm(dp, dp->data.conf); + if (ret) + dev_err(&dp->alt->dev, + "unable to send Configure command (%d)\n", ret); + break; + case DP_STATE_EXIT: + if (typec_altmode_exit(dp->alt)) + dev_err(&dp->alt->dev, "Exit Mode Failed!\n"); + break; + default: + break; + } + + dp->state = DP_STATE_IDLE; + + mutex_unlock(&dp->lock); +} + +static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + u8 old_state; + + mutex_lock(&dp->lock); + + old_state = dp->state; + dp->data.status = vdo; + + if (old_state != DP_STATE_IDLE) + dev_warn(&alt->dev, "ATTENTION while processing state %d\n", + old_state); + + if (dp_altmode_status_update(dp)) + dev_warn(&alt->dev, "%s: status update failed\n", __func__); + + if (dp_altmode_notify(dp)) + dev_err(&alt->dev, "%s: notification failed\n", __func__); + + if (old_state == DP_STATE_IDLE && dp->state != DP_STATE_IDLE) + schedule_work(&dp->work); + + mutex_unlock(&dp->lock); +} + +static int dp_altmode_vdm(struct typec_altmode *alt, + const u32 hdr, const u32 *vdo, int count) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + int cmd_type = PD_VDO_CMDT(hdr); + int cmd = PD_VDO_CMD(hdr); + int ret = 0; + + mutex_lock(&dp->lock); + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto err_unlock; + } + + switch (cmd_type) { + case CMDT_RSP_ACK: + switch (cmd) { + case CMD_ENTER_MODE: + dp->state = DP_STATE_UPDATE; + break; + case CMD_EXIT_MODE: + dp->data.status = 0; + dp->data.conf = 0; + break; + case DP_CMD_STATUS_UPDATE: + dp->data.status = *vdo; + ret = dp_altmode_status_update(dp); + break; + case DP_CMD_CONFIGURE: + ret = dp_altmode_configured(dp); + break; + default: + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case DP_CMD_CONFIGURE: + dp->data.conf = 0; + ret = dp_altmode_configured(dp); + break; + default: + break; + } + break; + default: + break; + } + + if (dp->state != DP_STATE_IDLE) + schedule_work(&dp->work); + +err_unlock: + mutex_unlock(&dp->lock); + return ret; +} + +static int dp_altmode_activate(struct typec_altmode *alt, int activate) +{ + return activate ? typec_altmode_enter(alt) : typec_altmode_exit(alt); +} + +static const struct typec_altmode_ops dp_altmode_ops = { + .attention = dp_altmode_attention, + .vdm = dp_altmode_vdm, + .activate = dp_altmode_activate, +}; + +static const char * const configurations[] = { + [DP_CONF_USB] = "USB", + [DP_CONF_DFP_D] = "source", + [DP_CONF_UFP_D] = "sink", +}; + +static ssize_t +configuration_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + u32 conf; + u32 cap; + int con; + int ret = 0; + + con = sysfs_match_string(configurations, buf); + if (con < 0) + return con; + + mutex_lock(&dp->lock); + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto err_unlock; + } + + cap = DP_CAP_CAPABILITY(dp->alt->vdo); + + if ((con == DP_CONF_DFP_D && !(cap & DP_CAP_DFP_D)) || + (con == DP_CONF_UFP_D && !(cap & DP_CAP_UFP_D))) { + ret = -EINVAL; + goto err_unlock; + } + + conf = dp->data.conf & ~DP_CONF_DUAL_D; + conf |= con; + + if (dp->alt->active) { + ret = dp_altmode_configure_vdm(dp, conf); + if (ret) + goto err_unlock; + } + + dp->data.conf = conf; + +err_unlock: + mutex_unlock(&dp->lock); + + return ret ? ret : size; +} + +static ssize_t configuration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + int len; + u8 cap; + u8 cur; + int i; + + mutex_lock(&dp->lock); + + cap = DP_CAP_CAPABILITY(dp->alt->vdo); + cur = DP_CONF_CURRENTLY(dp->data.conf); + + len = sprintf(buf, "%s ", cur ? "USB" : "[USB]"); + + for (i = 1; i < ARRAY_SIZE(configurations); i++) { + if (i == cur) + len += sprintf(buf + len, "[%s] ", configurations[i]); + else if ((i == DP_CONF_DFP_D && cap & DP_CAP_DFP_D) || + (i == DP_CONF_UFP_D && cap & DP_CAP_UFP_D)) + len += sprintf(buf + len, "%s ", configurations[i]); + } + + mutex_unlock(&dp->lock); + + buf[len - 1] = '\n'; + return len; +} +static DEVICE_ATTR_RW(configuration); + +static const char * const pin_assignments[] = { + [DP_PIN_ASSIGN_A] = "A", + [DP_PIN_ASSIGN_B] = "B", + [DP_PIN_ASSIGN_C] = "C", + [DP_PIN_ASSIGN_D] = "D", + [DP_PIN_ASSIGN_E] = "E", + [DP_PIN_ASSIGN_F] = "F", +}; + +static ssize_t +pin_assignment_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + u8 assignments; + u32 conf; + int ret; + + ret = sysfs_match_string(pin_assignments, buf); + if (ret < 0) + return ret; + + conf = DP_CONF_SET_PIN_ASSIGN(BIT(ret)); + ret = 0; + + mutex_lock(&dp->lock); + + if (conf & dp->data.conf) + goto out_unlock; + + if (dp->state != DP_STATE_IDLE) { + ret = -EBUSY; + goto out_unlock; + } + + if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D) + assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo); + else + assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo); + + if (!(DP_CONF_GET_PIN_ASSIGN(conf) & assignments)) { + ret = -EINVAL; + goto out_unlock; + } + + conf |= dp->data.conf & ~DP_CONF_PIN_ASSIGNEMENT_MASK; + + /* Only send Configure command if a configuration has been set */ + if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) { + ret = dp_altmode_configure_vdm(dp, conf); + if (ret) + goto out_unlock; + } + + dp->data.conf = conf; + +out_unlock: + mutex_unlock(&dp->lock); + + return ret ? ret : size; +} + +static ssize_t pin_assignment_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dp_altmode *dp = dev_get_drvdata(dev); + u8 assignments; + int len = 0; + u8 cur; + int i; + + mutex_lock(&dp->lock); + + cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf)); + + if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D) + assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo); + else + assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo); + + for (i = 0; assignments; assignments >>= 1, i++) { + if (assignments & 1) { + if (i == cur) + len += sprintf(buf + len, "[%s] ", + pin_assignments[i]); + else + len += sprintf(buf + len, "%s ", + pin_assignments[i]); + } + } + + mutex_unlock(&dp->lock); + + buf[len - 1] = '\n'; + return len; +} +static DEVICE_ATTR_RW(pin_assignment); + +static struct attribute *dp_altmode_attrs[] = { + &dev_attr_configuration.attr, + &dev_attr_pin_assignment.attr, + NULL +}; + +static const struct attribute_group dp_altmode_group = { + .name = "displayport", + .attrs = dp_altmode_attrs, +}; + +static int dp_altmode_probe(struct typec_altmode *alt) +{ + const struct typec_altmode *port = typec_altmode_get_partner(alt); + struct dp_altmode *dp; + int ret; + + /* FIXME: Port can only be DFP_U. */ + + /* Make sure we have compatiple pin configurations */ + if (!(DP_CAP_DFP_D_PIN_ASSIGN(port->vdo) & + DP_CAP_UFP_D_PIN_ASSIGN(alt->vdo)) && + !(DP_CAP_UFP_D_PIN_ASSIGN(port->vdo) & + DP_CAP_DFP_D_PIN_ASSIGN(alt->vdo))) + return -ENODEV; + + ret = sysfs_create_group(&alt->dev.kobj, &dp_altmode_group); + if (ret) + return ret; + + dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + INIT_WORK(&dp->work, dp_altmode_work); + mutex_init(&dp->lock); + dp->port = port; + dp->alt = alt; + + alt->desc = "DisplayPort"; + alt->ops = &dp_altmode_ops; + + typec_altmode_set_drvdata(alt, dp); + + dp->state = DP_STATE_ENTER; + schedule_work(&dp->work); + + return 0; +} + +static void dp_altmode_remove(struct typec_altmode *alt) +{ + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + + sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group); + cancel_work_sync(&dp->work); +} + +static const struct typec_device_id dp_typec_id[] = { + { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE }, + { }, +}; +MODULE_DEVICE_TABLE(typec, dp_typec_id); + +static struct typec_altmode_driver dp_altmode_driver = { + .id_table = dp_typec_id, + .probe = dp_altmode_probe, + .remove = dp_altmode_remove, + .driver = { + .name = "typec_displayport", + .owner = THIS_MODULE, + }, +}; +module_typec_altmode_driver(dp_altmode_driver); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DisplayPort Alternate Mode"); diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c new file mode 100644 index 000000000000..95a2b10127db --- /dev/null +++ b/drivers/usb/typec/bus.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * Bus for USB Type-C Alternate Modes + * + * Copyright (C) 2018 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/usb/pd_vdo.h> + +#include "bus.h" + +static inline int typec_altmode_set_mux(struct altmode *alt, u8 state) +{ + return alt->mux ? alt->mux->set(alt->mux, state) : 0; +} + +static int typec_altmode_set_state(struct typec_altmode *adev, int state) +{ + bool is_port = is_typec_port(adev->dev.parent); + struct altmode *port_altmode; + int ret; + + port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner; + + ret = typec_altmode_set_mux(port_altmode, state); + if (ret) + return ret; + + blocking_notifier_call_chain(&port_altmode->nh, state, NULL); + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* Common API */ + +/** + * typec_altmode_notify - Communication between the OS and alternate mode driver + * @adev: Handle to the alternate mode + * @conf: Alternate mode specific configuration value + * @data: Alternate mode specific data + * + * The primary purpose for this function is to allow the alternate mode drivers + * to tell which pin configuration has been negotiated with the partner. That + * information will then be used for example to configure the muxes. + * Communication to the other direction is also possible, and low level device + * drivers can also send notifications to the alternate mode drivers. The actual + * communication will be specific for every SVID. + */ +int typec_altmode_notify(struct typec_altmode *adev, + unsigned long conf, void *data) +{ + bool is_port; + struct altmode *altmode; + struct altmode *partner; + int ret; + + if (!adev) + return 0; + + altmode = to_altmode(adev); + + if (!altmode->partner) + return -ENODEV; + + is_port = is_typec_port(adev->dev.parent); + partner = altmode->partner; + + ret = typec_altmode_set_mux(is_port ? altmode : partner, (u8)conf); + if (ret) + return ret; + + blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh, + conf, data); + + if (partner->adev.ops && partner->adev.ops->notify) + return partner->adev.ops->notify(&partner->adev, conf, data); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_altmode_notify); + +/** + * typec_altmode_enter - Enter Mode + * @adev: The alternate mode + * + * The alternate mode drivers use this function to enter mode. The port drivers + * use this to inform the alternate mode drivers that the partner has initiated + * Enter Mode command. + */ +int typec_altmode_enter(struct typec_altmode *adev) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev = &partner->adev; + int ret; + + if (!adev || adev->active) + return 0; + + if (!pdev->ops || !pdev->ops->enter) + return -EOPNOTSUPP; + + /* Moving to USB Safe State */ + ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE); + if (ret) + return ret; + + /* Enter Mode */ + return pdev->ops->enter(pdev); +} +EXPORT_SYMBOL_GPL(typec_altmode_enter); + +/** + * typec_altmode_exit - Exit Mode + * @adev: The alternate mode + * + * The partner of @adev has initiated Exit Mode command. + */ +int typec_altmode_exit(struct typec_altmode *adev) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev = &partner->adev; + int ret; + + if (!adev || !adev->active) + return 0; + + if (!pdev->ops || !pdev->ops->enter) + return -EOPNOTSUPP; + + /* Moving to USB Safe State */ + ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE); + if (ret) + return ret; + + /* Exit Mode command */ + return pdev->ops->exit(pdev); +} +EXPORT_SYMBOL_GPL(typec_altmode_exit); + +/** + * typec_altmode_attention - Attention command + * @adev: The alternate mode + * @vdo: VDO for the Attention command + * + * Notifies the partner of @adev about Attention command. + */ +void typec_altmode_attention(struct typec_altmode *adev, u32 vdo) +{ + struct typec_altmode *pdev = &to_altmode(adev)->partner->adev; + + if (pdev->ops && pdev->ops->attention) + pdev->ops->attention(pdev, vdo); +} +EXPORT_SYMBOL_GPL(typec_altmode_attention); + +/** + * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner + * @adev: Alternate mode handle + * @header: VDM Header + * @vdo: Array of Vendor Defined Data Objects + * @count: Number of Data Objects + * + * The alternate mode drivers use this function for SVID specific communication + * with the partner. The port drivers use it to deliver the Structured VDMs + * received from the partners to the alternate mode drivers. + */ +int typec_altmode_vdm(struct typec_altmode *adev, + const u32 header, const u32 *vdo, int count) +{ + struct typec_altmode *pdev; + struct altmode *altmode; + + if (!adev) + return 0; + + altmode = to_altmode(adev); + + if (!altmode->partner) + return -ENODEV; + + pdev = &altmode->partner->adev; + + if (!pdev->ops || !pdev->ops->vdm) + return -EOPNOTSUPP; + + return pdev->ops->vdm(pdev, header, vdo, count); +} +EXPORT_SYMBOL_GPL(typec_altmode_vdm); + +const struct typec_altmode * +typec_altmode_get_partner(struct typec_altmode *adev) +{ + return &to_altmode(adev)->partner->adev; +} +EXPORT_SYMBOL_GPL(typec_altmode_get_partner); + +/* -------------------------------------------------------------------------- */ +/* API for the alternate mode drivers */ + +/** + * typec_altmode_get_plug - Find cable plug alternate mode + * @adev: Handle to partner alternate mode + * @index: Cable plug index + * + * Increment reference count for cable plug alternate mode device. Returns + * handle to the cable plug alternate mode, or NULL if none is found. + */ +struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev, + enum typec_plug_index index) +{ + struct altmode *port = to_altmode(adev)->partner; + + if (port->plug[index]) { + get_device(&port->plug[index]->adev.dev); + return &port->plug[index]->adev; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_altmode_get_plug); + +/** + * typec_altmode_put_plug - Decrement cable plug alternate mode reference count + * @plug: Handle to the cable plug alternate mode + */ +void typec_altmode_put_plug(struct typec_altmode *plug) +{ + if (plug) + put_device(&plug->dev); +} +EXPORT_SYMBOL_GPL(typec_altmode_put_plug); + +int __typec_altmode_register_driver(struct typec_altmode_driver *drv, + struct module *module) +{ + if (!drv->probe) + return -EINVAL; + + drv->driver.owner = module; + drv->driver.bus = &typec_bus; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__typec_altmode_register_driver); + +void typec_altmode_unregister_driver(struct typec_altmode_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver); + +/* -------------------------------------------------------------------------- */ +/* API for the port drivers */ + +/** + * typec_match_altmode - Match SVID to an array of alternate modes + * @altmodes: Array of alternate modes + * @n: Number of elements in the array, or -1 for NULL termiated arrays + * @svid: Standard or Vendor ID to match with + * + * Return pointer to an alternate mode with SVID mathing @svid, or NULL when no + * match is found. + */ +struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes, + size_t n, u16 svid, u8 mode) +{ + int i; + + for (i = 0; i < n; i++) { + if (!altmodes[i]) + break; + if (altmodes[i]->svid == svid && altmodes[i]->mode == mode) + return altmodes[i]; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_match_altmode); + +/* -------------------------------------------------------------------------- */ + +static ssize_t +description_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_altmode *alt = to_typec_altmode(dev); + + return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); +} +static DEVICE_ATTR_RO(description); + +static struct attribute *typec_attrs[] = { + &dev_attr_description.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec); + +static int typec_match(struct device *dev, struct device_driver *driver) +{ + struct typec_altmode_driver *drv = to_altmode_driver(driver); + struct typec_altmode *altmode = to_typec_altmode(dev); + const struct typec_device_id *id; + + for (id = drv->id_table; id->svid; id++) + if (id->svid == altmode->svid && + (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode)) + return 1; + return 0; +} + +static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct typec_altmode *altmode = to_typec_altmode(dev); + + if (add_uevent_var(env, "SVID=%04X", altmode->svid)) + return -ENOMEM; + + if (add_uevent_var(env, "MODE=%u", altmode->mode)) + return -ENOMEM; + + return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X", + altmode->svid, altmode->mode); +} + +static int typec_altmode_create_links(struct altmode *alt) +{ + struct device *port_dev = &alt->partner->adev.dev; + struct device *dev = &alt->adev.dev; + int err; + + err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port"); + if (err) + return err; + + err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner"); + if (err) + sysfs_remove_link(&dev->kobj, "port"); + + return err; +} + +static void typec_altmode_remove_links(struct altmode *alt) +{ + sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner"); + sysfs_remove_link(&alt->adev.dev.kobj, "port"); +} + +static int typec_probe(struct device *dev) +{ + struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); + struct typec_altmode *adev = to_typec_altmode(dev); + struct altmode *altmode = to_altmode(adev); + int ret; + + /* Fail if the port does not support the alternate mode */ + if (!altmode->partner) + return -ENODEV; + + ret = typec_altmode_create_links(altmode); + if (ret) { + dev_warn(dev, "failed to create symlinks\n"); + return ret; + } + + ret = drv->probe(adev); + if (ret) + typec_altmode_remove_links(altmode); + + return ret; +} + +static int typec_remove(struct device *dev) +{ + struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); + struct typec_altmode *adev = to_typec_altmode(dev); + struct altmode *altmode = to_altmode(adev); + + typec_altmode_remove_links(altmode); + + if (drv->remove) + drv->remove(to_typec_altmode(dev)); + + if (adev->active) { + WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE)); + typec_altmode_update_active(adev, false); + } + + adev->desc = NULL; + adev->ops = NULL; + + return 0; +} + +struct bus_type typec_bus = { + .name = "typec", + .dev_groups = typec_groups, + .match = typec_match, + .uevent = typec_uevent, + .probe = typec_probe, + .remove = typec_remove, +}; diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h new file mode 100644 index 000000000000..db40e61d8b72 --- /dev/null +++ b/drivers/usb/typec/bus.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __USB_TYPEC_ALTMODE_H__ +#define __USB_TYPEC_ALTMODE_H__ + +#include <linux/usb/typec_altmode.h> +#include <linux/usb/typec_mux.h> + +struct bus_type; + +struct altmode { + unsigned int id; + struct typec_altmode adev; + struct typec_mux *mux; + + enum typec_port_data roles; + + struct attribute *attrs[5]; + char group_name[8]; + struct attribute_group group; + const struct attribute_group *groups[2]; + + struct altmode *partner; + struct altmode *plug[2]; + + struct blocking_notifier_head nh; +}; + +#define to_altmode(d) container_of(d, struct altmode, adev) + +extern struct bus_type typec_bus; +extern const struct device_type typec_altmode_dev_type; +extern const struct device_type typec_port_dev_type; + +#define is_typec_altmode(_dev_) (_dev_->type == &typec_altmode_dev_type) +#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) + +#endif /* __USB_TYPEC_ALTMODE_H__ */ diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 53df10df2f9d..c202975f8097 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -10,39 +10,13 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/slab.h> -#include <linux/usb/typec.h> -#include <linux/usb/typec_mux.h> -struct typec_mode { - int index; - u32 vdo; - char *desc; - enum typec_port_type roles; - - struct typec_altmode *alt_mode; - - unsigned int active:1; - - char group_name[6]; - struct attribute_group group; - struct attribute *attrs[5]; - struct device_attribute vdo_attr; - struct device_attribute desc_attr; - struct device_attribute active_attr; - struct device_attribute roles_attr; -}; - -struct typec_altmode { - struct device dev; - u16 svid; - int n_modes; - struct typec_mode modes[ALTMODE_MAX_MODES]; - const struct attribute_group *mode_groups[ALTMODE_MAX_MODES]; -}; +#include "bus.h" struct typec_plug { struct device dev; enum typec_plug_index index; + struct ida mode_ids; }; struct typec_cable { @@ -57,11 +31,13 @@ struct typec_partner { unsigned int usb_pd:1; struct usb_pd_identity *identity; enum typec_accessory accessory; + struct ida mode_ids; }; struct typec_port { unsigned int id; struct device dev; + struct ida mode_ids; int prefer_role; enum typec_data_role data_role; @@ -82,17 +58,14 @@ struct typec_port { #define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) #define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) #define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) -#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) static const struct device_type typec_partner_dev_type; static const struct device_type typec_cable_dev_type; static const struct device_type typec_plug_dev_type; -static const struct device_type typec_port_dev_type; #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) #define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) #define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) -#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) static DEFINE_IDA(typec_index_ida); static struct class *typec_class; @@ -174,28 +147,148 @@ static void typec_report_identity(struct device *dev) /* ------------------------------------------------------------------------- */ /* Alternate Modes */ +static int altmode_match(struct device *dev, void *data) +{ + struct typec_altmode *adev = to_typec_altmode(dev); + struct typec_device_id *id = data; + + if (!is_typec_altmode(dev)) + return 0; + + return ((adev->svid == id->svid) && (adev->mode == id->mode)); +} + +static void typec_altmode_set_partner(struct altmode *altmode) +{ + struct typec_altmode *adev = &altmode->adev; + struct typec_device_id id = { adev->svid, adev->mode, }; + struct typec_port *port = typec_altmode2port(adev); + struct altmode *partner; + struct device *dev; + + dev = device_find_child(&port->dev, &id, altmode_match); + if (!dev) + return; + + /* Bind the port alt mode to the partner/plug alt mode. */ + partner = to_altmode(to_typec_altmode(dev)); + altmode->partner = partner; + + /* Bind the partner/plug alt mode to the port alt mode. */ + if (is_typec_plug(adev->dev.parent)) { + struct typec_plug *plug = to_typec_plug(adev->dev.parent); + + partner->plug[plug->index] = altmode; + } else { + partner->partner = altmode; + } +} + +static void typec_altmode_put_partner(struct altmode *altmode) +{ + struct altmode *partner = altmode->partner; + struct typec_altmode *adev; + + if (!partner) + return; + + adev = &partner->adev; + + if (is_typec_plug(adev->dev.parent)) { + struct typec_plug *plug = to_typec_plug(adev->dev.parent); + + partner->plug[plug->index] = NULL; + } else { + partner->partner = NULL; + } + put_device(&adev->dev); +} + +static int __typec_port_match(struct device *dev, const void *name) +{ + return !strcmp((const char *)name, dev_name(dev)); +} + +static void *typec_port_match(struct device_connection *con, int ep, void *data) +{ + return class_find_device(typec_class, NULL, con->endpoint[ep], + __typec_port_match); +} + +struct typec_altmode * +typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, + struct notifier_block *nb) +{ + struct typec_device_id id = { svid, mode, }; + struct device *altmode_dev; + struct device *port_dev; + struct altmode *altmode; + int ret; + + /* Find the port linked to the caller */ + port_dev = device_connection_find_match(dev, NULL, NULL, + typec_port_match); + if (IS_ERR_OR_NULL(port_dev)) + return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV); + + /* Find the altmode with matching svid */ + altmode_dev = device_find_child(port_dev, &id, altmode_match); + + put_device(port_dev); + + if (!altmode_dev) + return ERR_PTR(-ENODEV); + + altmode = to_altmode(to_typec_altmode(altmode_dev)); + + /* Register notifier */ + ret = blocking_notifier_chain_register(&altmode->nh, nb); + if (ret) { + put_device(altmode_dev); + return ERR_PTR(ret); + } + + return &altmode->adev; +} +EXPORT_SYMBOL_GPL(typec_altmode_register_notifier); + +void typec_altmode_unregister_notifier(struct typec_altmode *adev, + struct notifier_block *nb) +{ + struct altmode *altmode = to_altmode(adev); + + blocking_notifier_chain_unregister(&altmode->nh, nb); + put_device(&adev->dev); +} +EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier); + /** * typec_altmode_update_active - Report Enter/Exit mode - * @alt: Handle to the alternate mode - * @mode: Mode index + * @adev: Handle to the alternate mode * @active: True when the mode has been entered * * If a partner or cable plug executes Enter/Exit Mode command successfully, the * drivers use this routine to report the updated state of the mode. */ -void typec_altmode_update_active(struct typec_altmode *alt, int mode, - bool active) +void typec_altmode_update_active(struct typec_altmode *adev, bool active) { - struct typec_mode *m = &alt->modes[mode]; char dir[6]; - if (m->active == active) + if (adev->active == active) return; - m->active = active; - snprintf(dir, sizeof(dir), "mode%d", mode); - sysfs_notify(&alt->dev.kobj, dir, "active"); - kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); + if (!is_typec_port(adev->dev.parent)) { + if (!active) + module_put(adev->dev.driver->owner); + else + WARN_ON(!try_module_get(adev->dev.driver->owner)); + } + + adev->active = active; + snprintf(dir, sizeof(dir), "mode%d", adev->mode); + sysfs_notify(&adev->dev.kobj, dir, "active"); + sysfs_notify(&adev->dev.kobj, NULL, "active"); + kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE); } EXPORT_SYMBOL_GPL(typec_altmode_update_active); @@ -220,68 +313,78 @@ struct typec_port *typec_altmode2port(struct typec_altmode *alt) EXPORT_SYMBOL_GPL(typec_altmode2port); static ssize_t -typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr, - char *buf) +vdo_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct typec_mode *mode = container_of(attr, struct typec_mode, - vdo_attr); + struct typec_altmode *alt = to_typec_altmode(dev); - return sprintf(buf, "0x%08x\n", mode->vdo); + return sprintf(buf, "0x%08x\n", alt->vdo); } +static DEVICE_ATTR_RO(vdo); static ssize_t -typec_altmode_desc_show(struct device *dev, struct device_attribute *attr, - char *buf) +description_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct typec_mode *mode = container_of(attr, struct typec_mode, - desc_attr); + struct typec_altmode *alt = to_typec_altmode(dev); - return sprintf(buf, "%s\n", mode->desc ? mode->desc : ""); + return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); } +static DEVICE_ATTR_RO(description); static ssize_t -typec_altmode_active_show(struct device *dev, struct device_attribute *attr, - char *buf) +active_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct typec_mode *mode = container_of(attr, struct typec_mode, - active_attr); + struct typec_altmode *alt = to_typec_altmode(dev); - return sprintf(buf, "%s\n", mode->active ? "yes" : "no"); + return sprintf(buf, "%s\n", alt->active ? "yes" : "no"); } -static ssize_t -typec_altmode_active_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) +static ssize_t active_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) { - struct typec_mode *mode = container_of(attr, struct typec_mode, - active_attr); - struct typec_port *port = typec_altmode2port(mode->alt_mode); - bool activate; + struct typec_altmode *adev = to_typec_altmode(dev); + struct altmode *altmode = to_altmode(adev); + bool enter; int ret; - if (!port->cap->activate_mode) - return -EOPNOTSUPP; - - ret = kstrtobool(buf, &activate); + ret = kstrtobool(buf, &enter); if (ret) return ret; - ret = port->cap->activate_mode(port->cap, mode->index, activate); - if (ret) - return ret; + if (adev->active == enter) + return size; + + if (is_typec_port(adev->dev.parent)) { + typec_altmode_update_active(adev, enter); + + /* Make sure that the partner exits the mode before disabling */ + if (altmode->partner && !enter && altmode->partner->adev.active) + typec_altmode_exit(&altmode->partner->adev); + } else if (altmode->partner) { + if (enter && !altmode->partner->adev.active) { + dev_warn(dev, "port has the mode disabled\n"); + return -EPERM; + } + } + + /* Note: If there is no driver, the mode will not be entered */ + if (adev->ops && adev->ops->activate) { + ret = adev->ops->activate(adev, enter); + if (ret) + return ret; + } return size; } +static DEVICE_ATTR_RW(active); static ssize_t -typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, - char *buf) +supported_roles_show(struct device *dev, struct device_attribute *attr, + char *buf) { - struct typec_mode *mode = container_of(attr, struct typec_mode, - roles_attr); + struct altmode *alt = to_altmode(to_typec_altmode(dev)); ssize_t ret; - switch (mode->roles) { + switch (alt->roles) { case TYPEC_PORT_SRC: ret = sprintf(buf, "source\n"); break; @@ -295,89 +398,74 @@ typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, } return ret; } +static DEVICE_ATTR_RO(supported_roles); -static void typec_init_modes(struct typec_altmode *alt, - const struct typec_mode_desc *desc, bool is_port) +static ssize_t +mode_show(struct device *dev, struct device_attribute *attr, char *buf) { - int i; - - for (i = 0; i < alt->n_modes; i++, desc++) { - struct typec_mode *mode = &alt->modes[i]; - - /* Not considering the human readable description critical */ - mode->desc = kstrdup(desc->desc, GFP_KERNEL); - if (desc->desc && !mode->desc) - dev_err(&alt->dev, "failed to copy mode%d desc\n", i); - - mode->alt_mode = alt; - mode->vdo = desc->vdo; - mode->roles = desc->roles; - mode->index = desc->index; - sprintf(mode->group_name, "mode%d", desc->index); - - sysfs_attr_init(&mode->vdo_attr.attr); - mode->vdo_attr.attr.name = "vdo"; - mode->vdo_attr.attr.mode = 0444; - mode->vdo_attr.show = typec_altmode_vdo_show; - - sysfs_attr_init(&mode->desc_attr.attr); - mode->desc_attr.attr.name = "description"; - mode->desc_attr.attr.mode = 0444; - mode->desc_attr.show = typec_altmode_desc_show; - - sysfs_attr_init(&mode->active_attr.attr); - mode->active_attr.attr.name = "active"; - mode->active_attr.attr.mode = 0644; - mode->active_attr.show = typec_altmode_active_show; - mode->active_attr.store = typec_altmode_active_store; - - mode->attrs[0] = &mode->vdo_attr.attr; - mode->attrs[1] = &mode->desc_attr.attr; - mode->attrs[2] = &mode->active_attr.attr; - - /* With ports, list the roles that the mode is supported with */ - if (is_port) { - sysfs_attr_init(&mode->roles_attr.attr); - mode->roles_attr.attr.name = "supported_roles"; - mode->roles_attr.attr.mode = 0444; - mode->roles_attr.show = typec_altmode_roles_show; - - mode->attrs[3] = &mode->roles_attr.attr; - } + struct typec_altmode *adev = to_typec_altmode(dev); - mode->group.attrs = mode->attrs; - mode->group.name = mode->group_name; - - alt->mode_groups[i] = &mode->group; - } + return sprintf(buf, "%u\n", adev->mode); } +static DEVICE_ATTR_RO(mode); -static ssize_t svid_show(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t +svid_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct typec_altmode *alt = to_altmode(dev); + struct typec_altmode *adev = to_typec_altmode(dev); - return sprintf(buf, "%04x\n", alt->svid); + return sprintf(buf, "%04x\n", adev->svid); } static DEVICE_ATTR_RO(svid); static struct attribute *typec_altmode_attrs[] = { + &dev_attr_active.attr, + &dev_attr_mode.attr, &dev_attr_svid.attr, + &dev_attr_vdo.attr, NULL }; ATTRIBUTE_GROUPS(typec_altmode); +static int altmode_id_get(struct device *dev) +{ + struct ida *ids; + + if (is_typec_partner(dev)) + ids = &to_typec_partner(dev)->mode_ids; + else if (is_typec_plug(dev)) + ids = &to_typec_plug(dev)->mode_ids; + else + ids = &to_typec_port(dev)->mode_ids; + + return ida_simple_get(ids, 0, 0, GFP_KERNEL); +} + +static void altmode_id_remove(struct device *dev, int id) +{ + struct ida *ids; + + if (is_typec_partner(dev)) + ids = &to_typec_partner(dev)->mode_ids; + else if (is_typec_plug(dev)) + ids = &to_typec_plug(dev)->mode_ids; + else + ids = &to_typec_port(dev)->mode_ids; + + ida_simple_remove(ids, id); +} + static void typec_altmode_release(struct device *dev) { - struct typec_altmode *alt = to_altmode(dev); - int i; + struct altmode *alt = to_altmode(to_typec_altmode(dev)); + + typec_altmode_put_partner(alt); - for (i = 0; i < alt->n_modes; i++) - kfree(alt->modes[i].desc); + altmode_id_remove(alt->adev.dev.parent, alt->id); kfree(alt); } -static const struct device_type typec_altmode_dev_type = { +const struct device_type typec_altmode_dev_type = { .name = "typec_alternate_mode", .groups = typec_altmode_groups, .release = typec_altmode_release, @@ -387,44 +475,74 @@ static struct typec_altmode * typec_register_altmode(struct device *parent, const struct typec_altmode_desc *desc) { - struct typec_altmode *alt; + unsigned int id = altmode_id_get(parent); + bool is_port = is_typec_port(parent); + struct altmode *alt; int ret; alt = kzalloc(sizeof(*alt), GFP_KERNEL); if (!alt) return ERR_PTR(-ENOMEM); - alt->svid = desc->svid; - alt->n_modes = desc->n_modes; - typec_init_modes(alt, desc->modes, is_typec_port(parent)); + alt->adev.svid = desc->svid; + alt->adev.mode = desc->mode; + alt->adev.vdo = desc->vdo; + alt->roles = desc->roles; + alt->id = id; + + alt->attrs[0] = &dev_attr_vdo.attr; + alt->attrs[1] = &dev_attr_description.attr; + alt->attrs[2] = &dev_attr_active.attr; + + if (is_port) { + alt->attrs[3] = &dev_attr_supported_roles.attr; + alt->adev.active = true; /* Enabled by default */ + } - alt->dev.parent = parent; - alt->dev.groups = alt->mode_groups; - alt->dev.type = &typec_altmode_dev_type; - dev_set_name(&alt->dev, "svid-%04x", alt->svid); + sprintf(alt->group_name, "mode%d", desc->mode); + alt->group.name = alt->group_name; + alt->group.attrs = alt->attrs; + alt->groups[0] = &alt->group; - ret = device_register(&alt->dev); + alt->adev.dev.parent = parent; + alt->adev.dev.groups = alt->groups; + alt->adev.dev.type = &typec_altmode_dev_type; + dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id); + + /* Link partners and plugs with the ports */ + if (is_port) + BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh); + else + typec_altmode_set_partner(alt); + + /* The partners are bind to drivers */ + if (is_typec_partner(parent)) + alt->adev.dev.bus = &typec_bus; + + ret = device_register(&alt->adev.dev); if (ret) { dev_err(parent, "failed to register alternate mode (%d)\n", ret); - put_device(&alt->dev); + put_device(&alt->adev.dev); return ERR_PTR(ret); } - return alt; + return &alt->adev; } /** * typec_unregister_altmode - Unregister Alternate Mode - * @alt: The alternate mode to be unregistered + * @adev: The alternate mode to be unregistered * * Unregister device created with typec_partner_register_altmode(), * typec_plug_register_altmode() or typec_port_register_altmode(). */ -void typec_unregister_altmode(struct typec_altmode *alt) +void typec_unregister_altmode(struct typec_altmode *adev) { - if (!IS_ERR_OR_NULL(alt)) - device_unregister(&alt->dev); + if (IS_ERR_OR_NULL(adev)) + return; + typec_mux_put(to_altmode(adev)->mux); + device_unregister(&adev->dev); } EXPORT_SYMBOL_GPL(typec_unregister_altmode); @@ -462,6 +580,7 @@ static void typec_partner_release(struct device *dev) { struct typec_partner *partner = to_typec_partner(dev); + ida_destroy(&partner->mode_ids); kfree(partner); } @@ -527,6 +646,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, if (!partner) return ERR_PTR(-ENOMEM); + ida_init(&partner->mode_ids); partner->usb_pd = desc->usb_pd; partner->accessory = desc->accessory; @@ -575,6 +695,7 @@ static void typec_plug_release(struct device *dev) { struct typec_plug *plug = to_typec_plug(dev); + ida_destroy(&plug->mode_ids); kfree(plug); } @@ -627,6 +748,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, sprintf(name, "plug%d", desc->index); + ida_init(&plug->mode_ids); plug->index = desc->index; plug->dev.class = typec_class; plug->dev.parent = &cable->dev; @@ -796,12 +918,18 @@ static const char * const typec_data_roles[] = { [TYPEC_HOST] = "host", }; -static const char * const typec_port_types[] = { +static const char * const typec_port_power_roles[] = { [TYPEC_PORT_SRC] = "source", [TYPEC_PORT_SNK] = "sink", [TYPEC_PORT_DRP] = "dual", }; +static const char * const typec_port_data_roles[] = { + [TYPEC_PORT_DFP] = "host", + [TYPEC_PORT_UFP] = "device", + [TYPEC_PORT_DRD] = "dual", +}; + static const char * const typec_port_types_drp[] = { [TYPEC_PORT_SRC] = "dual [source] sink", [TYPEC_PORT_SNK] = "dual source [sink]", @@ -932,7 +1060,7 @@ static ssize_t power_role_store(struct device *dev, mutex_lock(&port->port_type_lock); if (port->port_type != TYPEC_PORT_DRP) { dev_dbg(dev, "port type fixed at \"%s\"", - typec_port_types[port->port_type]); + typec_port_power_roles[port->port_type]); ret = -EOPNOTSUPP; goto unlock_and_ret; } @@ -973,7 +1101,7 @@ port_type_store(struct device *dev, struct device_attribute *attr, return -EOPNOTSUPP; } - ret = sysfs_match_string(typec_port_types, buf); + ret = sysfs_match_string(typec_port_power_roles, buf); if (ret < 0) return ret; @@ -1007,7 +1135,7 @@ port_type_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%s\n", typec_port_types_drp[port->port_type]); - return sprintf(buf, "[%s]\n", typec_port_types[port->cap->type]); + return sprintf(buf, "[%s]\n", typec_port_power_roles[port->cap->type]); } static DEVICE_ATTR_RW(port_type); @@ -1141,12 +1269,13 @@ static void typec_release(struct device *dev) struct typec_port *port = to_typec_port(dev); ida_simple_remove(&typec_index_ida, port->id); + ida_destroy(&port->mode_ids); typec_switch_put(port->sw); typec_mux_put(port->mux); kfree(port); } -static const struct device_type typec_port_dev_type = { +const struct device_type typec_port_dev_type = { .name = "typec_port", .groups = typec_groups, .uevent = typec_uevent, @@ -1252,6 +1381,50 @@ void typec_set_pwr_opmode(struct typec_port *port, } EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); +/** + * typec_find_port_power_role - Get the typec port power capability + * @name: port power capability string + * + * This routine is used to find the typec_port_type by its string name. + * + * Returns typec_port_type if success, otherwise negative error code. + */ +int typec_find_port_power_role(const char *name) +{ + return match_string(typec_port_power_roles, + ARRAY_SIZE(typec_port_power_roles), name); +} +EXPORT_SYMBOL_GPL(typec_find_port_power_role); + +/** + * typec_find_power_role - Find the typec one specific power role + * @name: power role string + * + * This routine is used to find the typec_role by its string name. + * + * Returns typec_role if success, otherwise negative error code. + */ +int typec_find_power_role(const char *name) +{ + return match_string(typec_roles, ARRAY_SIZE(typec_roles), name); +} +EXPORT_SYMBOL_GPL(typec_find_power_role); + +/** + * typec_find_port_data_role - Get the typec port data capability + * @name: port data capability string + * + * This routine is used to find the typec_port_data by its string name. + * + * Returns typec_port_data if success, otherwise negative error code. + */ +int typec_find_port_data_role(const char *name) +{ + return match_string(typec_port_data_roles, + ARRAY_SIZE(typec_port_data_roles), name); +} +EXPORT_SYMBOL_GPL(typec_find_port_data_role); + /* ------------------------------------------ */ /* API for Multiplexer/DeMultiplexer Switches */ @@ -1280,12 +1453,24 @@ int typec_set_orientation(struct typec_port *port, EXPORT_SYMBOL_GPL(typec_set_orientation); /** + * typec_get_orientation - Get USB Type-C cable plug orientation + * @port: USB Type-C Port + * + * Get current cable plug orientation for @port. + */ +enum typec_orientation typec_get_orientation(struct typec_port *port) +{ + return port->orientation; +} +EXPORT_SYMBOL_GPL(typec_get_orientation); + +/** * typec_set_mode - Set mode of operation for USB Type-C connector - * @port: USB Type-C port for the connector - * @mode: Operation mode for the connector + * @port: USB Type-C connector + * @mode: Accessory Mode, USB Operation or Safe State * - * Set mode @mode for @port. This function will configure the muxes needed to - * enter @mode. + * Configure @port for Accessory Mode @mode. This function will configure the + * muxes needed for @mode. */ int typec_set_mode(struct typec_port *port, int mode) { @@ -1299,6 +1484,7 @@ EXPORT_SYMBOL_GPL(typec_set_mode); * typec_port_register_altmode - Register USB Type-C Port Alternate Mode * @port: USB Type-C Port that supports the alternate mode * @desc: Description of the alternate mode + * @drvdata: Private pointer to driver specific info * * This routine is used to register an alternate mode that @port is capable of * supporting. @@ -1309,7 +1495,23 @@ struct typec_altmode * typec_port_register_altmode(struct typec_port *port, const struct typec_altmode_desc *desc) { - return typec_register_altmode(&port->dev, desc); + struct typec_altmode *adev; + struct typec_mux *mux; + char id[10]; + + sprintf(id, "id%04xm%02x", desc->svid, desc->mode); + + mux = typec_mux_get(port->dev.parent, id); + if (IS_ERR(mux)) + return ERR_CAST(mux); + + adev = typec_register_altmode(&port->dev, desc); + if (IS_ERR(adev)) + typec_mux_put(mux); + else + to_altmode(adev)->mux = mux; + + return adev; } EXPORT_SYMBOL_GPL(typec_port_register_altmode); @@ -1345,7 +1547,7 @@ struct typec_port *typec_register_port(struct device *parent, goto err_switch; } - port->mux = typec_mux_get(cap->fwnode ? &port->dev : parent); + port->mux = typec_mux_get(parent, "typec-mux"); if (IS_ERR(port->mux)) { ret = PTR_ERR(port->mux); goto err_mux; @@ -1383,10 +1585,12 @@ struct typec_port *typec_register_port(struct device *parent, break; } + ida_init(&port->mode_ids); + mutex_init(&port->port_type_lock); + port->id = id; port->cap = cap; port->port_type = cap->type; - mutex_init(&port->port_type_lock); port->prefer_role = cap->prefer_role; port->dev.class = typec_class; @@ -1430,8 +1634,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port); static int __init typec_init(void) { + int ret; + + ret = bus_register(&typec_bus); + if (ret) + return ret; + typec_class = class_create(THIS_MODULE, "typec"); - return PTR_ERR_OR_ZERO(typec_class); + if (IS_ERR(typec_class)) { + bus_unregister(&typec_bus); + return PTR_ERR(typec_class); + } + + return 0; } subsys_initcall(typec_init); @@ -1439,6 +1654,7 @@ static void __exit typec_exit(void) { class_destroy(typec_class); ida_destroy(&typec_index_ida); + bus_unregister(&typec_bus); } module_exit(typec_exit); diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 1e68da10bf17..82bed9810be6 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -864,17 +864,6 @@ done: return ret; } -static int tcpm_set_current_limit(struct tcpc_dev *dev, u32 max_ma, u32 mv) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - - fusb302_log(chip, "current limit: %d ma, %d mv (not implemented)", - max_ma, mv); - - return 0; -} - static int fusb302_pd_tx_flush(struct fusb302_chip *chip) { return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0, @@ -1213,7 +1202,6 @@ static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) fusb302_tcpc_dev->set_polarity = tcpm_set_polarity; fusb302_tcpc_dev->set_vconn = tcpm_set_vconn; fusb302_tcpc_dev->set_vbus = tcpm_set_vbus; - fusb302_tcpc_dev->set_current_limit = tcpm_set_current_limit; fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx; fusb302_tcpc_dev->set_roles = tcpm_set_roles; fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling; diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 9d8330e9c431..ddaac63ecf12 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -123,19 +123,19 @@ static void *typec_mux_match(struct device_connection *con, int ep, void *data) /** * typec_mux_get - Find USB Type-C Multiplexer * @dev: The caller device + * @name: Mux identifier * * Finds a mux linked to the caller. This function is primarily meant for the * Type-C drivers. Returns a reference to the mux on success, NULL if no * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection * was found but the mux has not been enumerated yet. */ -struct typec_mux *typec_mux_get(struct device *dev) +struct typec_mux *typec_mux_get(struct device *dev, const char *name) { struct typec_mux *mux; mutex_lock(&mux_lock); - mux = device_connection_find_match(dev, "typec-mux", NULL, - typec_mux_match); + mux = device_connection_find_match(dev, name, NULL, typec_mux_match); if (!IS_ERR_OR_NULL(mux)) get_device(mux->dev); mutex_unlock(&mux_lock); diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c index b0e88db60ecf..64eb5983e17a 100644 --- a/drivers/usb/typec/mux/pi3usb30532.c +++ b/drivers/usb/typec/mux/pi3usb30532.c @@ -9,7 +9,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> -#include <linux/usb/tcpm.h> +#include <linux/usb/typec_dp.h> #include <linux/usb/typec_mux.h> #define PI3USB30532_CONF 0x00 @@ -83,21 +83,24 @@ static int pi3usb30532_mux_set(struct typec_mux *mux, int state) new_conf = pi->conf; switch (state) { - case TYPEC_MUX_NONE: + case TYPEC_STATE_SAFE: new_conf = PI3USB30532_CONF_OPEN; break; - case TYPEC_MUX_USB: + case TYPEC_STATE_USB: new_conf = (new_conf & PI3USB30532_CONF_SWAP) | PI3USB30532_CONF_USB3; break; - case TYPEC_MUX_DP: + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: new_conf = (new_conf & PI3USB30532_CONF_SWAP) | PI3USB30532_CONF_4LANE_DP; break; - case TYPEC_MUX_DOCK: + case TYPEC_DP_STATE_D: new_conf = (new_conf & PI3USB30532_CONF_SWAP) | PI3USB30532_CONF_USB3_AND_2LANE_DP; break; + default: + break; } ret = pi3usb30532_set_conf(pi, new_conf); diff --git a/drivers/staging/typec/tcpci.c b/drivers/usb/typec/tcpci.c index 076d97eaff6f..ac6b418b15f1 100644 --- a/drivers/staging/typec/tcpci.c +++ b/drivers/usb/typec/tcpci.c @@ -10,6 +10,7 @@ #include <linux/module.h> #include <linux/i2c.h> #include <linux/interrupt.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/usb/pd.h> #include <linux/usb/tcpm.h> @@ -184,15 +185,25 @@ static int tcpci_set_polarity(struct tcpc_dev *tcpc, enum typec_cc_polarity polarity) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; int ret; - ret = regmap_write(tcpci->regmap, TCPC_TCPC_CTRL, - (polarity == TYPEC_POLARITY_CC2) ? - TCPC_TCPC_CTRL_ORIENTATION : 0); + /* Keep the disconnect cc line open */ + ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, ®); if (ret < 0) return ret; - return 0; + if (polarity == TYPEC_POLARITY_CC2) + reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT; + else + reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT; + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + + return regmap_write(tcpci->regmap, TCPC_TCPC_CTRL, + (polarity == TYPEC_POLARITY_CC2) ? + TCPC_TCPC_CTRL_ORIENTATION : 0); } static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) @@ -207,12 +218,9 @@ static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) return ret; } - ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL, - enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); - if (ret < 0) - return ret; - - return 0; + return regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, + TCPC_POWER_CTRL_VCONN_ENABLE, + enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); } static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached, @@ -372,6 +380,12 @@ static int tcpci_init(struct tcpc_dev *tcpc) if (ret < 0) return ret; + /* Enable Vbus detection */ + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_ENABLE_VBUS_DETECT); + if (ret < 0) + return ret; + reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS | TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS; @@ -463,17 +477,16 @@ static const struct regmap_config tcpci_regmap_config = { .max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */ }; -static const struct tcpc_config tcpci_tcpc_config = { - .type = TYPEC_PORT_DFP, - .default_role = TYPEC_SINK, -}; - static int tcpci_parse_config(struct tcpci *tcpci) { tcpci->controls_vbus = true; /* XXX */ - /* TODO: Populate struct tcpc_config from ACPI/device-tree */ - tcpci->tcpc.config = &tcpci_tcpc_config; + tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev, + "connector"); + if (!tcpci->tcpc.fwnode) { + dev_err(tcpci->dev, "Can't find connector node.\n"); + return -EINVAL; + } return 0; } @@ -509,7 +522,7 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) return ERR_PTR(err); tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); - if (PTR_ERR_OR_ZERO(tcpci->port)) + if (IS_ERR(tcpci->port)) return ERR_CAST(tcpci->port); return tcpci; @@ -537,24 +550,27 @@ static int tcpci_probe(struct i2c_client *client, if (IS_ERR(chip->data.regmap)) return PTR_ERR(chip->data.regmap); + i2c_set_clientdata(client, chip); + /* Disable chip interrupts before requesting irq */ err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val, sizeof(u16)); if (err < 0) return err; + chip->tcpci = tcpci_register_port(&client->dev, &chip->data); + if (IS_ERR(chip->tcpci)) + return PTR_ERR(chip->tcpci); + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, _tcpci_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW, dev_name(&client->dev), chip); - if (err < 0) + if (err < 0) { + tcpci_unregister_port(chip->tcpci); return err; + } - chip->tcpci = tcpci_register_port(&client->dev, &chip->data); - if (PTR_ERR_OR_ZERO(chip->tcpci)) - return PTR_ERR(chip->tcpci); - - i2c_set_clientdata(client, chip); return 0; } @@ -575,7 +591,7 @@ MODULE_DEVICE_TABLE(i2c, tcpci_id); #ifdef CONFIG_OF static const struct of_device_id tcpci_of_match[] = { - { .compatible = "usb,tcpci", }, + { .compatible = "nxp,ptn5110", }, {}, }; MODULE_DEVICE_TABLE(of, tcpci_of_match); diff --git a/drivers/staging/typec/tcpci.h b/drivers/usb/typec/tcpci.h index 303ebde26546..303ebde26546 100644 --- a/drivers/staging/typec/tcpci.h +++ b/drivers/usb/typec/tcpci.h diff --git a/drivers/staging/typec/tcpci_rt1711h.c b/drivers/usb/typec/tcpci_rt1711h.c index 017389021b96..017389021b96 100644 --- a/drivers/staging/typec/tcpci_rt1711h.c +++ b/drivers/usb/typec/tcpci_rt1711h.c diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 8a201dd53d36..4f1f4215f3d6 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -26,7 +26,7 @@ #include <linux/usb/pd_vdo.h> #include <linux/usb/role.h> #include <linux/usb/tcpm.h> -#include <linux/usb/typec.h> +#include <linux/usb/typec_altmode.h> #include <linux/workqueue.h> #define FOREACH_STATE(S) \ @@ -169,13 +169,14 @@ enum pd_msg_request { /* Alternate mode support */ #define SVID_DISCOVERY_MAX 16 +#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) struct pd_mode_data { int svid_index; /* current SVID index */ int nsvids; u16 svids[SVID_DISCOVERY_MAX]; int altmodes; /* number of alternate modes */ - struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX]; + struct typec_altmode_desc altmode_desc[ALTMODE_DISCOVERY_MAX]; }; struct pd_pps_data { @@ -310,8 +311,8 @@ struct tcpm_port { /* Alternate mode data */ struct pd_mode_data mode_data; - struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX]; - struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX]; + struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX]; + struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX]; /* Deadline in jiffies to exit src_try_wait state */ unsigned long max_wait; @@ -418,17 +419,18 @@ static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) u64 ts_nsec = local_clock(); unsigned long rem_nsec; + mutex_lock(&port->logbuffer_lock); if (!port->logbuffer[port->logbuffer_head]) { port->logbuffer[port->logbuffer_head] = kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); - if (!port->logbuffer[port->logbuffer_head]) + if (!port->logbuffer[port->logbuffer_head]) { + mutex_unlock(&port->logbuffer_lock); return; + } } vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); - mutex_lock(&port->logbuffer_lock); - if (tcpm_log_full(port)) { port->logbuffer_head = max(port->logbuffer_head - 1, 0); strcpy(tmpbuffer, "overflow"); @@ -640,14 +642,14 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port, } EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); -static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode, +static int tcpm_mux_set(struct tcpm_port *port, int state, enum usb_role usb_role, enum typec_orientation orientation) { int ret; - tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d", - mode, usb_role, orientation); + tcpm_log(port, "Requesting mux state %d, usb-role %d, orientation %d", + state, usb_role, orientation); ret = typec_set_orientation(port->typec_port, orientation); if (ret) @@ -659,7 +661,7 @@ static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode, return ret; } - return typec_set_mode(port->typec_port, mode); + return typec_set_mode(port->typec_port, state); } static int tcpm_set_polarity(struct tcpm_port *port, @@ -724,6 +726,9 @@ static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv) tcpm_log(port, "Setting voltage/current limit %u mV %u mA", mv, max_ma); + port->supply_voltage = mv; + port->current_limit = max_ma; + if (port->tcpc->set_current_limit) ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv); @@ -786,7 +791,7 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached, else usb_role = USB_ROLE_DEVICE; - ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation); + ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation); if (ret < 0) return ret; @@ -994,7 +999,6 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload, { struct pd_mode_data *pmdata = &port->mode_data; struct typec_altmode_desc *paltmode; - struct typec_mode_desc *pmode; int i; if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { @@ -1002,32 +1006,36 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload, return; } - paltmode = &pmdata->altmode_desc[pmdata->altmodes]; - memset(paltmode, 0, sizeof(*paltmode)); + for (i = 1; i < cnt; i++) { + paltmode = &pmdata->altmode_desc[pmdata->altmodes]; + memset(paltmode, 0, sizeof(*paltmode)); - paltmode->svid = pmdata->svids[pmdata->svid_index]; + paltmode->svid = pmdata->svids[pmdata->svid_index]; + paltmode->mode = i; + paltmode->vdo = le32_to_cpu(payload[i]); - tcpm_log(port, " Alternate mode %d: SVID 0x%04x", - pmdata->altmodes, paltmode->svid); + tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x", + pmdata->altmodes, paltmode->svid, + paltmode->mode, paltmode->vdo); - for (i = 1; i < cnt && paltmode->n_modes < ALTMODE_MAX_MODES; i++) { - pmode = &paltmode->modes[paltmode->n_modes]; - memset(pmode, 0, sizeof(*pmode)); - pmode->vdo = le32_to_cpu(payload[i]); - pmode->index = i - 1; - paltmode->n_modes++; - tcpm_log(port, " VDO %d: 0x%08x", - pmode->index, pmode->vdo); + pmdata->altmodes++; } - port->partner_altmode[pmdata->altmodes] = - typec_partner_register_altmode(port->partner, paltmode); - if (!port->partner_altmode[pmdata->altmodes]) { - tcpm_log(port, - "Failed to register alternate modes for SVID 0x%04x", - paltmode->svid); - return; +} + +static void tcpm_register_partner_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data; + struct typec_altmode *altmode; + int i; + + for (i = 0; i < modep->altmodes; i++) { + altmode = typec_partner_register_altmode(port->partner, + &modep->altmode_desc[i]); + if (!altmode) + tcpm_log(port, "Failed to register partner SVID 0x%04x", + modep->altmode_desc[i].svid); + port->partner_altmode[i] = altmode; } - pmdata->altmodes++; } #define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header) @@ -1035,19 +1043,32 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload, static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, u32 *response) { - u32 p0 = le32_to_cpu(payload[0]); - int cmd_type = PD_VDO_CMDT(p0); - int cmd = PD_VDO_CMD(p0); + struct typec_altmode *adev; + struct typec_altmode *pdev; struct pd_mode_data *modep; + u32 p[PD_MAX_PAYLOAD]; int rlen = 0; - u16 svid; + int cmd_type; + int cmd; int i; + for (i = 0; i < cnt; i++) + p[i] = le32_to_cpu(payload[i]); + + cmd_type = PD_VDO_CMDT(p[0]); + cmd = PD_VDO_CMD(p[0]); + tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d", - p0, cmd_type, cmd, cnt); + p[0], cmd_type, cmd, cnt); modep = &port->mode_data; + adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); + + pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); + switch (cmd_type) { case CMDT_INIT: switch (cmd) { @@ -1069,17 +1090,19 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, case CMD_EXIT_MODE: break; case CMD_ATTENTION: - break; + /* Attention command does not have response */ + typec_altmode_attention(adev, p[1]); + return 0; default: break; } if (rlen >= 1) { - response[0] = p0 | VDO_CMDT(CMDT_RSP_ACK); + response[0] = p[0] | VDO_CMDT(CMDT_RSP_ACK); } else if (rlen == 0) { - response[0] = p0 | VDO_CMDT(CMDT_RSP_NAK); + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); rlen = 1; } else { - response[0] = p0 | VDO_CMDT(CMDT_RSP_BUSY); + response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY); rlen = 1; } break; @@ -1112,14 +1135,39 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, svdm_consume_modes(port, payload, cnt); modep->svid_index++; if (modep->svid_index < modep->nsvids) { - svid = modep->svids[modep->svid_index]; + u16 svid = modep->svids[modep->svid_index]; response[0] = VDO(svid, 1, CMD_DISCOVER_MODES); rlen = 1; } else { - /* enter alternate mode if/when implemented */ + tcpm_register_partner_altmodes(port); } break; case CMD_ENTER_MODE: + typec_altmode_update_active(pdev, true); + + if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { + response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + return 1; + } + return 0; + case CMD_EXIT_MODE: + typec_altmode_update_active(pdev, false); + + /* Back to USB Operation */ + WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, + NULL)); + break; + default: + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case CMD_ENTER_MODE: + /* Back to USB Operation */ + WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, + NULL)); break; default: break; @@ -1129,6 +1177,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, break; } + /* Informing the alternate mode drivers about everything */ + typec_altmode_vdm(adev, p[0], &p[1], cnt); + return rlen; } @@ -1412,6 +1463,57 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, return 0; } +static int tcpm_altmode_enter(struct typec_altmode *altmode) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + u32 header; + + mutex_lock(&port->lock); + header = VDO(altmode->svid, 1, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm(port, header, NULL, 0); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + mutex_unlock(&port->lock); + + return 0; +} + +static int tcpm_altmode_exit(struct typec_altmode *altmode) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + u32 header; + + mutex_lock(&port->lock); + header = VDO(altmode->svid, 1, CMD_EXIT_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm(port, header, NULL, 0); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + mutex_unlock(&port->lock); + + return 0; +} + +static int tcpm_altmode_vdm(struct typec_altmode *altmode, + u32 header, const u32 *data, int count) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + + mutex_lock(&port->lock); + tcpm_queue_vdm(port, header, data, count - 1); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + mutex_unlock(&port->lock); + + return 0; +} + +static const struct typec_altmode_ops tcpm_altmode_ops = { + .enter = tcpm_altmode_enter, + .exit = tcpm_altmode_exit, + .vdm = tcpm_altmode_vdm, +}; + /* * PD (data, control) command handling functions */ @@ -2136,7 +2238,7 @@ static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port) * PPS APDO. Again skip the first sink PDO as this will * always be 5V 3A. */ - for (j = i; j < port->nr_snk_pdo; j++) { + for (j = 1; j < port->nr_snk_pdo; j++) { pdo = port->snk_pdo[j]; switch (pdo_type(pdo)) { @@ -2431,15 +2533,15 @@ static int tcpm_set_charge(struct tcpm_port *port, bool charge) return 0; } -static bool tcpm_start_drp_toggling(struct tcpm_port *port) +static bool tcpm_start_drp_toggling(struct tcpm_port *port, + enum typec_cc_status cc) { int ret; if (port->tcpc->start_drp_toggling && port->port_type == TYPEC_PORT_DRP) { tcpm_log_force(port, "Start DRP toggling"); - ret = port->tcpc->start_drp_toggling(port->tcpc, - tcpm_rp_cc(port)); + ret = port->tcpc->start_drp_toggling(port->tcpc, cc); if (!ret) return true; } @@ -2543,7 +2645,7 @@ out_disable_vconn: out_disable_pd: port->tcpc->set_pd_rx(port->tcpc, false); out_disable_mux: - tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE, + tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, TYPEC_ORIENTATION_NONE); return ret; } @@ -2589,13 +2691,11 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_init_vconn(port); tcpm_set_current_limit(port, 0, 0); tcpm_set_polarity(port, TYPEC_POLARITY_CC1); - tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE, + tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, TYPEC_ORIENTATION_NONE); tcpm_set_attached_state(port, false); port->try_src_count = 0; port->try_snk_count = 0; - port->supply_voltage = 0; - port->current_limit = 0; port->usb_type = POWER_SUPPLY_USB_TYPE_C; power_supply_changed(port->psy); @@ -2747,7 +2847,7 @@ static void run_state_machine(struct tcpm_port *port) if (!port->non_pd_role_swap) tcpm_swap_complete(port, -ENOTCONN); tcpm_src_detach(port); - if (tcpm_start_drp_toggling(port)) { + if (tcpm_start_drp_toggling(port, tcpm_rp_cc(port))) { tcpm_set_state(port, DRP_TOGGLING, 0); break; } @@ -2922,7 +3022,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, -ENOTCONN); tcpm_pps_complete(port, -ENOTCONN); tcpm_snk_detach(port); - if (tcpm_start_drp_toggling(port)) { + if (tcpm_start_drp_toggling(port, TYPEC_CC_RD)) { tcpm_set_state(port, DRP_TOGGLING, 0); break; } @@ -3043,7 +3143,8 @@ static void run_state_machine(struct tcpm_port *port) tcpm_port_is_sink(port) && time_is_after_jiffies(port->delayed_runtime)) { tcpm_set_state(port, SNK_DISCOVERY, - port->delayed_runtime - jiffies); + jiffies_to_msecs(port->delayed_runtime - + jiffies)); break; } tcpm_set_state(port, unattached_state(port), 0); @@ -4236,6 +4337,81 @@ static int tcpm_copy_vdos(u32 *dest_vdo, const u32 *src_vdo, return nr_vdo; } +static int tcpm_fw_get_caps(struct tcpm_port *port, + struct fwnode_handle *fwnode) +{ + const char *cap_str; + int ret; + u32 mw; + + if (!fwnode) + return -EINVAL; + + /* USB data support is optional */ + ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); + if (ret == 0) { + port->typec_caps.data = typec_find_port_data_role(cap_str); + if (port->typec_caps.data < 0) + return -EINVAL; + } + + ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + if (ret < 0) + return ret; + + port->typec_caps.type = typec_find_port_power_role(cap_str); + if (port->typec_caps.type < 0) + return -EINVAL; + port->port_type = port->typec_caps.type; + + if (port->port_type == TYPEC_PORT_SNK) + goto sink; + + /* Get source pdos */ + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", + NULL, 0); + if (ret <= 0) + return -EINVAL; + + port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", + port->src_pdo, port->nr_src_pdo); + if ((ret < 0) || tcpm_validate_caps(port, port->src_pdo, + port->nr_src_pdo)) + return -EINVAL; + + if (port->port_type == TYPEC_PORT_SRC) + return 0; + + /* Get the preferred power role for DRP */ + ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str); + if (ret < 0) + return ret; + + port->typec_caps.prefer_role = typec_find_power_role(cap_str); + if (port->typec_caps.prefer_role < 0) + return -EINVAL; +sink: + /* Get sink pdos */ + ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", + NULL, 0); + if (ret <= 0) + return -EINVAL; + + port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", + port->snk_pdo, port->nr_snk_pdo); + if ((ret < 0) || tcpm_validate_caps(port, port->snk_pdo, + port->nr_snk_pdo)) + return -EINVAL; + + if (fwnode_property_read_u32(fwnode, "op-sink-microwatt", &mw) < 0) + return -EINVAL; + port->operating_snk_mw = mw / 1000; + + return 0; +} + int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo) { @@ -4521,12 +4697,36 @@ static int devm_tcpm_psy_register(struct tcpm_port *port) return PTR_ERR_OR_ZERO(port->psy); } +static int tcpm_copy_caps(struct tcpm_port *port, + const struct tcpc_config *tcfg) +{ + if (tcpm_validate_caps(port, tcfg->src_pdo, tcfg->nr_src_pdo) || + tcpm_validate_caps(port, tcfg->snk_pdo, tcfg->nr_snk_pdo)) + return -EINVAL; + + port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcfg->src_pdo, + tcfg->nr_src_pdo); + port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcfg->snk_pdo, + tcfg->nr_snk_pdo); + + port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcfg->snk_vdo, + tcfg->nr_snk_vdo); + + port->operating_snk_mw = tcfg->operating_snk_mw; + + port->typec_caps.prefer_role = tcfg->default_role; + port->typec_caps.type = tcfg->type; + port->typec_caps.data = tcfg->data; + + return 0; +} + struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) { struct tcpm_port *port; int i, err; - if (!dev || !tcpc || !tcpc->config || + if (!dev || !tcpc || !tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc || !tcpc->set_polarity || !tcpc->set_vconn || !tcpc->set_vbus || !tcpc->set_pd_rx || !tcpc->set_roles || !tcpc->pd_transmit) @@ -4556,29 +4756,18 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) init_completion(&port->pps_complete); tcpm_debugfs_init(port); - if (tcpm_validate_caps(port, tcpc->config->src_pdo, - tcpc->config->nr_src_pdo) || - tcpm_validate_caps(port, tcpc->config->snk_pdo, - tcpc->config->nr_snk_pdo)) { - err = -EINVAL; + err = tcpm_fw_get_caps(port, tcpc->fwnode); + if ((err < 0) && tcpc->config) + err = tcpm_copy_caps(port, tcpc->config); + if (err < 0) goto out_destroy_wq; - } - port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo, - tcpc->config->nr_src_pdo); - port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcpc->config->snk_pdo, - tcpc->config->nr_snk_pdo); - port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo, - tcpc->config->nr_snk_vdo); - - port->operating_snk_mw = tcpc->config->operating_snk_mw; - if (!tcpc->config->try_role_hw) - port->try_role = tcpc->config->default_role; + + if (!tcpc->config || !tcpc->config->try_role_hw) + port->try_role = port->typec_caps.prefer_role; else port->try_role = TYPEC_NO_PREFERRED_ROLE; - port->typec_caps.prefer_role = tcpc->config->default_role; - port->typec_caps.type = tcpc->config->type; - port->typec_caps.data = tcpc->config->data; + port->typec_caps.fwnode = tcpc->fwnode; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ port->typec_caps.dr_set = tcpm_dr_set; @@ -4588,7 +4777,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.port_type_set = tcpm_port_type_set; port->partner_desc.identity = &port->partner_ident; - port->port_type = tcpc->config->type; + port->port_type = port->typec_caps.type; port->role_sw = usb_role_switch_get(port->dev); if (IS_ERR(port->role_sw)) { @@ -4606,7 +4795,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_destroy_wq; } - if (tcpc->config->alt_modes) { + if (tcpc->config && tcpc->config->alt_modes) { const struct typec_altmode_desc *paltmode = tcpc->config->alt_modes; i = 0; @@ -4621,6 +4810,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) dev_name(dev), paltmode->svid); break; } + typec_altmode_set_drvdata(alt, port); + alt->ops = &tcpm_altmode_ops; port->port_altmode[i] = alt; i++; paltmode++; diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c index 4b4c8d271b27..c84c8c189e90 100644 --- a/drivers/usb/typec/tps6598x.c +++ b/drivers/usb/typec/tps6598x.c @@ -81,12 +81,21 @@ struct tps6598x { struct typec_capability typec_cap; }; +/* + * Max data bytes for Data1, Data2, and other registers. See ch 1.3.2: + * http://www.ti.com/lit/ug/slvuan1a/slvuan1a.pdf + */ +#define TPS_MAX_LEN 64 + static int tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len) { - u8 data[len + 1]; + u8 data[TPS_MAX_LEN + 1]; int ret; + if (WARN_ON(len + 1 > sizeof(data))) + return -EINVAL; + if (!tps->i2c_protocol) return regmap_raw_read(tps->regmap, reg, val, len); diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index bd5cca5632b3..8d0a6fe748bd 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -350,6 +350,19 @@ static void ucsi_connector_change(struct work_struct *work) } if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) { + typec_set_pwr_role(con->port, con->status.pwr_dir); + + switch (con->status.partner_type) { + case UCSI_CONSTAT_PARTNER_TYPE_UFP: + typec_set_data_role(con->port, TYPEC_HOST); + break; + case UCSI_CONSTAT_PARTNER_TYPE_DFP: + typec_set_data_role(con->port, TYPEC_DEVICE); + break; + default: + break; + } + if (con->status.connected) ucsi_register_partner(con); else diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index 44eb4e1ea817..a18112a83fae 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -79,6 +79,11 @@ static int ucsi_acpi_probe(struct platform_device *pdev) return -ENODEV; } + /* This will make sure we can use ioremap_nocache() */ + status = acpi_release_memory(ACPI_HANDLE(&pdev->dev), res, 1); + if (ACPI_FAILURE(status)) + return -ENOMEM; + /* * NOTE: The memory region for the data structures is used also in an * operation region, which means ACPI has already reserved it. Therefore |