diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 21:00:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 21:00:51 +0000 |
commit | 6d03a247468059b0e59c821ef39e6762d4d6fc30 (patch) | |
tree | 17b9c00de2c62e68c965c742cdbc206f77a375da /drivers/usb | |
parent | Releasing progress-linux version 6.8.12-1~progress7.99u1. (diff) | |
download | linux-6d03a247468059b0e59c821ef39e6762d4d6fc30.tar.xz linux-6d03a247468059b0e59c821ef39e6762d4d6fc30.zip |
Merging upstream version 6.9.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/usb')
89 files changed, 4360 insertions, 1838 deletions
diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c index ee917f1b09..8b936a2e93 100644 --- a/drivers/usb/cdns3/drd.c +++ b/drivers/usb/cdns3/drd.c @@ -435,7 +435,7 @@ int cdns_drd_init(struct cdns *cdns) writel(1, &cdns->otg_v1_regs->simulate); cdns->version = CDNS3_CONTROLLER_V1; } else { - dev_err(cdns->dev, "not supporte DID=0x%08x\n", state); + dev_err(cdns->dev, "not supported DID=0x%08x\n", state); return -EINVAL; } diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 351ede4b5d..58e3ca7e47 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -116,3 +116,30 @@ config USB_AUTOSUSPEND_DELAY The default value Linux has always had is 2 seconds. Change this value if you want a different delay and cannot modify the command line or module parameter. + +config USB_DEFAULT_AUTHORIZATION_MODE + int "Default authorization mode for USB devices" + range 0 2 + default 1 + depends on USB + help + Select the default USB device authorization mode. Can be overridden + with usbcore.authorized_default command line or module parameter. + + This option allows you to choose whether USB devices that are + connected to the system can be used by default, or if they are + locked down. + + With value 0 all connected USB devices with the exception of root + hub require user space authorization before they can be used. + + With value 1 (default) no user space authorization is required to + use connected USB devices. + + With value 2 all connected USB devices with exception of internal + USB devices require user space authorization before they can be + used. Note that in this mode the differentiation between internal + and external USB devices relies on ACPI, and on systems without + ACPI selecting value 2 is analogous to selecting value 0. + + If unsure, keep the default value. diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index e01b1913d0..e02ba15f6e 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1710,9 +1710,7 @@ int usb_autoresume_device(struct usb_device *udev) { int status; - status = pm_runtime_get_sync(&udev->dev); - if (status < 0) - pm_runtime_put_sync(&udev->dev); + status = pm_runtime_resume_and_get(&udev->dev); dev_vdbg(&udev->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&udev->dev.power.usage_count), status); @@ -1818,9 +1816,7 @@ int usb_autopm_get_interface(struct usb_interface *intf) { int status; - status = pm_runtime_get_sync(&intf->dev); - if (status < 0) - pm_runtime_put_sync(&intf->dev); + status = pm_runtime_resume_and_get(&intf->dev); dev_vdbg(&intf->dev, "%s: cnt %d -> %d\n", __func__, atomic_read(&intf->dev.power.usage_count), status); diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c index a2530811cf..4b38b87a13 100644 --- a/drivers/usb/core/endpoint.c +++ b/drivers/usb/core/endpoint.c @@ -141,7 +141,7 @@ static void ep_device_release(struct device *dev) kfree(ep_dev); } -struct device_type usb_ep_device_type = { +const struct device_type usb_ep_device_type = { .name = "usb_endpoint", .release = ep_device_release, }; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 12b6dfeaf6..c0e005670d 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -357,12 +357,10 @@ static const u8 ss_rh_config_descriptor[] = { #define USB_AUTHORIZE_ALL 1 #define USB_AUTHORIZE_INTERNAL 2 -static int authorized_default = USB_AUTHORIZE_WIRED; +static int authorized_default = CONFIG_USB_DEFAULT_AUTHORIZATION_MODE; module_param(authorized_default, int, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(authorized_default, - "Default USB device authorization: 0 is not authorized, 1 is " - "authorized, 2 is authorized for internal devices, -1 is " - "authorized (default, same as 1)"); + "Default USB device authorization: 0 is not authorized, 1 is authorized (default), 2 is authorized for internal devices, -1 is authorized (same as 1)"); /*-------------------------------------------------------------------------*/ /** @@ -1664,9 +1662,10 @@ static void __usb_hcd_giveback_urb(struct urb *urb) usb_put_urb(urb); } -static void usb_giveback_urb_bh(struct tasklet_struct *t) +static void usb_giveback_urb_bh(struct work_struct *work) { - struct giveback_urb_bh *bh = from_tasklet(bh, t, bh); + struct giveback_urb_bh *bh = + container_of(work, struct giveback_urb_bh, bh); struct list_head local_list; spin_lock_irq(&bh->lock); @@ -1691,9 +1690,9 @@ static void usb_giveback_urb_bh(struct tasklet_struct *t) spin_lock_irq(&bh->lock); if (!list_empty(&bh->head)) { if (bh->high_prio) - tasklet_hi_schedule(&bh->bh); + queue_work(system_bh_highpri_wq, &bh->bh); else - tasklet_schedule(&bh->bh); + queue_work(system_bh_wq, &bh->bh); } bh->running = false; spin_unlock_irq(&bh->lock); @@ -1706,7 +1705,7 @@ static void usb_giveback_urb_bh(struct tasklet_struct *t) * @status: completion status code for the URB. * * Context: atomic. The completion callback is invoked in caller's context. - * For HCDs with HCD_BH flag set, the completion callback is invoked in tasklet + * For HCDs with HCD_BH flag set, the completion callback is invoked in BH * context (except for URBs submitted to the root hub which always complete in * caller's context). * @@ -1725,7 +1724,7 @@ void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status) struct giveback_urb_bh *bh; bool running; - /* pass status to tasklet via unlinked */ + /* pass status to BH via unlinked */ if (likely(!urb->unlinked)) urb->unlinked = status; @@ -1747,9 +1746,9 @@ void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status) if (running) ; else if (bh->high_prio) - tasklet_hi_schedule(&bh->bh); + queue_work(system_bh_highpri_wq, &bh->bh); else - tasklet_schedule(&bh->bh); + queue_work(system_bh_wq, &bh->bh); } EXPORT_SYMBOL_GPL(usb_hcd_giveback_urb); @@ -2540,7 +2539,7 @@ static void init_giveback_urb_bh(struct giveback_urb_bh *bh) spin_lock_init(&bh->lock); INIT_LIST_HEAD(&bh->head); - tasklet_setup(&bh->bh, usb_giveback_urb_bh); + INIT_WORK(&bh->bh, usb_giveback_urb_bh); } struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver, @@ -2794,10 +2793,16 @@ int usb_add_hcd(struct usb_hcd *hcd, struct usb_device *rhdev; struct usb_hcd *shared_hcd; - if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) { - hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); - if (IS_ERR(hcd->phy_roothub)) - return PTR_ERR(hcd->phy_roothub); + if (!hcd->skip_phy_initialization) { + if (usb_hcd_is_primary_hcd(hcd)) { + hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } else { + hcd->phy_roothub = usb_phy_roothub_alloc_usb3_phy(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } retval = usb_phy_roothub_init(hcd->phy_roothub); if (retval) @@ -2926,7 +2931,7 @@ int usb_add_hcd(struct usb_hcd *hcd, && device_can_wakeup(&hcd->self.root_hub->dev)) dev_dbg(hcd->self.controller, "supports USB remote wakeup\n"); - /* initialize tasklets */ + /* initialize BHs */ init_giveback_urb_bh(&hcd->high_prio_bh); hcd->high_prio_bh.high_prio = true; init_giveback_urb_bh(&hcd->low_prio_bh); @@ -3036,7 +3041,7 @@ void usb_remove_hcd(struct usb_hcd *hcd) mutex_unlock(&usb_bus_idr_lock); /* - * tasklet_kill() isn't needed here because: + * flush_work() isn't needed here because: * - driver's disconnect() called from usb_disconnect() should * make sure its URBs are completed during the disconnect() * callback diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 48e1e43780..0080530398 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -37,6 +37,7 @@ #include <asm/byteorder.h> #include "hub.h" +#include "phy.h" #include "otg_productlist.h" #define USB_VENDOR_GENESYS_LOGIC 0x05e3 @@ -633,6 +634,34 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, ret = 0; } mutex_unlock(&hub->status_mutex); + + /* + * There is no need to lock status_mutex here, because status_mutex + * protects hub->status, and the phy driver only checks the port + * status without changing the status. + */ + if (!ret) { + struct usb_device *hdev = hub->hdev; + + /* + * Only roothub will be notified of connection changes, + * since the USB PHY only cares about changes at the next + * level. + */ + if (is_root_hub(hdev)) { + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + bool connect; + bool connect_change; + + connect_change = *change & USB_PORT_STAT_C_CONNECTION; + connect = *status & USB_PORT_STAT_CONNECTION; + if (connect_change && connect) + usb_phy_roothub_notify_connect(hcd->phy_roothub, port1 - 1); + else if (connect_change) + usb_phy_roothub_notify_disconnect(hcd->phy_roothub, port1 - 1); + } + } + return ret; } diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 077dfe48d0..d2b2787be4 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1198,6 +1198,8 @@ EXPORT_SYMBOL_GPL(usb_get_status); * same status code used to report a true stall. * * This call is synchronous, and may not be used in an interrupt context. + * If a thread in your driver uses this call, make sure your disconnect() + * method can wait for it to complete. * * Return: Zero on success, or else the status code returned by the * underlying usb_control_msg() call. @@ -1516,7 +1518,8 @@ void usb_enable_interface(struct usb_device *dev, * This call is synchronous, and may not be used in an interrupt context. * Also, drivers must not change altsettings while urbs are scheduled for * endpoints in that interface; all such urbs must first be completed - * (perhaps forced by unlinking). + * (perhaps forced by unlinking). If a thread in your driver uses this call, + * make sure your disconnect() method can wait for it to complete. * * Return: Zero on success, or else the status code returned by the * underlying usb_control_msg() call. @@ -1849,7 +1852,7 @@ static int usb_if_uevent(const struct device *dev, struct kobj_uevent_env *env) return 0; } -struct device_type usb_if_device_type = { +const struct device_type usb_if_device_type = { .name = "usb_interface", .release = usb_release_interface, .uevent = usb_if_uevent, diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c index db4ccf9ce3..f1a499ee48 100644 --- a/drivers/usb/core/of.c +++ b/drivers/usb/core/of.c @@ -8,6 +8,7 @@ */ #include <linux/of.h> +#include <linux/of_graph.h> #include <linux/usb/of.h> /** @@ -75,6 +76,76 @@ bool usb_of_has_combined_node(struct usb_device *udev) } EXPORT_SYMBOL_GPL(usb_of_has_combined_node); +static bool usb_of_has_devices_or_graph(const struct usb_device *hub) +{ + const struct device_node *np = hub->dev.of_node; + struct device_node *child; + + if (of_graph_is_present(np)) + return true; + + for_each_child_of_node(np, child) + if (of_property_present(child, "reg")) + return true; + + return false; +} + +/** + * usb_of_get_connect_type() - get a USB hub's port connect_type + * @hub: hub to which port is for @port1 + * @port1: one-based index of port + * + * Get the connect_type of @port1 based on the device node for @hub. If the + * port is described in the OF graph, the connect_type is "hotplug". If the + * @hub has a child device has with a 'reg' property equal to @port1 the + * connect_type is "hard-wired". If there isn't an OF graph or child node at + * all then the connect_type is "unknown". Otherwise, the port is considered + * "unused" because it isn't described at all. + * + * Return: A connect_type for @port1 based on the device node for @hub. + */ +enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int port1) +{ + struct device_node *np, *child, *ep, *remote_np; + enum usb_port_connect_type connect_type; + + /* Only set connect_type if binding has ports/hardwired devices. */ + if (!usb_of_has_devices_or_graph(hub)) + return USB_PORT_CONNECT_TYPE_UNKNOWN; + + /* Assume port is unused if there's a graph or a child node. */ + connect_type = USB_PORT_NOT_USED; + + np = hub->dev.of_node; + /* + * Hotplug ports are connected to an available remote node, e.g. + * usb-a-connector compatible node, in the OF graph. + */ + if (of_graph_is_present(np)) { + ep = of_graph_get_endpoint_by_regs(np, port1, -1); + if (ep) { + remote_np = of_graph_get_remote_port_parent(ep); + of_node_put(ep); + if (of_device_is_available(remote_np)) + connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG; + of_node_put(remote_np); + } + } + + /* + * Hard-wired ports are child nodes with a reg property corresponding + * to the port number, i.e. a usb device. + */ + child = usb_of_get_device_node(hub, port1); + if (of_device_is_available(child)) + connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED; + of_node_put(child); + + return connect_type; +} +EXPORT_SYMBOL_GPL(usb_of_get_connect_type); + /** * usb_of_get_interface_node() - get a USB interface node * @udev: USB device of interface diff --git a/drivers/usb/core/phy.c b/drivers/usb/core/phy.c index fb1588e7c2..faa20054ad 100644 --- a/drivers/usb/core/phy.c +++ b/drivers/usb/core/phy.c @@ -19,6 +19,30 @@ struct usb_phy_roothub { struct list_head list; }; +/* Allocate the roothub_entry by specific name of phy */ +static int usb_phy_roothub_add_phy_by_name(struct device *dev, const char *name, + struct list_head *list) +{ + struct usb_phy_roothub *roothub_entry; + struct phy *phy; + + phy = devm_of_phy_get(dev, dev->of_node, name); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL); + if (!roothub_entry) + return -ENOMEM; + + INIT_LIST_HEAD(&roothub_entry->list); + + roothub_entry->phy = phy; + + list_add_tail(&roothub_entry->list, list); + + return 0; +} + static int usb_phy_roothub_add_phy(struct device *dev, int index, struct list_head *list) { @@ -65,6 +89,9 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) INIT_LIST_HEAD(&phy_roothub->list); + if (!usb_phy_roothub_add_phy_by_name(dev, "usb2-phy", &phy_roothub->list)) + return phy_roothub; + for (i = 0; i < num_phys; i++) { err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list); if (err) @@ -75,6 +102,41 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) } EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc); +/** + * usb_phy_roothub_alloc_usb3_phy - alloc the roothub + * @dev: the device of the host controller + * + * Allocate the usb phy roothub if the host use a generic usb3-phy. + * + * Return: On success, a pointer to the usb_phy_roothub. Otherwise, + * %NULL if no use usb3 phy or %-ENOMEM if out of memory. + */ +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev) +{ + struct usb_phy_roothub *phy_roothub; + int num_phys; + + if (!IS_ENABLED(CONFIG_GENERIC_PHY)) + return NULL; + + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + if (num_phys <= 0) + return NULL; + + phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL); + if (!phy_roothub) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&phy_roothub->list); + + if (!usb_phy_roothub_add_phy_by_name(dev, "usb3-phy", &phy_roothub->list)) + return phy_roothub; + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc_usb3_phy); + int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub) { struct usb_phy_roothub *roothub_entry; @@ -172,6 +234,64 @@ int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub) } EXPORT_SYMBOL_GPL(usb_phy_roothub_calibrate); +/** + * usb_phy_roothub_notify_connect() - connect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for connect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_connect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_connect); + +/** + * usb_phy_roothub_notify_disconnect() - disconnect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for disconnect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_disconnect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_disconnect); + int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub) { struct usb_phy_roothub *roothub_entry; diff --git a/drivers/usb/core/phy.h b/drivers/usb/core/phy.h index 20a267cd98..88b49c0ea6 100644 --- a/drivers/usb/core/phy.h +++ b/drivers/usb/core/phy.h @@ -12,6 +12,7 @@ struct device; struct usb_phy_roothub; struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev); +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev); int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub); int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); @@ -19,6 +20,8 @@ int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub, enum phy_mode mode); int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub); +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port); +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port); int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub); void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub); diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index d34d67fcb4..e7da2fca11 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -11,6 +11,7 @@ #include <linux/slab.h> #include <linux/pm_qos.h> #include <linux/component.h> +#include <linux/usb/of.h> #include "hub.h" @@ -465,7 +466,7 @@ static const struct dev_pm_ops usb_port_pm_ops = { #endif }; -struct device_type usb_port_device_type = { +const struct device_type usb_port_device_type = { .name = "usb_port", .release = usb_port_device_release, .pm = &usb_port_pm_ops, @@ -745,6 +746,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) return -ENOMEM; } + port_dev->connect_type = usb_of_get_connect_type(hdev, port1); hub->ports[port1 - 1] = port_dev; port_dev->portnum = port1; set_bit(port1, hub->power_bits); diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index f91f543ec6..d83231d673 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -273,9 +273,10 @@ static ssize_t avoid_reset_quirk_store(struct device *dev, const char *buf, size_t count) { struct usb_device *udev = to_usb_device(dev); - int val, rc; + bool val; + int rc; - if (sscanf(buf, "%d", &val) != 1 || val < 0 || val > 1) + if (kstrtobool(buf, &val) != 0) return -EINVAL; rc = usb_lock_device_interruptible(udev); if (rc < 0) @@ -322,13 +323,14 @@ static ssize_t persist_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct usb_device *udev = to_usb_device(dev); - int value, rc; + bool value; + int rc; /* Hubs are always enabled for USB_PERSIST */ if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) return -EPERM; - if (sscanf(buf, "%d", &value) != 1) + if (kstrtobool(buf, &value) != 0) return -EINVAL; rc = usb_lock_device_interruptible(udev); @@ -739,14 +741,14 @@ static ssize_t authorized_store(struct device *dev, { ssize_t result; struct usb_device *usb_dev = to_usb_device(dev); - unsigned val; - result = sscanf(buf, "%u\n", &val); - if (result != 1) + bool val; + + if (kstrtobool(buf, &val) != 0) result = -EINVAL; - else if (val == 0) - result = usb_deauthorize_device(usb_dev); - else + else if (val) result = usb_authorize_device(usb_dev); + else + result = usb_deauthorize_device(usb_dev); return result < 0 ? result : size; } static DEVICE_ATTR_IGNORE_LOCKDEP(authorized, S_IRUGO | S_IWUSR, @@ -847,16 +849,10 @@ static const struct attribute_group dev_string_attr_grp = { .is_visible = dev_string_attrs_are_visible, }; -const struct attribute_group *usb_device_groups[] = { - &dev_attr_grp, - &dev_string_attr_grp, - NULL -}; - /* Binary descriptors */ static ssize_t -read_descriptors(struct file *filp, struct kobject *kobj, +descriptors_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { @@ -878,7 +874,7 @@ read_descriptors(struct file *filp, struct kobject *kobj, srclen = sizeof(struct usb_device_descriptor); } else { src = udev->rawdescriptors[cfgno]; - srclen = __le16_to_cpu(udev->config[cfgno].desc. + srclen = le16_to_cpu(udev->config[cfgno].desc. wTotalLength); } if (off < srclen) { @@ -893,11 +889,69 @@ read_descriptors(struct file *filp, struct kobject *kobj, } return count - nleft; } +static BIN_ATTR_RO(descriptors, 18 + 65535); /* dev descr + max-size raw descriptor */ + +static ssize_t +bos_descriptors_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_device *udev = to_usb_device(dev); + struct usb_host_bos *bos = udev->bos; + struct usb_bos_descriptor *desc; + size_t desclen, n = 0; + + if (bos) { + desc = bos->desc; + desclen = le16_to_cpu(desc->wTotalLength); + if (off < desclen) { + n = min(count, desclen - (size_t) off); + memcpy(buf, (void *) desc + off, n); + } + } + return n; +} +static BIN_ATTR_RO(bos_descriptors, 65535); /* max-size BOS */ -static struct bin_attribute dev_bin_attr_descriptors = { - .attr = {.name = "descriptors", .mode = 0444}, - .read = read_descriptors, - .size = 18 + 65535, /* dev descr + max-size raw descriptor */ +/* When modifying this list, be sure to modify dev_bin_attrs_are_visible() + * accordingly. + */ +static struct bin_attribute *dev_bin_attrs[] = { + &bin_attr_descriptors, + &bin_attr_bos_descriptors, + NULL +}; + +static umode_t dev_bin_attrs_are_visible(struct kobject *kobj, + struct bin_attribute *a, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_device *udev = to_usb_device(dev); + + /* + * There's no need to check if the descriptors attribute should + * be visible because all devices have a device descriptor. The + * bos_descriptors attribute should be visible if and only if + * the device has a BOS, so check if it exists here. + */ + if (a == &bin_attr_bos_descriptors) { + if (udev->bos == NULL) + return 0; + } + return a->attr.mode; +} + +static const struct attribute_group dev_bin_attr_grp = { + .bin_attrs = dev_bin_attrs, + .is_bin_visible = dev_bin_attrs_are_visible, +}; + +const struct attribute_group *usb_device_groups[] = { + &dev_attr_grp, + &dev_string_attr_grp, + &dev_bin_attr_grp, + NULL }; /* @@ -1015,10 +1069,6 @@ int usb_create_sysfs_dev_files(struct usb_device *udev) struct device *dev = &udev->dev; int retval; - retval = device_create_bin_file(dev, &dev_bin_attr_descriptors); - if (retval) - goto error; - retval = add_persist_attributes(dev); if (retval) goto error; @@ -1048,7 +1098,6 @@ void usb_remove_sysfs_dev_files(struct usb_device *udev) remove_power_attributes(dev); remove_persist_attributes(dev); - device_remove_bin_file(dev, &dev_bin_attr_descriptors); } /* Interface Association Descriptor fields */ diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index a34b22537d..7f8a912d4f 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -142,12 +142,19 @@ int usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable) } EXPORT_SYMBOL_GPL(usb_acpi_set_power_state); -static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle, - struct acpi_pld_info *pld) +/* + * Private to usb-acpi, all the core needs to know is that + * port_dev->location is non-zero when it has been set by the firmware. + */ +#define USB_ACPI_LOCATION_VALID (1 << 31) + +static void +usb_acpi_get_connect_type(struct usb_port *port_dev, acpi_handle *handle) { enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *upc = NULL; + struct acpi_pld_info *pld = NULL; acpi_status status; /* @@ -158,6 +165,12 @@ static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle, * a usb device is directly hard-wired to the port. If no visible and * no connectable, the port would be not used. */ + + status = acpi_get_physical_device_location(handle, &pld); + if (ACPI_SUCCESS(status) && pld) + port_dev->location = USB_ACPI_LOCATION_VALID | + pld->group_token << 8 | pld->group_position; + status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer); if (ACPI_FAILURE(status)) goto out; @@ -166,25 +179,22 @@ static enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle, if (!upc || (upc->type != ACPI_TYPE_PACKAGE) || upc->package.count != 4) goto out; + /* UPC states port is connectable */ if (upc->package.elements[0].integer.value) - if (pld->user_visible) + if (!pld) + ; /* keep connect_type as unknown */ + else if (pld->user_visible) connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG; else connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED; - else if (!pld->user_visible) + else connect_type = USB_PORT_NOT_USED; out: + port_dev->connect_type = connect_type; kfree(upc); - return connect_type; + ACPI_FREE(pld); } - -/* - * Private to usb-acpi, all the core needs to know is that - * port_dev->location is non-zero when it has been set by the firmware. - */ -#define USB_ACPI_LOCATION_VALID (1 << 31) - static struct acpi_device * usb_acpi_get_companion_for_port(struct usb_port *port_dev) { @@ -222,22 +232,12 @@ static struct acpi_device * usb_acpi_find_companion_for_port(struct usb_port *port_dev) { struct acpi_device *adev; - struct acpi_pld_info *pld; - acpi_handle *handle; - acpi_status status; adev = usb_acpi_get_companion_for_port(port_dev); if (!adev) return NULL; - handle = adev->handle; - status = acpi_get_physical_device_location(handle, &pld); - if (ACPI_SUCCESS(status) && pld) { - port_dev->location = USB_ACPI_LOCATION_VALID - | pld->group_token << 8 | pld->group_position; - port_dev->connect_type = usb_acpi_get_connect_type(handle, pld); - ACPI_FREE(pld); - } + usb_acpi_get_connect_type(port_dev, adev->handle); return adev; } diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index dc8d9228a5..a0c432b14b 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -592,7 +592,7 @@ static char *usb_devnode(const struct device *dev, usb_dev->bus->busnum, usb_dev->devnum); } -struct device_type usb_device_type = { +const struct device_type usb_device_type = { .name = "usb_device", .release = usb_release_dev, .uevent = usb_dev_uevent, diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index bfecb50773..b8324ea05b 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -144,10 +144,10 @@ static inline int usb_disable_usb2_hardware_lpm(struct usb_device *udev) extern const struct class usbmisc_class; extern const struct bus_type usb_bus_type; extern struct mutex usb_port_peer_mutex; -extern struct device_type usb_device_type; -extern struct device_type usb_if_device_type; -extern struct device_type usb_ep_device_type; -extern struct device_type usb_port_device_type; +extern const struct device_type usb_device_type; +extern const struct device_type usb_if_device_type; +extern const struct device_type usb_ep_device_type; +extern const struct device_type usb_port_device_type; extern struct usb_device_driver usb_generic_driver; static inline int is_usb_device(const struct device *dev) diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 5fc27b20df..31078f3d41 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -131,7 +131,7 @@ config USB_DWC3_QCOM tristate "Qualcomm Platform" depends on ARCH_QCOM || COMPILE_TEST depends on EXTCON || !EXTCON - depends on (OF || ACPI) + depends on OF default USB_DWC3 help Some Qualcomm SoCs use DesignWare Core IP for USB2/3 diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index d96a28eecb..180dd8d292 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -755,6 +755,7 @@ struct dwc3_ep { #define DWC3_EP_PENDING_CLEAR_STALL BIT(11) #define DWC3_EP_TXFIFO_RESIZED BIT(12) #define DWC3_EP_DELAY_STOP BIT(13) +#define DWC3_EP_RESOURCE_ALLOCATED BIT(14) /* This last one is specific to EP0 */ #define DWC3_EP0_DIR_IN BIT(31) @@ -1258,6 +1259,7 @@ struct dwc3 { #define DWC31_REVISION_170A 0x3137302a #define DWC31_REVISION_180A 0x3138302a #define DWC31_REVISION_190A 0x3139302a +#define DWC31_REVISION_200A 0x3230302a #define DWC32_REVISION_ANY 0x0 #define DWC32_REVISION_100A 0x3130302a diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index ea6e29091c..fad151e78f 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -97,9 +97,15 @@ #define USBSS_VBUS_STAT_SESSVALID BIT(2) #define USBSS_VBUS_STAT_VBUSVALID BIT(0) -/* Mask for PHY PLL REFCLK */ +/* USB_PHY_CTRL register bits in CTRL_MMR */ +#define PHY_CORE_VOLTAGE_MASK BIT(31) #define PHY_PLL_REFCLK_MASK GENMASK(3, 0) +/* USB PHY2 register offsets */ +#define USB_PHY_PLL_REG12 0x130 +#define USB_PHY_PLL_LDO_REF_EN BIT(5) +#define USB_PHY_PLL_LDO_REF_EN_EN BIT(4) + #define DWC3_AM62_AUTOSUSPEND_DELAY 100 struct dwc3_am62 { @@ -162,6 +168,13 @@ static int phy_syscon_pll_refclk(struct dwc3_am62 *am62) am62->offset = args.args[0]; + /* Core voltage. PHY_CORE_VOLTAGE bit Recommended to be 0 always */ + ret = regmap_update_bits(am62->syscon, am62->offset, PHY_CORE_VOLTAGE_MASK, 0); + if (ret) { + dev_err(dev, "failed to set phy core voltage\n"); + return ret; + } + ret = regmap_update_bits(am62->syscon, am62->offset, PHY_PLL_REFCLK_MASK, am62->rate_code); if (ret) { dev_err(dev, "failed to set phy pll reference clock rate\n"); @@ -176,8 +189,9 @@ static int dwc3_ti_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *node = pdev->dev.of_node; struct dwc3_am62 *am62; - int i, ret; unsigned long rate; + void __iomem *phy; + int i, ret; u32 reg; am62 = devm_kzalloc(dev, sizeof(*am62), GFP_KERNEL); @@ -219,6 +233,17 @@ static int dwc3_ti_probe(struct platform_device *pdev) if (ret) return ret; + /* Workaround Errata i2409 */ + phy = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(phy)) { + dev_err(dev, "can't map PHY IOMEM resource. Won't apply i2409 fix.\n"); + phy = NULL; + } else { + reg = readl(phy + USB_PHY_PLL_REG12); + reg |= USB_PHY_PLL_LDO_REF_EN | USB_PHY_PLL_LDO_REF_EN_EN; + writel(reg, phy + USB_PHY_PLL_REG12); + } + /* VBUS divider select */ am62->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider"); reg = dwc3_ti_readl(am62, USBSS_PHY_CONFIG); diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index d1539fc9ea..be7be00ecb 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -52,8 +52,7 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) if (of_device_is_compatible(np, "rockchip,rk3399-dwc3")) simple->need_reset = true; - simple->resets = of_reset_control_array_get(np, false, true, - true); + simple->resets = of_reset_control_array_get_optional_exclusive(np); if (IS_ERR(simple->resets)) { ret = PTR_ERR(simple->resets); dev_err(dev, "failed to get device resets, err=%d\n", ret); @@ -173,6 +172,7 @@ static const struct of_device_id of_dwc3_simple_match[] = { { .compatible = "sprd,sc9860-dwc3" }, { .compatible = "allwinner,sun50i-h6-dwc3" }, { .compatible = "hisilicon,hi3670-dwc3" }, + { .compatible = "hisilicon,hi3798mv200-dwc3" }, { .compatible = "intel,keembay-dwc3" }, { /* Sentinel */ } }; diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index dbd6a5b2b2..f6b2fab49d 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -4,7 +4,6 @@ * Inspired by dwc3-of-simple.c */ -#include <linux/acpi.h> #include <linux/io.h> #include <linux/of.h> #include <linux/clk.h> @@ -53,22 +52,10 @@ #define APPS_USB_AVG_BW 0 #define APPS_USB_PEAK_BW MBps_to_icc(40) -struct dwc3_acpi_pdata { - u32 qscratch_base_offset; - u32 qscratch_base_size; - u32 dwc3_core_base_size; - int qusb2_phy_irq_index; - int dp_hs_phy_irq_index; - int dm_hs_phy_irq_index; - int ss_phy_irq_index; - bool is_urs; -}; - struct dwc3_qcom { struct device *dev; void __iomem *qscratch_base; struct platform_device *dwc3; - struct platform_device *urs_usb; struct clk **clks; int num_clocks; struct reset_control *resets; @@ -84,8 +71,6 @@ struct dwc3_qcom { struct notifier_block vbus_nb; struct notifier_block host_nb; - const struct dwc3_acpi_pdata *acpi_pdata; - enum usb_dr_mode mode; bool is_suspended; bool pm_suspended; @@ -248,9 +233,6 @@ static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom) struct device *dev = qcom->dev; int ret; - if (has_acpi_companion(dev)) - return 0; - qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr"); if (IS_ERR(qcom->icc_path_ddr)) { return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr), @@ -519,31 +501,13 @@ static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom) PIPE_UTMI_CLK_DIS); } -static int dwc3_qcom_get_irq(struct platform_device *pdev, - const char *name, int num) -{ - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); - struct platform_device *pdev_irq = qcom->urs_usb ? qcom->urs_usb : pdev; - struct device_node *np = pdev->dev.of_node; - int ret; - - if (np) - ret = platform_get_irq_byname_optional(pdev_irq, name); - else - ret = platform_get_irq_optional(pdev_irq, num); - - return ret; -} - static int dwc3_qcom_setup_irq(struct platform_device *pdev) { struct dwc3_qcom *qcom = platform_get_drvdata(pdev); - const struct dwc3_acpi_pdata *pdata = qcom->acpi_pdata; int irq; int ret; - irq = dwc3_qcom_get_irq(pdev, "qusb2_phy", - pdata ? pdata->qusb2_phy_irq_index : -1); + irq = platform_get_irq_byname_optional(pdev, "qusb2_phy"); if (irq > 0) { /* Keep wakeup interrupts disabled until suspend */ ret = devm_request_threaded_irq(qcom->dev, irq, NULL, @@ -557,8 +521,7 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev) qcom->qusb2_phy_irq = irq; } - irq = dwc3_qcom_get_irq(pdev, "dp_hs_phy_irq", - pdata ? pdata->dp_hs_phy_irq_index : -1); + irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_irq"); if (irq > 0) { ret = devm_request_threaded_irq(qcom->dev, irq, NULL, qcom_dwc3_resume_irq, @@ -571,8 +534,7 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev) qcom->dp_hs_phy_irq = irq; } - irq = dwc3_qcom_get_irq(pdev, "dm_hs_phy_irq", - pdata ? pdata->dm_hs_phy_irq_index : -1); + irq = platform_get_irq_byname_optional(pdev, "dm_hs_phy_irq"); if (irq > 0) { ret = devm_request_threaded_irq(qcom->dev, irq, NULL, qcom_dwc3_resume_irq, @@ -585,8 +547,7 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev) qcom->dm_hs_phy_irq = irq; } - irq = dwc3_qcom_get_irq(pdev, "ss_phy_irq", - pdata ? pdata->ss_phy_irq_index : -1); + irq = platform_get_irq_byname_optional(pdev, "ss_phy_irq"); if (irq > 0) { ret = devm_request_threaded_irq(qcom->dev, irq, NULL, qcom_dwc3_resume_irq, @@ -649,88 +610,6 @@ static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count) return 0; } -static const struct property_entry dwc3_qcom_acpi_properties[] = { - PROPERTY_ENTRY_STRING("dr_mode", "host"), - {} -}; - -static const struct software_node dwc3_qcom_swnode = { - .properties = dwc3_qcom_acpi_properties, -}; - -static int dwc3_qcom_acpi_register_core(struct platform_device *pdev) -{ - struct dwc3_qcom *qcom = platform_get_drvdata(pdev); - struct device *dev = &pdev->dev; - struct resource *res, *child_res = NULL; - struct platform_device *pdev_irq = qcom->urs_usb ? qcom->urs_usb : - pdev; - int irq; - int ret; - - qcom->dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO); - if (!qcom->dwc3) - return -ENOMEM; - - qcom->dwc3->dev.parent = dev; - qcom->dwc3->dev.type = dev->type; - qcom->dwc3->dev.dma_mask = dev->dma_mask; - qcom->dwc3->dev.dma_parms = dev->dma_parms; - qcom->dwc3->dev.coherent_dma_mask = dev->coherent_dma_mask; - - child_res = kcalloc(2, sizeof(*child_res), GFP_KERNEL); - if (!child_res) { - platform_device_put(qcom->dwc3); - return -ENOMEM; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "failed to get memory resource\n"); - ret = -ENODEV; - goto out; - } - - child_res[0].flags = res->flags; - child_res[0].start = res->start; - child_res[0].end = child_res[0].start + - qcom->acpi_pdata->dwc3_core_base_size; - - irq = platform_get_irq(pdev_irq, 0); - if (irq < 0) { - ret = irq; - goto out; - } - child_res[1].flags = IORESOURCE_IRQ; - child_res[1].start = child_res[1].end = irq; - - ret = platform_device_add_resources(qcom->dwc3, child_res, 2); - if (ret) { - dev_err(&pdev->dev, "failed to add resources\n"); - goto out; - } - - ret = device_add_software_node(&qcom->dwc3->dev, &dwc3_qcom_swnode); - if (ret < 0) { - dev_err(&pdev->dev, "failed to add properties\n"); - goto out; - } - - ret = platform_device_add(qcom->dwc3); - if (ret) { - dev_err(&pdev->dev, "failed to add device\n"); - device_remove_software_node(&qcom->dwc3->dev); - goto out; - } - kfree(child_res); - return 0; - -out: - platform_device_put(qcom->dwc3); - kfree(child_res); - return ret; -} - static int dwc3_qcom_of_register_core(struct platform_device *pdev) { struct dwc3_qcom *qcom = platform_get_drvdata(pdev); @@ -763,57 +642,12 @@ node_put: return ret; } -static struct platform_device *dwc3_qcom_create_urs_usb_platdev(struct device *dev) -{ - struct platform_device *urs_usb = NULL; - struct fwnode_handle *fwh; - struct acpi_device *adev; - char name[8]; - int ret; - int id; - - /* Figure out device id */ - ret = sscanf(fwnode_get_name(dev->fwnode), "URS%d", &id); - if (!ret) - return NULL; - - /* Find the child using name */ - snprintf(name, sizeof(name), "USB%d", id); - fwh = fwnode_get_named_child_node(dev->fwnode, name); - if (!fwh) - return NULL; - - adev = to_acpi_device_node(fwh); - if (!adev) - goto err_put_handle; - - urs_usb = acpi_create_platform_device(adev, NULL); - if (IS_ERR_OR_NULL(urs_usb)) - goto err_put_handle; - - return urs_usb; - -err_put_handle: - fwnode_handle_put(fwh); - - return urs_usb; -} - -static void dwc3_qcom_destroy_urs_usb_platdev(struct platform_device *urs_usb) -{ - struct fwnode_handle *fwh = urs_usb->dev.fwnode; - - platform_device_unregister(urs_usb); - fwnode_handle_put(fwh); -} - static int dwc3_qcom_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct dwc3_qcom *qcom; - struct resource *res, *parent_res = NULL; - struct resource local_res; + struct resource *res; int ret, i; bool ignore_pipe_clk; bool wakeup_source; @@ -825,14 +659,6 @@ static int dwc3_qcom_probe(struct platform_device *pdev) platform_set_drvdata(pdev, qcom); qcom->dev = &pdev->dev; - if (has_acpi_companion(dev)) { - qcom->acpi_pdata = acpi_device_get_match_data(dev); - if (!qcom->acpi_pdata) { - dev_err(&pdev->dev, "no supporting ACPI device data\n"); - return -EINVAL; - } - } - qcom->resets = devm_reset_control_array_get_optional_exclusive(dev); if (IS_ERR(qcom->resets)) { return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets), @@ -861,40 +687,16 @@ static int dwc3_qcom_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (np) { - parent_res = res; - } else { - memcpy(&local_res, res, sizeof(struct resource)); - parent_res = &local_res; - - parent_res->start = res->start + - qcom->acpi_pdata->qscratch_base_offset; - parent_res->end = parent_res->start + - qcom->acpi_pdata->qscratch_base_size; - - if (qcom->acpi_pdata->is_urs) { - qcom->urs_usb = dwc3_qcom_create_urs_usb_platdev(dev); - if (IS_ERR_OR_NULL(qcom->urs_usb)) { - dev_err(dev, "failed to create URS USB platdev\n"); - if (!qcom->urs_usb) - ret = -ENODEV; - else - ret = PTR_ERR(qcom->urs_usb); - goto clk_disable; - } - } - } - - qcom->qscratch_base = devm_ioremap_resource(dev, parent_res); + qcom->qscratch_base = devm_ioremap_resource(dev, res); if (IS_ERR(qcom->qscratch_base)) { ret = PTR_ERR(qcom->qscratch_base); - goto free_urs; + goto clk_disable; } ret = dwc3_qcom_setup_irq(pdev); if (ret) { dev_err(dev, "failed to setup IRQs, err=%d\n", ret); - goto free_urs; + goto clk_disable; } /* @@ -906,14 +708,10 @@ static int dwc3_qcom_probe(struct platform_device *pdev) if (ignore_pipe_clk) dwc3_qcom_select_utmi_clk(qcom); - if (np) - ret = dwc3_qcom_of_register_core(pdev); - else - ret = dwc3_qcom_acpi_register_core(pdev); - + ret = dwc3_qcom_of_register_core(pdev); if (ret) { dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret); - goto free_urs; + goto clk_disable; } ret = dwc3_qcom_interconnect_init(qcom); @@ -945,16 +743,8 @@ static int dwc3_qcom_probe(struct platform_device *pdev) interconnect_exit: dwc3_qcom_interconnect_exit(qcom); depopulate: - if (np) { - of_platform_depopulate(&pdev->dev); - } else { - device_remove_software_node(&qcom->dwc3->dev); - platform_device_del(qcom->dwc3); - } + of_platform_depopulate(&pdev->dev); platform_device_put(qcom->dwc3); -free_urs: - if (qcom->urs_usb) - dwc3_qcom_destroy_urs_usb_platdev(qcom->urs_usb); clk_disable: for (i = qcom->num_clocks - 1; i >= 0; i--) { clk_disable_unprepare(qcom->clks[i]); @@ -969,21 +759,12 @@ reset_assert: static void dwc3_qcom_remove(struct platform_device *pdev) { struct dwc3_qcom *qcom = platform_get_drvdata(pdev); - struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; int i; - if (np) { - of_platform_depopulate(&pdev->dev); - } else { - device_remove_software_node(&qcom->dwc3->dev); - platform_device_del(qcom->dwc3); - } + of_platform_depopulate(&pdev->dev); platform_device_put(qcom->dwc3); - if (qcom->urs_usb) - dwc3_qcom_destroy_urs_usb_platdev(qcom->urs_usb); - for (i = qcom->num_clocks - 1; i >= 0; i--) { clk_disable_unprepare(qcom->clks[i]); clk_put(qcom->clks[i]); @@ -1053,38 +834,6 @@ static const struct of_device_id dwc3_qcom_of_match[] = { }; MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match); -#ifdef CONFIG_ACPI -static const struct dwc3_acpi_pdata sdm845_acpi_pdata = { - .qscratch_base_offset = SDM845_QSCRATCH_BASE_OFFSET, - .qscratch_base_size = SDM845_QSCRATCH_SIZE, - .dwc3_core_base_size = SDM845_DWC3_CORE_SIZE, - .qusb2_phy_irq_index = 1, - .dp_hs_phy_irq_index = 4, - .dm_hs_phy_irq_index = 3, - .ss_phy_irq_index = 2 -}; - -static const struct dwc3_acpi_pdata sdm845_acpi_urs_pdata = { - .qscratch_base_offset = SDM845_QSCRATCH_BASE_OFFSET, - .qscratch_base_size = SDM845_QSCRATCH_SIZE, - .dwc3_core_base_size = SDM845_DWC3_CORE_SIZE, - .qusb2_phy_irq_index = 1, - .dp_hs_phy_irq_index = 4, - .dm_hs_phy_irq_index = 3, - .ss_phy_irq_index = 2, - .is_urs = true, -}; - -static const struct acpi_device_id dwc3_qcom_acpi_match[] = { - { "QCOM2430", (unsigned long)&sdm845_acpi_pdata }, - { "QCOM0304", (unsigned long)&sdm845_acpi_urs_pdata }, - { "QCOM0497", (unsigned long)&sdm845_acpi_urs_pdata }, - { "QCOM04A6", (unsigned long)&sdm845_acpi_pdata }, - { }, -}; -MODULE_DEVICE_TABLE(acpi, dwc3_qcom_acpi_match); -#endif - static struct platform_driver dwc3_qcom_driver = { .probe = dwc3_qcom_probe, .remove_new = dwc3_qcom_remove, @@ -1092,7 +841,6 @@ static struct platform_driver dwc3_qcom_driver = { .name = "dwc3-qcom", .pm = &dwc3_qcom_dev_pm_ops, .of_match_table = dwc3_qcom_of_match, - .acpi_match_table = ACPI_PTR(dwc3_qcom_acpi_match), }, }; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 6ae8a36f21..d96ffbe520 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -226,7 +226,8 @@ void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) /* reinitialize physical ep1 */ dep = dwc->eps[1]; - dep->flags = DWC3_EP_ENABLED; + dep->flags &= DWC3_EP_RESOURCE_ALLOCATED; + dep->flags |= DWC3_EP_ENABLED; /* stall is always issued on EP0 */ dep = dwc->eps[0]; @@ -646,6 +647,7 @@ static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) return -EINVAL; case USB_STATE_ADDRESS: + dwc3_gadget_start_config(dwc, 2); dwc3_gadget_clear_tx_fifos(dwc); ret = dwc3_ep0_delegate_req(dwc, ctrl); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 579d90efc2..89fc690fdf 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -519,77 +519,56 @@ static void dwc3_free_trb_pool(struct dwc3_ep *dep) static int dwc3_gadget_set_xfer_resource(struct dwc3_ep *dep) { struct dwc3_gadget_ep_cmd_params params; + int ret; + + if (dep->flags & DWC3_EP_RESOURCE_ALLOCATED) + return 0; memset(¶ms, 0x00, sizeof(params)); params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1); - return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETTRANSFRESOURCE, + ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETTRANSFRESOURCE, ¶ms); + if (ret) + return ret; + + dep->flags |= DWC3_EP_RESOURCE_ALLOCATED; + return 0; } /** - * dwc3_gadget_start_config - configure ep resources - * @dep: endpoint that is being enabled - * - * Issue a %DWC3_DEPCMD_DEPSTARTCFG command to @dep. After the command's - * completion, it will set Transfer Resource for all available endpoints. - * - * The assignment of transfer resources cannot perfectly follow the data book - * due to the fact that the controller driver does not have all knowledge of the - * configuration in advance. It is given this information piecemeal by the - * composite gadget framework after every SET_CONFIGURATION and - * SET_INTERFACE. Trying to follow the databook programming model in this - * scenario can cause errors. For two reasons: - * - * 1) The databook says to do %DWC3_DEPCMD_DEPSTARTCFG for every - * %USB_REQ_SET_CONFIGURATION and %USB_REQ_SET_INTERFACE (8.1.5). This is - * incorrect in the scenario of multiple interfaces. - * - * 2) The databook does not mention doing more %DWC3_DEPCMD_DEPXFERCFG for new - * endpoint on alt setting (8.1.6). - * - * The following simplified method is used instead: + * dwc3_gadget_start_config - reset endpoint resources + * @dwc: pointer to the DWC3 context + * @resource_index: DEPSTARTCFG.XferRscIdx value (must be 0 or 2) * - * All hardware endpoints can be assigned a transfer resource and this setting - * will stay persistent until either a core reset or hibernation. So whenever we - * do a %DWC3_DEPCMD_DEPSTARTCFG(0) we can go ahead and do - * %DWC3_DEPCMD_DEPXFERCFG for every hardware endpoint as well. We are - * guaranteed that there are as many transfer resources as endpoints. + * Set resource_index=0 to reset all endpoints' resources allocation. Do this as + * part of the power-on/soft-reset initialization. * - * This function is called for each endpoint when it is being enabled but is - * triggered only when called for EP0-out, which always happens first, and which - * should only happen in one of the above conditions. + * Set resource_index=2 to reset only non-control endpoints' resources. Do this + * on receiving the SET_CONFIGURATION request or hibernation resume. */ -static int dwc3_gadget_start_config(struct dwc3_ep *dep) +int dwc3_gadget_start_config(struct dwc3 *dwc, unsigned int resource_index) { struct dwc3_gadget_ep_cmd_params params; - struct dwc3 *dwc; u32 cmd; int i; int ret; - if (dep->number) - return 0; + if (resource_index != 0 && resource_index != 2) + return -EINVAL; memset(¶ms, 0x00, sizeof(params)); cmd = DWC3_DEPCMD_DEPSTARTCFG; - dwc = dep->dwc; + cmd |= DWC3_DEPCMD_PARAM(resource_index); - ret = dwc3_send_gadget_ep_cmd(dep, cmd, ¶ms); + ret = dwc3_send_gadget_ep_cmd(dwc->eps[0], cmd, ¶ms); if (ret) return ret; - for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { - struct dwc3_ep *dep = dwc->eps[i]; - - if (!dep) - continue; - - ret = dwc3_gadget_set_xfer_resource(dep); - if (ret) - return ret; - } + /* Reset resource allocation flags */ + for (i = resource_index; i < dwc->num_eps && dwc->eps[i]; i++) + dwc->eps[i]->flags &= ~DWC3_EP_RESOURCE_ALLOCATED; return 0; } @@ -884,16 +863,18 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action) ret = dwc3_gadget_resize_tx_fifos(dep); if (ret) return ret; - - ret = dwc3_gadget_start_config(dep); - if (ret) - return ret; } ret = dwc3_gadget_set_ep_config(dep, action); if (ret) return ret; + if (!(dep->flags & DWC3_EP_RESOURCE_ALLOCATED)) { + ret = dwc3_gadget_set_xfer_resource(dep); + if (ret) + return ret; + } + if (!(dep->flags & DWC3_EP_ENABLED)) { struct dwc3_trb *trb_st_hw; struct dwc3_trb *trb_link; @@ -1047,7 +1028,7 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep) dep->stream_capable = false; dep->type = 0; - mask = DWC3_EP_TXFIFO_RESIZED; + mask = DWC3_EP_TXFIFO_RESIZED | DWC3_EP_RESOURCE_ALLOCATED; /* * dwc3_remove_requests() can exit early if DWC3 EP delayed stop is * set. Do not clear DEP flags, so that the end transfer command will @@ -2911,6 +2892,12 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) /* Start with SuperSpeed Default */ dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + ret = dwc3_gadget_start_config(dwc, 0); + if (ret) { + dev_err(dwc->dev, "failed to config endpoints\n"); + return ret; + } + dep = dwc->eps[0]; dep->flags = 0; ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); @@ -3433,7 +3420,7 @@ static int dwc3_gadget_ep_reclaim_trb_sg(struct dwc3_ep *dep, struct dwc3_request *req, const struct dwc3_event_depevt *event, int status) { - struct dwc3_trb *trb = &dep->trb_pool[dep->trb_dequeue]; + struct dwc3_trb *trb; struct scatterlist *sg = req->sg; struct scatterlist *s; unsigned int num_queued = req->num_queued_sgs; diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index 55a56cf67d..d73e735e40 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -119,6 +119,7 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol); void dwc3_ep0_send_delayed_status(struct dwc3 *dwc); void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt); +int dwc3_gadget_start_config(struct dwc3 *dwc, unsigned int resource_index); /** * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 6c143f7d24..a171b27a78 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -13,9 +13,53 @@ #include <linux/usb.h> #include <linux/usb/hcd.h> +#include "../host/xhci-port.h" +#include "../host/xhci-ext-caps.h" +#include "../host/xhci-caps.h" #include "../host/xhci-plat.h" #include "core.h" +#define XHCI_HCSPARAMS1 0x4 +#define XHCI_PORTSC_BASE 0x400 + +/** + * dwc3_power_off_all_roothub_ports - Power off all Root hub ports + * @dwc: Pointer to our controller context structure + */ +static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) +{ + void __iomem *xhci_regs; + u32 op_regs_base; + int port_num; + u32 offset; + u32 reg; + int i; + + /* xhci regs is not mapped yet, do it temperary here */ + if (dwc->xhci_resources[0].start) { + xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END); + if (!xhci_regs) { + dev_err(dwc->dev, "Failed to ioremap xhci_regs\n"); + return; + } + + op_regs_base = HC_LENGTH(readl(xhci_regs)); + reg = readl(xhci_regs + XHCI_HCSPARAMS1); + port_num = HCS_MAX_PORTS(reg); + + for (i = 1; i <= port_num; i++) { + offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1); + reg = readl(xhci_regs + offset); + reg &= ~PORT_POWER; + writel(reg, xhci_regs + offset); + } + + iounmap(xhci_regs); + } else { + dev_err(dwc->dev, "xhci base reg invalid\n"); + } +} + static void dwc3_xhci_plat_start(struct usb_hcd *hcd) { struct platform_device *pdev; @@ -87,6 +131,12 @@ int dwc3_host_init(struct dwc3 *dwc) int ret, irq; int prop_idx = 0; + /* + * Some platforms need to power off all Root hub ports immediately after DWC3 set to host + * mode to avoid VBUS glitch happen when xhci get reset later. + */ + dwc3_power_off_all_roothub_ports(dwc); + irq = dwc3_host_get_irq(dwc); if (irq < 0) return irq; diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index b3592bcb0f..566ff0b128 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -190,6 +190,7 @@ config USB_F_MASS_STORAGE tristate config USB_F_FS + select DMA_SHARED_BUFFER tristate config USB_F_UAC1 diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 839426d92b..a057cbedf3 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -15,6 +15,9 @@ /* #define VERBOSE_DEBUG */ #include <linux/blkdev.h> +#include <linux/dma-buf.h> +#include <linux/dma-fence.h> +#include <linux/dma-resv.h> #include <linux/pagemap.h> #include <linux/export.h> #include <linux/fs_parser.h> @@ -43,6 +46,10 @@ #define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */ +#define DMABUF_ENQUEUE_TIMEOUT_MS 5000 + +MODULE_IMPORT_NS(DMA_BUF); + /* Reference counter handling */ static void ffs_data_get(struct ffs_data *ffs); static void ffs_data_put(struct ffs_data *ffs); @@ -124,6 +131,25 @@ struct ffs_ep { u8 num; }; +struct ffs_dmabuf_priv { + struct list_head entry; + struct kref ref; + struct ffs_data *ffs; + struct dma_buf_attachment *attach; + struct sg_table *sgt; + enum dma_data_direction dir; + spinlock_t lock; + u64 context; + struct usb_request *req; /* P: ffs->eps_lock */ + struct usb_ep *ep; /* P: ffs->eps_lock */ +}; + +struct ffs_dma_fence { + struct dma_fence base; + struct ffs_dmabuf_priv *priv; + struct work_struct work; +}; + struct ffs_epfile { /* Protects ep->ep and ep->req. */ struct mutex mutex; @@ -197,6 +223,11 @@ struct ffs_epfile { unsigned char isoc; /* P: ffs->eps_lock */ unsigned char _pad; + + /* Protects dmabufs */ + struct mutex dmabufs_mutex; + struct list_head dmabufs; /* P: dmabufs_mutex */ + atomic_t seqno; }; struct ffs_buffer { @@ -939,31 +970,44 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, return ret; } -static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) +static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) { struct ffs_epfile *epfile = file->private_data; - struct usb_request *req; struct ffs_ep *ep; - char *data = NULL; - ssize_t ret, data_len = -EINVAL; - int halt; - - /* Are we still active? */ - if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) - return -ENODEV; + int ret; /* Wait for endpoint to be enabled */ ep = epfile->ep; if (!ep) { if (file->f_flags & O_NONBLOCK) - return -EAGAIN; + return ERR_PTR(-EAGAIN); ret = wait_event_interruptible( epfile->ffs->wait, (ep = epfile->ep)); if (ret) - return -EINTR; + return ERR_PTR(-EINTR); } + return ep; +} + +static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) +{ + struct ffs_epfile *epfile = file->private_data; + struct usb_request *req; + struct ffs_ep *ep; + char *data = NULL; + ssize_t ret, data_len = -EINVAL; + int halt; + + /* Are we still active? */ + if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) + return -ENODEV; + + ep = ffs_epfile_wait_ep(file); + if (IS_ERR(ep)) + return PTR_ERR(ep); + /* Do we halt? */ halt = (!io_data->read == !epfile->in); if (halt && epfile->isoc) @@ -1263,10 +1307,58 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) return res; } +static void ffs_dmabuf_release(struct kref *ref) +{ + struct ffs_dmabuf_priv *priv = container_of(ref, struct ffs_dmabuf_priv, ref); + struct dma_buf_attachment *attach = priv->attach; + struct dma_buf *dmabuf = attach->dmabuf; + + pr_vdebug("FFS DMABUF release\n"); + dma_resv_lock(dmabuf->resv, NULL); + dma_buf_unmap_attachment(attach, priv->sgt, priv->dir); + dma_resv_unlock(dmabuf->resv); + + dma_buf_detach(attach->dmabuf, attach); + dma_buf_put(dmabuf); + kfree(priv); +} + +static void ffs_dmabuf_get(struct dma_buf_attachment *attach) +{ + struct ffs_dmabuf_priv *priv = attach->importer_priv; + + kref_get(&priv->ref); +} + +static void ffs_dmabuf_put(struct dma_buf_attachment *attach) +{ + struct ffs_dmabuf_priv *priv = attach->importer_priv; + + kref_put(&priv->ref, ffs_dmabuf_release); +} + static int ffs_epfile_release(struct inode *inode, struct file *file) { struct ffs_epfile *epfile = inode->i_private; + struct ffs_dmabuf_priv *priv, *tmp; + struct ffs_data *ffs = epfile->ffs; + + mutex_lock(&epfile->dmabufs_mutex); + + /* Close all attached DMABUFs */ + list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) { + /* Cancel any pending transfer */ + spin_lock_irq(&ffs->eps_lock); + if (priv->ep && priv->req) + usb_ep_dequeue(priv->ep, priv->req); + spin_unlock_irq(&ffs->eps_lock); + + list_del(&priv->entry); + ffs_dmabuf_put(priv->attach); + } + + mutex_unlock(&epfile->dmabufs_mutex); __ffs_epfile_read_buffer_free(epfile); ffs_data_closed(epfile->ffs); @@ -1274,6 +1366,357 @@ ffs_epfile_release(struct inode *inode, struct file *file) return 0; } +static void ffs_dmabuf_cleanup(struct work_struct *work) +{ + struct ffs_dma_fence *dma_fence = + container_of(work, struct ffs_dma_fence, work); + struct ffs_dmabuf_priv *priv = dma_fence->priv; + struct dma_buf_attachment *attach = priv->attach; + struct dma_fence *fence = &dma_fence->base; + + ffs_dmabuf_put(attach); + dma_fence_put(fence); +} + +static void ffs_dmabuf_signal_done(struct ffs_dma_fence *dma_fence, int ret) +{ + struct ffs_dmabuf_priv *priv = dma_fence->priv; + struct dma_fence *fence = &dma_fence->base; + bool cookie = dma_fence_begin_signalling(); + + dma_fence_get(fence); + fence->error = ret; + dma_fence_signal(fence); + dma_fence_end_signalling(cookie); + + /* + * The fence will be unref'd in ffs_dmabuf_cleanup. + * It can't be done here, as the unref functions might try to lock + * the resv object, which would deadlock. + */ + INIT_WORK(&dma_fence->work, ffs_dmabuf_cleanup); + queue_work(priv->ffs->io_completion_wq, &dma_fence->work); +} + +static void ffs_epfile_dmabuf_io_complete(struct usb_ep *ep, + struct usb_request *req) +{ + pr_vdebug("FFS: DMABUF transfer complete, status=%d\n", req->status); + ffs_dmabuf_signal_done(req->context, req->status); + usb_ep_free_request(ep, req); +} + +static const char *ffs_dmabuf_get_driver_name(struct dma_fence *fence) +{ + return "functionfs"; +} + +static const char *ffs_dmabuf_get_timeline_name(struct dma_fence *fence) +{ + return ""; +} + +static void ffs_dmabuf_fence_release(struct dma_fence *fence) +{ + struct ffs_dma_fence *dma_fence = + container_of(fence, struct ffs_dma_fence, base); + + kfree(dma_fence); +} + +static const struct dma_fence_ops ffs_dmabuf_fence_ops = { + .get_driver_name = ffs_dmabuf_get_driver_name, + .get_timeline_name = ffs_dmabuf_get_timeline_name, + .release = ffs_dmabuf_fence_release, +}; + +static int ffs_dma_resv_lock(struct dma_buf *dmabuf, bool nonblock) +{ + if (!nonblock) + return dma_resv_lock_interruptible(dmabuf->resv, NULL); + + if (!dma_resv_trylock(dmabuf->resv)) + return -EBUSY; + + return 0; +} + +static struct dma_buf_attachment * +ffs_dmabuf_find_attachment(struct ffs_epfile *epfile, struct dma_buf *dmabuf) +{ + struct device *dev = epfile->ffs->gadget->dev.parent; + struct dma_buf_attachment *attach = NULL; + struct ffs_dmabuf_priv *priv; + + mutex_lock(&epfile->dmabufs_mutex); + + list_for_each_entry(priv, &epfile->dmabufs, entry) { + if (priv->attach->dev == dev + && priv->attach->dmabuf == dmabuf) { + attach = priv->attach; + break; + } + } + + if (attach) + ffs_dmabuf_get(attach); + + mutex_unlock(&epfile->dmabufs_mutex); + + return attach ?: ERR_PTR(-EPERM); +} + +static int ffs_dmabuf_attach(struct file *file, int fd) +{ + bool nonblock = file->f_flags & O_NONBLOCK; + struct ffs_epfile *epfile = file->private_data; + struct usb_gadget *gadget = epfile->ffs->gadget; + struct dma_buf_attachment *attach; + struct ffs_dmabuf_priv *priv; + enum dma_data_direction dir; + struct sg_table *sg_table; + struct dma_buf *dmabuf; + int err; + + if (!gadget || !gadget->sg_supported) + return -EPERM; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + attach = dma_buf_attach(dmabuf, gadget->dev.parent); + if (IS_ERR(attach)) { + err = PTR_ERR(attach); + goto err_dmabuf_put; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + err = -ENOMEM; + goto err_dmabuf_detach; + } + + dir = epfile->in ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + err = ffs_dma_resv_lock(dmabuf, nonblock); + if (err) + goto err_free_priv; + + sg_table = dma_buf_map_attachment(attach, dir); + dma_resv_unlock(dmabuf->resv); + + if (IS_ERR(sg_table)) { + err = PTR_ERR(sg_table); + goto err_free_priv; + } + + attach->importer_priv = priv; + + priv->sgt = sg_table; + priv->dir = dir; + priv->ffs = epfile->ffs; + priv->attach = attach; + spin_lock_init(&priv->lock); + kref_init(&priv->ref); + priv->context = dma_fence_context_alloc(1); + + mutex_lock(&epfile->dmabufs_mutex); + list_add(&priv->entry, &epfile->dmabufs); + mutex_unlock(&epfile->dmabufs_mutex); + + return 0; + +err_free_priv: + kfree(priv); +err_dmabuf_detach: + dma_buf_detach(dmabuf, attach); +err_dmabuf_put: + dma_buf_put(dmabuf); + + return err; +} + +static int ffs_dmabuf_detach(struct file *file, int fd) +{ + struct ffs_epfile *epfile = file->private_data; + struct ffs_data *ffs = epfile->ffs; + struct device *dev = ffs->gadget->dev.parent; + struct ffs_dmabuf_priv *priv, *tmp; + struct dma_buf *dmabuf; + int ret = -EPERM; + + dmabuf = dma_buf_get(fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + mutex_lock(&epfile->dmabufs_mutex); + + list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) { + if (priv->attach->dev == dev + && priv->attach->dmabuf == dmabuf) { + /* Cancel any pending transfer */ + spin_lock_irq(&ffs->eps_lock); + if (priv->ep && priv->req) + usb_ep_dequeue(priv->ep, priv->req); + spin_unlock_irq(&ffs->eps_lock); + + list_del(&priv->entry); + + /* Unref the reference from ffs_dmabuf_attach() */ + ffs_dmabuf_put(priv->attach); + ret = 0; + break; + } + } + + mutex_unlock(&epfile->dmabufs_mutex); + dma_buf_put(dmabuf); + + return ret; +} + +static int ffs_dmabuf_transfer(struct file *file, + const struct usb_ffs_dmabuf_transfer_req *req) +{ + bool nonblock = file->f_flags & O_NONBLOCK; + struct ffs_epfile *epfile = file->private_data; + struct dma_buf_attachment *attach; + struct ffs_dmabuf_priv *priv; + struct ffs_dma_fence *fence; + struct usb_request *usb_req; + enum dma_resv_usage resv_dir; + struct dma_buf *dmabuf; + unsigned long timeout; + struct ffs_ep *ep; + bool cookie; + u32 seqno; + long retl; + int ret; + + if (req->flags & ~USB_FFS_DMABUF_TRANSFER_MASK) + return -EINVAL; + + dmabuf = dma_buf_get(req->fd); + if (IS_ERR(dmabuf)) + return PTR_ERR(dmabuf); + + if (req->length > dmabuf->size || req->length == 0) { + ret = -EINVAL; + goto err_dmabuf_put; + } + + attach = ffs_dmabuf_find_attachment(epfile, dmabuf); + if (IS_ERR(attach)) { + ret = PTR_ERR(attach); + goto err_dmabuf_put; + } + + priv = attach->importer_priv; + + ep = ffs_epfile_wait_ep(file); + if (IS_ERR(ep)) { + ret = PTR_ERR(ep); + goto err_attachment_put; + } + + ret = ffs_dma_resv_lock(dmabuf, nonblock); + if (ret) + goto err_attachment_put; + + /* Make sure we don't have writers */ + timeout = nonblock ? 0 : msecs_to_jiffies(DMABUF_ENQUEUE_TIMEOUT_MS); + retl = dma_resv_wait_timeout(dmabuf->resv, + dma_resv_usage_rw(epfile->in), + true, timeout); + if (retl == 0) + retl = -EBUSY; + if (retl < 0) { + ret = (int)retl; + goto err_resv_unlock; + } + + ret = dma_resv_reserve_fences(dmabuf->resv, 1); + if (ret) + goto err_resv_unlock; + + fence = kmalloc(sizeof(*fence), GFP_KERNEL); + if (!fence) { + ret = -ENOMEM; + goto err_resv_unlock; + } + + fence->priv = priv; + + spin_lock_irq(&epfile->ffs->eps_lock); + + /* In the meantime, endpoint got disabled or changed. */ + if (epfile->ep != ep) { + ret = -ESHUTDOWN; + goto err_fence_put; + } + + usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC); + if (!usb_req) { + ret = -ENOMEM; + goto err_fence_put; + } + + /* + * usb_ep_queue() guarantees that all transfers are processed in the + * order they are enqueued, so we can use a simple incrementing + * sequence number for the dma_fence. + */ + seqno = atomic_add_return(1, &epfile->seqno); + + dma_fence_init(&fence->base, &ffs_dmabuf_fence_ops, + &priv->lock, priv->context, seqno); + + resv_dir = epfile->in ? DMA_RESV_USAGE_WRITE : DMA_RESV_USAGE_READ; + + dma_resv_add_fence(dmabuf->resv, &fence->base, resv_dir); + dma_resv_unlock(dmabuf->resv); + + /* Now that the dma_fence is in place, queue the transfer. */ + + usb_req->length = req->length; + usb_req->buf = NULL; + usb_req->sg = priv->sgt->sgl; + usb_req->num_sgs = sg_nents_for_len(priv->sgt->sgl, req->length); + usb_req->sg_was_mapped = true; + usb_req->context = fence; + usb_req->complete = ffs_epfile_dmabuf_io_complete; + + cookie = dma_fence_begin_signalling(); + ret = usb_ep_queue(ep->ep, usb_req, GFP_ATOMIC); + dma_fence_end_signalling(cookie); + if (!ret) { + priv->req = usb_req; + priv->ep = ep->ep; + } else { + pr_warn("FFS: Failed to queue DMABUF: %d\n", ret); + ffs_dmabuf_signal_done(fence, ret); + usb_ep_free_request(ep->ep, usb_req); + } + + spin_unlock_irq(&epfile->ffs->eps_lock); + dma_buf_put(dmabuf); + + return ret; + +err_fence_put: + spin_unlock_irq(&epfile->ffs->eps_lock); + dma_fence_put(&fence->base); +err_resv_unlock: + dma_resv_unlock(dmabuf->resv); +err_attachment_put: + ffs_dmabuf_put(attach); +err_dmabuf_put: + dma_buf_put(dmabuf); + + return ret; +} + static long ffs_epfile_ioctl(struct file *file, unsigned code, unsigned long value) { @@ -1284,17 +1727,48 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; - /* Wait for endpoint to be enabled */ - ep = epfile->ep; - if (!ep) { - if (file->f_flags & O_NONBLOCK) - return -EAGAIN; + switch (code) { + case FUNCTIONFS_DMABUF_ATTACH: + { + int fd; - ret = wait_event_interruptible( - epfile->ffs->wait, (ep = epfile->ep)); - if (ret) - return -EINTR; + if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) { + ret = -EFAULT; + break; + } + + return ffs_dmabuf_attach(file, fd); } + case FUNCTIONFS_DMABUF_DETACH: + { + int fd; + + if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) { + ret = -EFAULT; + break; + } + + return ffs_dmabuf_detach(file, fd); + } + case FUNCTIONFS_DMABUF_TRANSFER: + { + struct usb_ffs_dmabuf_transfer_req req; + + if (copy_from_user(&req, (void __user *)value, sizeof(req))) { + ret = -EFAULT; + break; + } + + return ffs_dmabuf_transfer(file, &req); + } + default: + break; + } + + /* Wait for endpoint to be enabled */ + ep = ffs_epfile_wait_ep(file); + if (IS_ERR(ep)) + return PTR_ERR(ep); spin_lock_irq(&epfile->ffs->eps_lock); @@ -1868,6 +2342,8 @@ static int ffs_epfiles_create(struct ffs_data *ffs) for (i = 1; i <= count; ++i, ++epfile) { epfile->ffs = ffs; mutex_init(&epfile->mutex); + mutex_init(&epfile->dmabufs_mutex); + INIT_LIST_HEAD(&epfile->dmabufs); if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); else @@ -3450,6 +3926,25 @@ static inline struct f_fs_opts *to_ffs_opts(struct config_item *item) func_inst.group); } +static ssize_t f_fs_opts_ready_show(struct config_item *item, char *page) +{ + struct f_fs_opts *opts = to_ffs_opts(item); + int ready; + + ffs_dev_lock(); + ready = opts->dev->desc_ready; + ffs_dev_unlock(); + + return sprintf(page, "%d\n", ready); +} + +CONFIGFS_ATTR_RO(f_fs_opts_, ready); + +static struct configfs_attribute *ffs_attrs[] = { + &f_fs_opts_attr_ready, + NULL, +}; + static void ffs_attr_release(struct config_item *item) { struct f_fs_opts *opts = to_ffs_opts(item); @@ -3463,6 +3958,7 @@ static struct configfs_item_operations ffs_item_ops = { static const struct config_item_type ffs_func_type = { .ct_item_ops = &ffs_item_ops, + .ct_attrs = ffs_attrs, .ct_owner = THIS_MODULE, }; diff --git a/drivers/usb/gadget/function/u_ether.c b/drivers/usb/gadget/function/u_ether.c index 3c5a6f6ac3..444212c0b5 100644 --- a/drivers/usb/gadget/function/u_ether.c +++ b/drivers/usb/gadget/function/u_ether.c @@ -718,7 +718,7 @@ static const struct net_device_ops eth_netdev_ops = { .ndo_validate_addr = eth_validate_addr, }; -static struct device_type gadget_type = { +static const struct device_type gadget_type = { .name = "gadget", }; diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 53e4cd81ea..d41f5f31da 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -373,6 +373,7 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) struct uvc_video *video = ureq->video; struct uvc_video_queue *queue = &video->queue; struct uvc_buffer *last_buf; + struct usb_request *to_queue = req; unsigned long flags; bool is_bulk = video->max_payload_size; int ret = 0; @@ -400,7 +401,8 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) case -EXDEV: uvcg_dbg(&video->uvc->func, "VS request missed xfer.\n"); - queue->flags |= UVC_QUEUE_DROP_INCOMPLETE; + if (req->length != 0) + queue->flags |= UVC_QUEUE_DROP_INCOMPLETE; break; case -ESHUTDOWN: /* disconnect from host. */ @@ -428,59 +430,59 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) * we're still streaming before queueing the usb_request * back to req_free */ - if (video->is_enabled) { + if (!video->is_enabled) { + uvc_video_free_request(ureq, ep); + spin_unlock_irqrestore(&video->req_lock, flags); + uvcg_queue_cancel(queue, 0); + + return; + } + + /* + * Here we check whether any request is available in the ready + * list. If it is, queue it to the ep and add the current + * usb_request to the req_free list - for video_pump to fill in. + * Otherwise, just use the current usb_request to queue a 0 + * length request to the ep. Since we always add to the req_free + * list if we dequeue from the ready list, there will never + * be a situation where the req_free list is completely out of + * requests and cannot recover. + */ + to_queue->length = 0; + if (!list_empty(&video->req_ready)) { + to_queue = list_first_entry(&video->req_ready, + struct usb_request, list); + list_del(&to_queue->list); + list_add_tail(&req->list, &video->req_free); /* - * Here we check whether any request is available in the ready - * list. If it is, queue it to the ep and add the current - * usb_request to the req_free list - for video_pump to fill in. - * Otherwise, just use the current usb_request to queue a 0 - * length request to the ep. Since we always add to the req_free - * list if we dequeue from the ready list, there will never - * be a situation where the req_free list is completely out of - * requests and cannot recover. + * Queue work to the wq as well since it is possible that a + * buffer may not have been completely encoded with the set of + * in-flight usb requests for whih the complete callbacks are + * firing. + * In that case, if we do not queue work to the worker thread, + * the buffer will never be marked as complete - and therefore + * not be returned to userpsace. As a result, + * dequeue -> queue -> dequeue flow of uvc buffers will not + * happen. */ - struct usb_request *to_queue = req; - - to_queue->length = 0; - if (!list_empty(&video->req_ready)) { - to_queue = list_first_entry(&video->req_ready, - struct usb_request, list); - list_del(&to_queue->list); - list_add_tail(&req->list, &video->req_free); - /* - * Queue work to the wq as well since it is possible that a - * buffer may not have been completely encoded with the set of - * in-flight usb requests for whih the complete callbacks are - * firing. - * In that case, if we do not queue work to the worker thread, - * the buffer will never be marked as complete - and therefore - * not be returned to userpsace. As a result, - * dequeue -> queue -> dequeue flow of uvc buffers will not - * happen. - */ - queue_work(video->async_wq, &video->pump); - } + queue_work(video->async_wq, &video->pump); + } + /* + * Queue to the endpoint. The actual queueing to ep will + * only happen on one thread - the async_wq for bulk endpoints + * and this thread for isoc endpoints. + */ + ret = uvcg_video_usb_req_queue(video, to_queue, !is_bulk); + if (ret < 0) { /* - * Queue to the endpoint. The actual queueing to ep will - * only happen on one thread - the async_wq for bulk endpoints - * and this thread for isoc endpoints. + * Endpoint error, but the stream is still enabled. + * Put request back in req_free for it to be cleaned + * up later. */ - ret = uvcg_video_usb_req_queue(video, to_queue, !is_bulk); - if (ret < 0) { - /* - * Endpoint error, but the stream is still enabled. - * Put request back in req_free for it to be cleaned - * up later. - */ - list_add_tail(&to_queue->list, &video->req_free); - } - } else { - uvc_video_free_request(ureq, ep); - ret = 0; + list_add_tail(&to_queue->list, &video->req_free); } + spin_unlock_irqrestore(&video->req_lock, flags); - if (ret < 0) - uvcg_queue_cancel(queue, 0); } static int @@ -625,14 +627,7 @@ static void uvcg_video_pump(struct work_struct *work) uvcg_queue_cancel(queue, 0); break; } - - /* The request is owned by the endpoint / ready list. */ - req = NULL; } - - if (!req) - return; - spin_lock_irqsave(&video->req_lock, flags); if (video->is_enabled) list_add_tail(&req->list, &video->req_free); diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 8ac29f7230..b3a9d18a8d 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -905,6 +905,11 @@ int usb_gadget_map_request_by_dev(struct device *dev, if (req->length == 0) return 0; + if (req->sg_was_mapped) { + req->num_mapped_sgs = req->num_sgs; + return 0; + } + if (req->num_sgs) { int mapped; @@ -950,7 +955,7 @@ EXPORT_SYMBOL_GPL(usb_gadget_map_request); void usb_gadget_unmap_request_by_dev(struct device *dev, struct usb_request *req, int is_in) { - if (req->length == 0) + if (req->length == 0 || req->sg_was_mapped) return; if (req->num_mapped_sgs) { diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index e8042c158f..3432ebfae9 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -13,7 +13,7 @@ * code from Dave Liu and Shlomi Gridish. */ -#undef VERBOSE +#define pr_fmt(x) "udc: " x #include <linux/module.h> #include <linux/kernel.h> @@ -183,9 +183,9 @@ __acquires(ep->udc->lock) usb_gadget_unmap_request(&ep->udc->gadget, &req->req, ep_is_in(ep)); if (status && (status != -ESHUTDOWN)) - VDBG("complete %s req %p stat %d len %u/%u", - ep->ep.name, &req->req, status, - req->req.actual, req->req.length); + dev_vdbg(&udc->gadget.dev, "complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); ep->stopped = 1; @@ -285,7 +285,7 @@ static int dr_controller_setup(struct fsl_udc *udc) timeout = jiffies + FSL_UDC_RESET_TIMEOUT; while (fsl_readl(&dr_regs->usbcmd) & USB_CMD_CTRL_RESET) { if (time_after(jiffies, timeout)) { - ERR("udc reset timeout!\n"); + dev_err(&udc->gadget.dev, "udc reset timeout!\n"); return -ETIMEDOUT; } cpu_relax(); @@ -308,9 +308,10 @@ static int dr_controller_setup(struct fsl_udc *udc) tmp &= USB_EP_LIST_ADDRESS_MASK; fsl_writel(tmp, &dr_regs->endpointlistaddr); - VDBG("vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x", - udc->ep_qh, (int)tmp, - fsl_readl(&dr_regs->endpointlistaddr)); + dev_vdbg(&udc->gadget.dev, + "vir[qh_base] is %p phy[qh_base] is 0x%8x reg is 0x%8x\n", + udc->ep_qh, (int)tmp, + fsl_readl(&dr_regs->endpointlistaddr)); max_no_of_ep = (0x0000001F & fsl_readl(&dr_regs->dccparams)); for (ep_num = 1; ep_num < max_no_of_ep; ep_num++) { @@ -498,7 +499,7 @@ static void struct_ep_qh_setup(struct fsl_udc *udc, unsigned char ep_num, tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; break; default: - VDBG("error ep type is %d", ep_type); + dev_vdbg(&udc->gadget.dev, "error ep type is %d\n", ep_type); return; } if (zlt) @@ -611,10 +612,10 @@ static int fsl_ep_enable(struct usb_ep *_ep, spin_unlock_irqrestore(&udc->lock, flags); retval = 0; - VDBG("enabled %s (ep%d%s) maxpacket %d",ep->ep.name, - ep->ep.desc->bEndpointAddress & 0x0f, - (desc->bEndpointAddress & USB_DIR_IN) - ? "in" : "out", max); + dev_vdbg(&udc->gadget.dev, "enabled %s (ep%d%s) maxpacket %d\n", + ep->ep.name, ep->ep.desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + max); en_done: return retval; } @@ -633,7 +634,10 @@ static int fsl_ep_disable(struct usb_ep *_ep) ep = container_of(_ep, struct fsl_ep, ep); if (!_ep || !ep->ep.desc) { - VDBG("%s not enabled", _ep ? ep->ep.name : NULL); + /* + * dev_vdbg(&udc->gadget.dev, "%s not enabled\n", + * _ep ? ep->ep.name : NULL); + */ return -EINVAL; } @@ -659,7 +663,7 @@ static int fsl_ep_disable(struct usb_ep *_ep) ep->stopped = 1; spin_unlock_irqrestore(&udc->lock, flags); - VDBG("disabled %s OK", _ep->name); + dev_vdbg(&udc->gadget.dev, "disabled %s OK\n", _ep->name); return 0; } @@ -719,8 +723,8 @@ static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) { u32 temp, bitmask, tmp_stat; - /* VDBG("QH addr Register 0x%8x", dr_regs->endpointlistaddr); - VDBG("ep_qh[%d] addr is 0x%8x", i, (u32)&(ep->udc->ep_qh[i])); */ + /* dev_vdbg(&udc->gadget.dev, "QH addr Register 0x%8x\n", dr_regs->endpointlistaddr); + dev_vdbg(&udc->gadget.dev, "ep_qh[%d] addr is 0x%8x\n", i, (u32)&(ep->udc->ep_qh[i])); */ bitmask = ep_is_in(ep) ? (1 << (ep_index(ep) + 16)) @@ -808,7 +812,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, *is_last = 0; if ((*is_last) == 0) - VDBG("multi-dtd request!"); + dev_vdbg(&udc_controller->gadget.dev, "multi-dtd request!\n"); /* Fill in the transfer size; set active bit */ swap_temp = ((*length << DTD_LENGTH_BIT_POS) | DTD_STATUS_ACTIVE); @@ -820,7 +824,7 @@ static struct ep_td_struct *fsl_build_dtd(struct fsl_req *req, unsigned *length, mb(); - VDBG("length = %d address= 0x%x", *length, (int)*dma); + dev_vdbg(&udc_controller->gadget.dev, "length = %d address= 0x%x\n", *length, (int)*dma); return dtd; } @@ -864,18 +868,18 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) { struct fsl_ep *ep = container_of(_ep, struct fsl_ep, ep); struct fsl_req *req = container_of(_req, struct fsl_req, req); - struct fsl_udc *udc; + struct fsl_udc *udc = ep->udc; unsigned long flags; int ret; /* catch various bogus parameters */ if (!_req || !req->req.complete || !req->req.buf || !list_empty(&req->queue)) { - VDBG("%s, bad params", __func__); + dev_vdbg(&udc->gadget.dev, "%s, bad params\n", __func__); return -EINVAL; } - if (unlikely(!_ep || !ep->ep.desc)) { - VDBG("%s, bad ep", __func__); + if (unlikely(!ep->ep.desc)) { + dev_vdbg(&udc->gadget.dev, "%s, bad ep\n", __func__); return -EINVAL; } if (usb_endpoint_xfer_isoc(ep->ep.desc)) { @@ -883,7 +887,6 @@ fsl_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) return -EMSGSIZE; } - udc = ep->udc; if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) return -ESHUTDOWN; @@ -1036,8 +1039,8 @@ static int fsl_ep_set_halt(struct usb_ep *_ep, int value) udc->ep0_dir = 0; } out: - VDBG(" %s %s halt stat %d", ep->ep.name, - value ? "set" : "clear", status); + dev_vdbg(&udc->gadget.dev, "%s %s halt stat %d\n", ep->ep.name, + value ? "set" : "clear", status); return status; } @@ -1105,7 +1108,8 @@ static void fsl_ep_fifo_flush(struct usb_ep *_ep) /* Wait until flush complete */ while (fsl_readl(&dr_regs->endptflush)) { if (time_after(jiffies, timeout)) { - ERR("ep flush timeout\n"); + dev_err(&udc_controller->gadget.dev, + "ep flush timeout\n"); return; } cpu_relax(); @@ -1177,7 +1181,7 @@ static int fsl_vbus_session(struct usb_gadget *gadget, int is_active) udc = container_of(gadget, struct fsl_udc, gadget); spin_lock_irqsave(&udc->lock, flags); - VDBG("VBUS %s", is_active ? "on" : "off"); + dev_vdbg(&gadget->dev, "VBUS %s\n", is_active ? "on" : "off"); udc->vbus_active = (is_active != 0); if (can_pullup(udc)) fsl_writel((fsl_readl(&dr_regs->usbcmd) | USB_CMD_RUN_STOP), @@ -1543,7 +1547,7 @@ static void ep0_req_complete(struct fsl_udc *udc, struct fsl_ep *ep0, udc->ep0_state = WAIT_FOR_SETUP; break; case WAIT_FOR_SETUP: - ERR("Unexpected ep0 packets\n"); + dev_err(&udc->gadget.dev, "Unexpected ep0 packets\n"); break; default: ep0stall(udc); @@ -1612,7 +1616,7 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, errors = hc32_to_cpu(curr_td->size_ioc_sts); if (errors & DTD_ERROR_MASK) { if (errors & DTD_STATUS_HALTED) { - ERR("dTD error %08x QH=%d\n", errors, pipe); + dev_err(&udc->gadget.dev, "dTD error %08x QH=%d\n", errors, pipe); /* Clear the errors and Halt condition */ tmp = hc32_to_cpu(curr_qh->size_ioc_int_sts); tmp &= ~errors; @@ -1623,32 +1627,35 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, break; } if (errors & DTD_STATUS_DATA_BUFF_ERR) { - VDBG("Transfer overflow"); + dev_vdbg(&udc->gadget.dev, "Transfer overflow\n"); status = -EPROTO; break; } else if (errors & DTD_STATUS_TRANSACTION_ERR) { - VDBG("ISO error"); + dev_vdbg(&udc->gadget.dev, "ISO error\n"); status = -EILSEQ; break; } else - ERR("Unknown error has occurred (0x%x)!\n", + dev_err(&udc->gadget.dev, + "Unknown error has occurred (0x%x)!\n", errors); } else if (hc32_to_cpu(curr_td->size_ioc_sts) & DTD_STATUS_ACTIVE) { - VDBG("Request not complete"); + dev_vdbg(&udc->gadget.dev, "Request not complete\n"); status = REQ_UNCOMPLETE; return status; } else if (remaining_length) { if (direction) { - VDBG("Transmit dTD remaining length not zero"); + dev_vdbg(&udc->gadget.dev, + "Transmit dTD remaining length not zero\n"); status = -EPROTO; break; } else { break; } } else { - VDBG("dTD transmitted successful"); + dev_vdbg(&udc->gadget.dev, + "dTD transmitted successful\n"); } if (j != curr_req->dtd_count - 1) @@ -1691,7 +1698,7 @@ static void dtd_complete_irq(struct fsl_udc *udc) /* If the ep is configured */ if (!curr_ep->ep.name) { - WARNING("Invalid EP?"); + dev_warn(&udc->gadget.dev, "Invalid EP?\n"); continue; } @@ -1700,8 +1707,9 @@ static void dtd_complete_irq(struct fsl_udc *udc) queue) { status = process_ep_req(udc, i, curr_req); - VDBG("status of process_ep_req= %d, ep = %d", - status, ep_num); + dev_vdbg(&udc->gadget.dev, + "status of process_ep_req= %d, ep = %d\n", + status, ep_num); if (status == REQ_UNCOMPLETE) break; /* write back status to req */ @@ -1820,7 +1828,7 @@ static void reset_irq(struct fsl_udc *udc) while (fsl_readl(&dr_regs->endpointprime)) { /* Wait until all endptprime bits cleared */ if (time_after(jiffies, timeout)) { - ERR("Timeout for reset\n"); + dev_err(&udc->gadget.dev, "Timeout for reset\n"); break; } cpu_relax(); @@ -1830,7 +1838,7 @@ static void reset_irq(struct fsl_udc *udc) fsl_writel(0xffffffff, &dr_regs->endptflush); if (fsl_readl(&dr_regs->portsc1) & PORTSCX_PORT_RESET) { - VDBG("Bus reset"); + dev_vdbg(&udc->gadget.dev, "Bus reset\n"); /* Bus is reseting */ udc->bus_reset = 1; /* Reset all the queues, include XD, dTD, EP queue @@ -1838,7 +1846,7 @@ static void reset_irq(struct fsl_udc *udc) reset_queues(udc, true); udc->usb_state = USB_STATE_DEFAULT; } else { - VDBG("Controller reset"); + dev_vdbg(&udc->gadget.dev, "Controller reset\n"); /* initialize usb hw reg except for regs for EP, not * touch usbintr reg */ dr_controller_setup(udc); @@ -1872,7 +1880,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* Clear notification bits */ fsl_writel(irq_src, &dr_regs->usbsts); - /* VDBG("irq_src [0x%8x]", irq_src); */ + /* dev_vdbg(&udc->gadget.dev, "irq_src [0x%8x]", irq_src); */ /* Need to resume? */ if (udc->usb_state == USB_STATE_SUSPENDED) @@ -1881,7 +1889,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* USB Interrupt */ if (irq_src & USB_STS_INT) { - VDBG("Packet int"); + dev_vdbg(&udc->gadget.dev, "Packet int\n"); /* Setup package, we only support ep0 as control ep */ if (fsl_readl(&dr_regs->endptsetupstat) & EP_SETUP_STATUS_EP0) { tripwire_handler(udc, 0, @@ -1910,7 +1918,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) /* Reset Received */ if (irq_src & USB_STS_RESET) { - VDBG("reset int"); + dev_vdbg(&udc->gadget.dev, "reset int\n"); reset_irq(udc); status = IRQ_HANDLED; } @@ -1922,7 +1930,7 @@ static irqreturn_t fsl_udc_irq(int irq, void *_udc) } if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { - VDBG("Error IRQ %x", irq_src); + dev_vdbg(&udc->gadget.dev, "Error IRQ %x\n", irq_src); } spin_unlock_irqrestore(&udc->lock, flags); @@ -1958,7 +1966,7 @@ static int fsl_udc_start(struct usb_gadget *g, udc_controller->transceiver->otg, &udc_controller->gadget); if (retval < 0) { - ERR("can't bind to transceiver\n"); + dev_err(&udc_controller->gadget.dev, "can't bind to transceiver\n"); udc_controller->driver = NULL; return retval; } @@ -2243,7 +2251,7 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->eps = kcalloc(udc->max_ep, sizeof(struct fsl_ep), GFP_KERNEL); if (!udc->eps) { - ERR("kmalloc udc endpoint status failed\n"); + dev_err(&udc->gadget.dev, "kmalloc udc endpoint status failed\n"); goto eps_alloc_failed; } @@ -2258,7 +2266,7 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->ep_qh = dma_alloc_coherent(&pdev->dev, size, &udc->ep_qh_dma, GFP_KERNEL); if (!udc->ep_qh) { - ERR("malloc QHs for udc failed\n"); + dev_err(&udc->gadget.dev, "malloc QHs for udc failed\n"); goto ep_queue_alloc_failed; } @@ -2269,14 +2277,14 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), struct fsl_req, req); if (!udc->status_req) { - ERR("kzalloc for udc status request failed\n"); + dev_err(&udc->gadget.dev, "kzalloc for udc status request failed\n"); goto udc_status_alloc_failed; } /* allocate a small amount of memory to get valid address */ udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); if (!udc->status_req->req.buf) { - ERR("kzalloc for udc request buffer failed\n"); + dev_err(&udc->gadget.dev, "kzalloc for udc request buffer failed\n"); goto udc_req_buf_alloc_failed; } @@ -2373,7 +2381,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (pdata->operating_mode == FSL_USB2_DR_OTG) { udc_controller->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); if (IS_ERR_OR_NULL(udc_controller->transceiver)) { - ERR("Can't find OTG driver!\n"); + dev_err(&udc_controller->gadget.dev, "Can't find OTG driver!\n"); ret = -ENODEV; goto err_kfree; } @@ -2389,7 +2397,7 @@ static int fsl_udc_probe(struct platform_device *pdev) if (pdata->operating_mode == FSL_USB2_DR_DEVICE) { if (!request_mem_region(res->start, resource_size(res), driver_name)) { - ERR("request mem region for %s failed\n", pdev->name); + dev_err(&udc_controller->gadget.dev, "request mem region for %s failed\n", pdev->name); ret = -EBUSY; goto err_kfree; } @@ -2420,7 +2428,7 @@ static int fsl_udc_probe(struct platform_device *pdev) /* Read Device Controller Capability Parameters register */ dccparams = fsl_readl(&dr_regs->dccparams); if (!(dccparams & DCCPARAMS_DC)) { - ERR("This SOC doesn't support device role\n"); + dev_err(&udc_controller->gadget.dev, "This SOC doesn't support device role\n"); ret = -ENODEV; goto err_exit; } @@ -2438,14 +2446,14 @@ static int fsl_udc_probe(struct platform_device *pdev) ret = request_irq(udc_controller->irq, fsl_udc_irq, IRQF_SHARED, driver_name, udc_controller); if (ret != 0) { - ERR("cannot request irq %d err %d\n", + dev_err(&udc_controller->gadget.dev, "cannot request irq %d err %d\n", udc_controller->irq, ret); goto err_exit; } /* Initialize the udc structure including QH member and other member */ if (struct_udc_setup(udc_controller, pdev)) { - ERR("Can't initialize udc data structure\n"); + dev_err(&udc_controller->gadget.dev, "Can't initialize udc data structure\n"); ret = -ENOMEM; goto err_free_irq; } @@ -2486,7 +2494,7 @@ static int fsl_udc_probe(struct platform_device *pdev) /* setup the udc->eps[] for non-control endpoints and link * to gadget.ep_list */ for (i = 1; i < (int)(udc_controller->max_ep / 2); i++) { - char name[14]; + char name[16]; sprintf(name, "ep%dout", i); struct_ep_setup(udc_controller, i * 2, name, 1); @@ -2666,6 +2674,15 @@ static const struct platform_device_id fsl_udc_devtype[] = { } }; MODULE_DEVICE_TABLE(platform, fsl_udc_devtype); + +static const struct of_device_id fsl_udc_dt_ids[] = { + { .compatible = "fsl-usb2-dr" }, + { .compatible = "fsl-usb2-mph" }, + { .compatible = "fsl,mpc5121-usb2-dr" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, fsl_udc_dt_ids); + static struct platform_driver udc_driver = { .probe = fsl_udc_probe, .remove_new = fsl_udc_remove, @@ -2675,6 +2692,7 @@ static struct platform_driver udc_driver = { .resume = fsl_udc_resume, .driver = { .name = driver_name, + .of_match_table = fsl_udc_dt_ids, /* udc suspend/resume called from OTG driver */ .suspend = fsl_udc_otg_suspend, .resume = fsl_udc_otg_resume, diff --git a/drivers/usb/gadget/udc/fsl_usb2_udc.h b/drivers/usb/gadget/udc/fsl_usb2_udc.h index 2efc5a930b..cc1756f3e8 100644 --- a/drivers/usb/gadget/udc/fsl_usb2_udc.h +++ b/drivers/usb/gadget/udc/fsl_usb2_udc.h @@ -508,53 +508,6 @@ struct fsl_udc { /*-------------------------------------------------------------------------*/ -#ifdef DEBUG -#define DBG(fmt, args...) printk(KERN_DEBUG "[%s] " fmt "\n", \ - __func__, ## args) -#else -#define DBG(fmt, args...) do{}while(0) -#endif - -#if 0 -static void dump_msg(const char *label, const u8 * buf, unsigned int length) -{ - unsigned int start, num, i; - char line[52], *p; - - if (length >= 512) - return; - DBG("%s, length %u:\n", label, length); - start = 0; - while (length > 0) { - num = min(length, 16u); - p = line; - for (i = 0; i < num; ++i) { - if (i == 8) - *p++ = ' '; - sprintf(p, " %02x", buf[i]); - p += 3; - } - *p = 0; - printk(KERN_DEBUG "%6x: %s\n", start, line); - buf += num; - start += num; - length -= num; - } -} -#endif - -#ifdef VERBOSE -#define VDBG DBG -#else -#define VDBG(stuff...) do{}while(0) -#endif - -#define ERR(stuff...) pr_err("udc: " stuff) -#define WARNING(stuff...) pr_warn("udc: " stuff) -#define INFO(stuff...) pr_info("udc: " stuff) - -/*-------------------------------------------------------------------------*/ - /* ### Add board specific defines here */ diff --git a/drivers/usb/gadget/udc/max3420_udc.c b/drivers/usb/gadget/udc/max3420_udc.c index 89e8cf2a2a..7349ea774a 100644 --- a/drivers/usb/gadget/udc/max3420_udc.c +++ b/drivers/usb/gadget/udc/max3420_udc.c @@ -1201,7 +1201,7 @@ static int max3420_probe(struct spi_device *spi) int err, irq; u8 reg[8]; - if (spi->master->flags & SPI_CONTROLLER_HALF_DUPLEX) { + if (spi->controller->flags & SPI_CONTROLLER_HALF_DUPLEX) { dev_err(&spi->dev, "UDC needs full duplex to work\n"); return -EINVAL; } diff --git a/drivers/usb/gadget/udc/pxa27x_udc.c b/drivers/usb/gadget/udc/pxa27x_udc.c index 61424cfd2e..1a6317e4b2 100644 --- a/drivers/usb/gadget/udc/pxa27x_udc.c +++ b/drivers/usb/gadget/udc/pxa27x_udc.c @@ -24,7 +24,6 @@ #include <linux/byteorder/generic.h> #include <linux/platform_data/pxa2xx_udc.h> #include <linux/of.h> -#include <linux/of_gpio.h> #include <linux/usb.h> #include <linux/usb/ch9.h> diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c index 547af2ed9e..ba5a066905 100644 --- a/drivers/usb/gadget/udc/snps_udc_plat.c +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -8,7 +8,6 @@ #include <linux/extcon.h> #include <linux/of_address.h> #include <linux/of_irq.h> -#include <linux/of_gpio.h> #include <linux/platform_device.h> #include <linux/phy/phy.h> #include <linux/module.h> diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index 6c47ab0a49..ad145a54ca 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -66,6 +66,15 @@ struct orion_ehci_hcd { static struct hc_driver __read_mostly ehci_orion_hc_driver; /* + * Legacy DMA mask is 32 bit. + * AC5 has the DDR starting at 8GB, hence it requires + * a larger (34-bit) DMA mask, in order for DMA allocations + * to succeed: + */ +static const u64 dma_mask_orion = DMA_BIT_MASK(32); +static const u64 dma_mask_ac5 = DMA_BIT_MASK(34); + +/* * Implement Orion USB controller specification guidelines */ static void orion_usb_phy_v1_setup(struct usb_hcd *hcd) @@ -211,6 +220,7 @@ static int ehci_orion_drv_probe(struct platform_device *pdev) int irq, err; enum orion_ehci_phy_ver phy_version; struct orion_ehci_hcd *priv; + u64 *dma_mask_ptr; if (usb_disabled()) return -ENODEV; @@ -228,7 +238,8 @@ static int ehci_orion_drv_probe(struct platform_device *pdev) * set. Since shared usb code relies on it, set it here for * now. Once we have dma capability bindings this can go away. */ - err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + dma_mask_ptr = (u64 *)of_device_get_match_data(&pdev->dev); + err = dma_coerce_mask_and_coherent(&pdev->dev, *dma_mask_ptr); if (err) goto err; @@ -332,8 +343,9 @@ static void ehci_orion_drv_remove(struct platform_device *pdev) } static const struct of_device_id ehci_orion_dt_ids[] = { - { .compatible = "marvell,orion-ehci", }, - { .compatible = "marvell,armada-3700-ehci", }, + { .compatible = "marvell,orion-ehci", .data = &dma_mask_orion}, + { .compatible = "marvell,armada-3700-ehci", .data = &dma_mask_orion}, + { .compatible = "marvell,ac5-ehci", .data = &dma_mask_ac5}, {}, }; MODULE_DEVICE_TABLE(of, ehci_orion_dt_ids); diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index 357d9aee38..3348c25ddb 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -27,7 +27,6 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/of_platform.h> -#include <linux/of_gpio.h> #include <linux/platform_data/usb-ohci-pxa27x.h> #include <linux/platform_data/pxa2xx_udc.h> #include <linux/platform_device.h> diff --git a/drivers/usb/host/xhci-caps.h b/drivers/usb/host/xhci-caps.h new file mode 100644 index 0000000000..9e94cebf4a --- /dev/null +++ b/drivers/usb/host/xhci-caps.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* hc_capbase bitmasks */ +/* bits 7:0 - how long is the Capabilities register */ +#define HC_LENGTH(p) XHCI_HC_LENGTH(p) +/* bits 31:16 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 0:7, Max Device Slots */ +#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +#define HCS_SLOTS_MASK 0xff +/* bits 8:18, Max Interrupters */ +#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +/* bits 24:31, Max Ports - max value is 0x7F = 127 ports */ +#define HCS_MAX_PORTS(p) (((p) >> 24) & 0x7f) + +/* HCSPARAMS2 - hcs_params2 - bitmasks */ +/* bits 0:3, frames or uframes that SW needs to queue transactions + * ahead of the HW to meet periodic deadlines */ +#define HCS_IST(p) (((p) >> 0) & 0xf) +/* bits 4:7, max number of Event Ring segments */ +#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) +/* bits 21:25 Hi 5 bits of Scratchpad buffers SW must allocate for the HW */ +/* bit 26 Scratchpad restore - for save/restore HW state - not used yet */ +/* bits 27:31 Lo 5 bits of Scratchpad buffers SW must allocate for the HW */ +#define HCS_MAX_SCRATCHPAD(p) ((((p) >> 16) & 0x3e0) | (((p) >> 27) & 0x1f)) + +/* HCSPARAMS3 - hcs_params3 - bitmasks */ +/* bits 0:7, Max U1 to U0 latency for the roothub ports */ +#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) +/* bits 16:31, Max U2 to U0 latency for the roothub ports */ +#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) + +/* HCCPARAMS - hcc_params - bitmasks */ +/* true: HC can use 64-bit address pointers */ +#define HCC_64BIT_ADDR(p) ((p) & (1 << 0)) +/* true: HC can do bandwidth negotiation */ +#define HCC_BANDWIDTH_NEG(p) ((p) & (1 << 1)) +/* true: HC uses 64-byte Device Context structures + * FIXME 64-byte context structures aren't supported yet. + */ +#define HCC_64BYTE_CONTEXT(p) ((p) & (1 << 2)) +/* true: HC has port power switches */ +#define HCC_PPC(p) ((p) & (1 << 3)) +/* true: HC has port indicators */ +#define HCS_INDICATOR(p) ((p) & (1 << 4)) +/* true: HC has Light HC Reset Capability */ +#define HCC_LIGHT_RESET(p) ((p) & (1 << 5)) +/* true: HC supports latency tolerance messaging */ +#define HCC_LTC(p) ((p) & (1 << 6)) +/* true: no secondary Stream ID Support */ +#define HCC_NSS(p) ((p) & (1 << 7)) +/* true: HC supports Stopped - Short Packet */ +#define HCC_SPC(p) ((p) & (1 << 9)) +/* true: HC has Contiguous Frame ID Capability */ +#define HCC_CFC(p) ((p) & (1 << 11)) +/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15 */ +#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) +/* Extended Capabilities pointer from PCI base - section 5.3.6 */ +#define HCC_EXT_CAPS(p) XHCI_HCC_EXT_CAPS(p) + +#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) + +/* db_off bitmask - bits 0:1 reserved */ +#define DBOFF_MASK (~0x3) + +/* run_regs_off bitmask - bits 0:4 reserved */ +#define RTSOFF_MASK (~0x1f) + +/* HCCPARAMS2 - hcc_params2 - bitmasks */ +/* true: HC supports U3 entry Capability */ +#define HCC2_U3C(p) ((p) & (1 << 0)) +/* true: HC supports Configure endpoint command Max exit latency too large */ +#define HCC2_CMC(p) ((p) & (1 << 1)) +/* true: HC supports Force Save context Capability */ +#define HCC2_FSC(p) ((p) & (1 << 2)) +/* true: HC supports Compliance Transition Capability */ +#define HCC2_CTC(p) ((p) & (1 << 3)) +/* true: HC support Large ESIT payload Capability > 48k */ +#define HCC2_LEC(p) ((p) & (1 << 4)) +/* true: HC support Configuration Information Capability */ +#define HCC2_CIC(p) ((p) & (1 << 5)) +/* true: HC support Extended TBC Capability, Isoc burst count > 65535 */ +#define HCC2_ETC(p) ((p) & (1 << 6)) diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c index d82935d311..8a9869ef0d 100644 --- a/drivers/usb/host/xhci-dbgcap.c +++ b/drivers/usb/host/xhci-dbgcap.c @@ -634,7 +634,8 @@ static int xhci_dbc_start(struct xhci_dbc *dbc) return ret; } - return mod_delayed_work(system_wq, &dbc->event_work, 1); + return mod_delayed_work(system_wq, &dbc->event_work, + msecs_to_jiffies(dbc->poll_interval)); } static void xhci_dbc_stop(struct xhci_dbc *dbc) @@ -899,8 +900,10 @@ static void xhci_dbc_handle_events(struct work_struct *work) enum evtreturn evtr; struct xhci_dbc *dbc; unsigned long flags; + unsigned int poll_interval; dbc = container_of(to_delayed_work(work), struct xhci_dbc, event_work); + poll_interval = dbc->poll_interval; spin_lock_irqsave(&dbc->lock, flags); evtr = xhci_dbc_do_handle_events(dbc); @@ -916,13 +919,18 @@ static void xhci_dbc_handle_events(struct work_struct *work) dbc->driver->disconnect(dbc); break; case EVT_DONE: + /* set fast poll rate if there are pending data transfers */ + if (!list_empty(&dbc->eps[BULK_OUT].list_pending) || + !list_empty(&dbc->eps[BULK_IN].list_pending)) + poll_interval = 1; break; default: dev_info(dbc->dev, "stop handling dbc events\n"); return; } - mod_delayed_work(system_wq, &dbc->event_work, 1); + mod_delayed_work(system_wq, &dbc->event_work, + msecs_to_jiffies(poll_interval)); } static const char * const dbc_state_strings[DS_MAX] = { @@ -1175,6 +1183,7 @@ xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver * dbc->idVendor = DBC_VENDOR_ID; dbc->bcdDevice = DBC_DEVICE_REV; dbc->bInterfaceProtocol = DBC_PROTOCOL; + dbc->poll_interval = DBC_POLL_INTERVAL_DEFAULT; if (readl(&dbc->regs->control) & DBC_CTRL_DBC_ENABLE) goto err; diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h index e39e3ae167..92661b555c 100644 --- a/drivers/usb/host/xhci-dbgcap.h +++ b/drivers/usb/host/xhci-dbgcap.h @@ -94,6 +94,7 @@ struct dbc_ep { #define DBC_QUEUE_SIZE 16 #define DBC_WRITE_BUF_SIZE 8192 +#define DBC_POLL_INTERVAL_DEFAULT 64 /* milliseconds */ /* * Private structure for DbC hardware state: @@ -140,6 +141,7 @@ struct xhci_dbc { enum dbc_state state; struct delayed_work event_work; + unsigned int poll_interval; /* ms */ unsigned resume_required:1; struct dbc_ep eps[2]; diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 0980ade2a2..61f083de6e 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -448,38 +448,6 @@ u32 xhci_port_state_to_neutral(u32 state) } EXPORT_SYMBOL_GPL(xhci_port_state_to_neutral); -/** - * xhci_find_slot_id_by_port() - Find slot id of a usb device on a roothub port - * @hcd: pointer to hcd of the roothub - * @xhci: pointer to xhci structure - * @port: one-based port number of the port in this roothub. - * - * Return: Slot id of the usb device connected to the root port, 0 if not found - */ - -int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, - u16 port) -{ - int slot_id; - int i; - enum usb_device_speed speed; - - slot_id = 0; - for (i = 0; i < MAX_HC_SLOTS; i++) { - if (!xhci->devs[i] || !xhci->devs[i]->udev) - continue; - speed = xhci->devs[i]->udev->speed; - if (((speed >= USB_SPEED_SUPER) == (hcd->speed >= HCD_USB3)) - && xhci->devs[i]->fake_port == port) { - slot_id = i; - break; - } - } - - return slot_id; -} -EXPORT_SYMBOL_GPL(xhci_find_slot_id_by_port); - /* * Stop device * It issues stop endpoint command for EP 0 to 30. And wait the last command @@ -930,7 +898,6 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, struct xhci_bus_state *bus_state; struct xhci_hcd *xhci; struct usb_hcd *hcd; - int slot_id; u32 wIndex; hcd = port->rhub->hcd; @@ -986,13 +953,11 @@ static int xhci_handle_usb2_port_link_resume(struct xhci_port *port, spin_lock_irqsave(&xhci->lock, *flags); if (time_left) { - slot_id = xhci_find_slot_id_by_port(hcd, xhci, - wIndex + 1); - if (!slot_id) { + if (!port->slot_id) { xhci_dbg(xhci, "slot_id is zero\n"); return -ENODEV; } - xhci_ring_device(xhci, slot_id); + xhci_ring_device(xhci, port->slot_id); } else { int port_status = readl(port->addr); @@ -1202,7 +1167,6 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, unsigned long flags; u32 temp, status; int retval = 0; - int slot_id; struct xhci_bus_state *bus_state; u16 link_state = 0; u16 wake_mask = 0; @@ -1332,15 +1296,13 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; } - slot_id = xhci_find_slot_id_by_port(hcd, xhci, - portnum1); - if (!slot_id) { + if (!port->slot_id) { xhci_warn(xhci, "slot_id is zero\n"); goto error; } /* unlock to execute stop endpoint commands */ spin_unlock_irqrestore(&xhci->lock, flags); - xhci_stop_device(xhci, slot_id, 1); + xhci_stop_device(xhci, port->slot_id, 1); spin_lock_irqsave(&xhci->lock, flags); xhci_set_link_state(xhci, port, XDEV_U3); @@ -1463,14 +1425,12 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if (link_state == USB_SS_PORT_LS_U3) { int retries = 16; - slot_id = xhci_find_slot_id_by_port(hcd, xhci, - portnum1); - if (slot_id) { + if (port->slot_id) { /* unlock to execute stop endpoint * commands */ spin_unlock_irqrestore(&xhci->lock, flags); - xhci_stop_device(xhci, slot_id, 1); + xhci_stop_device(xhci, port->slot_id, 1); spin_lock_irqsave(&xhci->lock, flags); } xhci_set_link_state(xhci, port, USB_SS_PORT_LS_U3); @@ -1584,13 +1544,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } bus_state->port_c_suspend |= 1 << wIndex; - slot_id = xhci_find_slot_id_by_port(hcd, xhci, - portnum1); - if (!slot_id) { + if (!port->slot_id) { xhci_dbg(xhci, "slot_id is zero\n"); goto error; } - xhci_ring_device(xhci, slot_id); + xhci_ring_device(xhci, port->slot_id); break; case USB_PORT_FEAT_C_SUSPEND: bus_state->port_c_suspend &= ~(1 << wIndex); @@ -1821,10 +1779,7 @@ retry: if (!portsc_buf[port_index]) continue; if (test_bit(port_index, &bus_state->bus_suspended)) { - int slot_id; - - slot_id = xhci_find_slot_id_by_port(hcd, xhci, - port_index + 1); + int slot_id = ports[port_index]->slot_id; if (slot_id) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_stop_device(xhci, slot_id, 1); @@ -1877,7 +1832,6 @@ int xhci_bus_resume(struct usb_hcd *hcd) struct xhci_bus_state *bus_state; unsigned long flags; int max_ports, port_index; - int slot_id; int sret; u32 next_state; u32 temp, portsc; @@ -1970,9 +1924,8 @@ int xhci_bus_resume(struct usb_hcd *hcd) continue; } xhci_test_and_clear_bit(xhci, ports[port_index], PORT_PLC); - slot_id = xhci_find_slot_id_by_port(hcd, xhci, port_index + 1); - if (slot_id) - xhci_ring_device(xhci, slot_id); + if (ports[port_index]->slot_id) + xhci_ring_device(xhci, ports[port_index]->slot_id); } (void) readl(&xhci->op_regs->command); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 450adaca68..69dd866698 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -84,7 +84,7 @@ static void xhci_free_segments_for_ring(struct xhci_hcd *xhci, struct xhci_segment *seg; seg = first->next; - while (seg != first) { + while (seg && seg != first) { struct xhci_segment *next = seg->next; xhci_segment_free(xhci, seg); seg = next; @@ -351,17 +351,10 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, next = xhci_segment_alloc(xhci, cycle_state, max_packet, num, flags); - if (!next) { - prev = *first; - while (prev) { - next = prev->next; - xhci_segment_free(xhci, prev); - prev = next; - } - return -ENOMEM; - } - xhci_link_segments(prev, next, type, chain_links); + if (!next) + goto free_segments; + xhci_link_segments(prev, next, type, chain_links); prev = next; num++; } @@ -369,6 +362,10 @@ static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci, *last = prev; return 0; + +free_segments: + xhci_free_segments_for_ring(xhci, *first); + return -ENOMEM; } /* @@ -444,19 +441,11 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, if (ret) return -ENOMEM; - if (ring->type == TYPE_STREAM) + if (ring->type == TYPE_STREAM) { ret = xhci_update_stream_segment_mapping(ring->trb_address_map, ring, first, last, flags); - if (ret) { - struct xhci_segment *next; - do { - next = first->next; - xhci_segment_free(xhci, first); - if (first == last) - break; - first = next; - } while (true); - return ret; + if (ret) + goto free_segments; } xhci_link_rings(xhci, ring, first, last, num_new_segs); @@ -466,6 +455,10 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, ring->num_segs); return 0; + +free_segments: + xhci_free_segments_for_ring(xhci, first); + return ret; } struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, @@ -789,15 +782,14 @@ static void xhci_free_tt_info(struct xhci_hcd *xhci, bool slot_found = false; /* If the device never made it past the Set Address stage, - * it may not have the real_port set correctly. + * it may not have the root hub port pointer set correctly. */ - if (virt_dev->real_port == 0 || - virt_dev->real_port > HCS_MAX_PORTS(xhci->hcs_params1)) { - xhci_dbg(xhci, "Bad real port.\n"); + if (!virt_dev->rhub_port) { + xhci_dbg(xhci, "Bad rhub port.\n"); return; } - tt_list_head = &(xhci->rh_bw[virt_dev->real_port - 1].tts); + tt_list_head = &(xhci->rh_bw[virt_dev->rhub_port->hw_portnum].tts); list_for_each_entry_safe(tt_info, next, tt_list_head, tt_list) { /* Multi-TT hubs will have more than one entry */ if (tt_info->slot_id == slot_id) { @@ -834,7 +826,7 @@ int xhci_alloc_tt_info(struct xhci_hcd *xhci, goto free_tts; INIT_LIST_HEAD(&tt_info->tt_list); list_add(&tt_info->tt_list, - &xhci->rh_bw[virt_dev->real_port - 1].tts); + &xhci->rh_bw[virt_dev->rhub_port->hw_portnum].tts); tt_info->slot_id = virt_dev->udev->slot_id; if (tt->multi) tt_info->ttport = i+1; @@ -908,6 +900,8 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id) if (dev->udev && dev->udev->slot_id) dev->udev->slot_id = 0; + if (dev->rhub_port && dev->rhub_port->slot_id == slot_id) + dev->rhub_port->slot_id = 0; kfree(xhci->devs[slot_id]); xhci->devs[slot_id] = NULL; } @@ -929,13 +923,12 @@ static void xhci_free_virt_devices_depth_first(struct xhci_hcd *xhci, int slot_i if (!vdev) return; - if (vdev->real_port == 0 || - vdev->real_port > HCS_MAX_PORTS(xhci->hcs_params1)) { - xhci_dbg(xhci, "Bad vdev->real_port.\n"); + if (!vdev->rhub_port) { + xhci_dbg(xhci, "Bad rhub port.\n"); goto out; } - tt_list_head = &(xhci->rh_bw[vdev->real_port - 1].tts); + tt_list_head = &(xhci->rh_bw[vdev->rhub_port->hw_portnum].tts); list_for_each_entry_safe(tt_info, next, tt_list_head, tt_list) { /* is this a hub device that added a tt_info to the tts list */ if (tt_info->slot_id == slot_id) { @@ -1051,16 +1044,16 @@ void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, * The xHCI roothub may have ports of differing speeds in any order in the port * status registers. * - * The xHCI hardware wants to know the roothub port number that the USB device + * The xHCI hardware wants to know the roothub port that the USB device * is attached to (or the roothub port its ancestor hub is attached to). All we * know is the index of that port under either the USB 2.0 or the USB 3.0 * roothub, but that doesn't give us the real index into the HW port status - * registers. Call xhci_find_raw_port_number() to get real index. + * registers. */ -static u32 xhci_find_real_port_number(struct xhci_hcd *xhci, - struct usb_device *udev) +static struct xhci_port *xhci_find_rhub_port(struct xhci_hcd *xhci, struct usb_device *udev) { struct usb_device *top_dev; + struct xhci_hub *rhub; struct usb_hcd *hcd; if (udev->speed >= USB_SPEED_SUPER) @@ -1072,7 +1065,8 @@ static u32 xhci_find_real_port_number(struct xhci_hcd *xhci, top_dev = top_dev->parent) /* Found device below root hub */; - return xhci_find_raw_port_number(hcd, top_dev->portnum); + rhub = xhci_get_rhub(hcd); + return rhub->ports[top_dev->portnum - 1]; } /* Setup an xHCI virtual device for a Set Address command */ @@ -1081,9 +1075,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud struct xhci_virt_device *dev; struct xhci_ep_ctx *ep0_ctx; struct xhci_slot_ctx *slot_ctx; - u32 port_num; u32 max_packets; - struct usb_device *top_dev; dev = xhci->devs[udev->slot_id]; /* Slot ID 0 is reserved */ @@ -1124,18 +1116,15 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud return -EINVAL; } /* Find the root hub port this device is under */ - port_num = xhci_find_real_port_number(xhci, udev); - if (!port_num) + dev->rhub_port = xhci_find_rhub_port(xhci, udev); + if (!dev->rhub_port) return -EINVAL; - slot_ctx->dev_info2 |= cpu_to_le32(ROOT_HUB_PORT(port_num)); - /* Set the port number in the virtual_device to the faked port number */ - for (top_dev = udev; top_dev->parent && top_dev->parent->parent; - top_dev = top_dev->parent) - /* Found device below root hub */; - dev->fake_port = top_dev->portnum; - dev->real_port = port_num; - xhci_dbg(xhci, "Set root hub portnum to %d\n", port_num); - xhci_dbg(xhci, "Set fake root hub portnum to %d\n", dev->fake_port); + /* Slot ID is set to the device directly below the root hub */ + if (!udev->parent->parent) + dev->rhub_port->slot_id = udev->slot_id; + slot_ctx->dev_info2 |= cpu_to_le32(ROOT_HUB_PORT(dev->rhub_port->hw_portnum + 1)); + xhci_dbg(xhci, "Slot ID %d: HW portnum %d, hcd portnum %d\n", + udev->slot_id, dev->rhub_port->hw_portnum, dev->rhub_port->hcd_portnum); /* Find the right bandwidth table that this device will be a part of. * If this is a full speed device attached directly to a root port (or a @@ -1144,12 +1133,12 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud * will never be created for the HS root hub. */ if (!udev->tt || !udev->tt->hub->parent) { - dev->bw_table = &xhci->rh_bw[port_num - 1].bw_table; + dev->bw_table = &xhci->rh_bw[dev->rhub_port->hw_portnum].bw_table; } else { struct xhci_root_port_bw_info *rh_bw; struct xhci_tt_bw_info *tt_bw; - rh_bw = &xhci->rh_bw[port_num - 1]; + rh_bw = &xhci->rh_bw[dev->rhub_port->hw_portnum]; /* Find the right TT. */ list_for_each_entry(tt_bw, &rh_bw->tts, tt_list) { if (tt_bw->slot_id != udev->tt->hub->slot_id) diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 61f3f8bbdc..27eb384a39 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -122,10 +122,6 @@ static u32 get_bw_boundary(enum usb_device_speed speed) * each HS root port is treated as a single bandwidth domain, * but each SS root port is treated as two bandwidth domains, one for IN eps, * one for OUT eps. -* @real_port value is defined as follow according to xHCI spec: -* 1 for SSport0, ..., N+1 for SSportN, N+2 for HSport0, N+3 for HSport1, etc -* so the bandwidth domain array is organized as follow for simplification: -* SSport0-OUT, SSport0-IN, ..., SSportX-OUT, SSportX-IN, HSport0, ..., HSportY */ static struct mu3h_sch_bw_info * get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev, @@ -136,19 +132,19 @@ get_bw_info(struct xhci_hcd_mtk *mtk, struct usb_device *udev, int bw_index; virt_dev = xhci->devs[udev->slot_id]; - if (!virt_dev->real_port) { - WARN_ONCE(1, "%s invalid real_port\n", dev_name(&udev->dev)); + if (!virt_dev->rhub_port) { + WARN_ONCE(1, "%s invalid rhub port\n", dev_name(&udev->dev)); return NULL; } if (udev->speed >= USB_SPEED_SUPER) { if (usb_endpoint_dir_out(&ep->desc)) - bw_index = (virt_dev->real_port - 1) * 2; + bw_index = (virt_dev->rhub_port->hw_portnum) * 2; else - bw_index = (virt_dev->real_port - 1) * 2 + 1; + bw_index = (virt_dev->rhub_port->hw_portnum) * 2 + 1; } else { /* add one more for each SS port */ - bw_index = virt_dev->real_port + xhci->usb3_rhub.num_ports - 1; + bw_index = virt_dev->rhub_port->hw_portnum + xhci->usb3_rhub.num_ports; } return &mtk->sch_array[bw_index]; diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index b534ca9752..93b6976480 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -307,8 +307,11 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) xhci->quirks |= XHCI_RESET_ON_RESUME; } - if (pdev->vendor == PCI_VENDOR_ID_AMD) + if (pdev->vendor == PCI_VENDOR_ID_AMD) { xhci->quirks |= XHCI_TRUST_TX_LENGTH; + if (pdev->device == 0x43f7) + xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; + } if ((pdev->vendor == PCI_VENDOR_ID_AMD) && ((pdev->device == PCI_DEVICE_ID_AMD_PROMONTORYA_4) || @@ -820,7 +823,6 @@ static int xhci_pci_poweroff_late(struct usb_hcd *hcd, bool do_wakeup) struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_port *port; struct usb_device *udev; - unsigned int slot_id; u32 portsc; int i; @@ -843,15 +845,14 @@ static int xhci_pci_poweroff_late(struct usb_hcd *hcd, bool do_wakeup) if ((portsc & PORT_PLS_MASK) != XDEV_U3) continue; - slot_id = xhci_find_slot_id_by_port(port->rhub->hcd, xhci, - port->hcd_portnum + 1); - if (!slot_id || !xhci->devs[slot_id]) { + if (!port->slot_id || !xhci->devs[port->slot_id]) { xhci_err(xhci, "No dev for slot_id %d for port %d-%d in U3\n", - slot_id, port->rhub->hcd->self.busnum, port->hcd_portnum + 1); + port->slot_id, port->rhub->hcd->self.busnum, + port->hcd_portnum + 1); continue; } - udev = xhci->devs[slot_id]->udev; + udev = xhci->devs[port->slot_id]->udev; /* if wakeup is enabled then don't disable the port */ if (udev->do_remote_wakeup && do_wakeup) diff --git a/drivers/usb/host/xhci-port.h b/drivers/usb/host/xhci-port.h new file mode 100644 index 0000000000..f19efb966d --- /dev/null +++ b/drivers/usb/host/xhci-port.h @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* PORTSC - Port Status and Control Register - port_status_base bitmasks */ +/* true: device connected */ +#define PORT_CONNECT (1 << 0) +/* true: port enabled */ +#define PORT_PE (1 << 1) +/* bit 2 reserved and zeroed */ +/* true: port has an over-current condition */ +#define PORT_OC (1 << 3) +/* true: port reset signaling asserted */ +#define PORT_RESET (1 << 4) +/* Port Link State - bits 5:8 + * A read gives the current link PM state of the port, + * a write with Link State Write Strobe set sets the link state. + */ +#define PORT_PLS_MASK (0xf << 5) +#define XDEV_U0 (0x0 << 5) +#define XDEV_U1 (0x1 << 5) +#define XDEV_U2 (0x2 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_DISABLED (0x4 << 5) +#define XDEV_RXDETECT (0x5 << 5) +#define XDEV_INACTIVE (0x6 << 5) +#define XDEV_POLLING (0x7 << 5) +#define XDEV_RECOVERY (0x8 << 5) +#define XDEV_HOT_RESET (0x9 << 5) +#define XDEV_COMP_MODE (0xa << 5) +#define XDEV_TEST_MODE (0xb << 5) +#define XDEV_RESUME (0xf << 5) + +/* true: port has power (see HCC_PPC) */ +#define PORT_POWER (1 << 9) +/* bits 10:13 indicate device speed: + * 0 - undefined speed - port hasn't be initialized by a reset yet + * 1 - full speed + * 2 - low speed + * 3 - high speed + * 4 - super speed + * 5-15 reserved + */ +#define DEV_SPEED_MASK (0xf << 10) +#define XDEV_FS (0x1 << 10) +#define XDEV_LS (0x2 << 10) +#define XDEV_HS (0x3 << 10) +#define XDEV_SS (0x4 << 10) +#define XDEV_SSP (0x5 << 10) +#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) +#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) +#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_LS) +#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) +#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) +#define DEV_SUPERSPEEDPLUS(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP) +#define DEV_SUPERSPEED_ANY(p) (((p) & DEV_SPEED_MASK) >= XDEV_SS) +#define DEV_PORT_SPEED(p) (((p) >> 10) & 0x0f) + +/* Bits 20:23 in the Slot Context are the speed for the device */ +#define SLOT_SPEED_FS (XDEV_FS << 10) +#define SLOT_SPEED_LS (XDEV_LS << 10) +#define SLOT_SPEED_HS (XDEV_HS << 10) +#define SLOT_SPEED_SS (XDEV_SS << 10) +#define SLOT_SPEED_SSP (XDEV_SSP << 10) +/* Port Indicator Control */ +#define PORT_LED_OFF (0 << 14) +#define PORT_LED_AMBER (1 << 14) +#define PORT_LED_GREEN (2 << 14) +#define PORT_LED_MASK (3 << 14) +/* Port Link State Write Strobe - set this when changing link state */ +#define PORT_LINK_STROBE (1 << 16) +/* true: connect status change */ +#define PORT_CSC (1 << 17) +/* true: port enable change */ +#define PORT_PEC (1 << 18) +/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port + * into an enabled state, and the device into the default state. A "warm" reset + * also resets the link, forcing the device through the link training sequence. + * SW can also look at the Port Reset register to see when warm reset is done. + */ +#define PORT_WRC (1 << 19) +/* true: over-current change */ +#define PORT_OCC (1 << 20) +/* true: reset change - 1 to 0 transition of PORT_RESET */ +#define PORT_RC (1 << 21) +/* port link status change - set on some port link state transitions: + * Transition Reason + * ------------------------------------------------------------------------------ + * - U3 to Resume Wakeup signaling from a device + * - Resume to Recovery to U0 USB 3.0 device resume + * - Resume to U0 USB 2.0 device resume + * - U3 to Recovery to U0 Software resume of USB 3.0 device complete + * - U3 to U0 Software resume of USB 2.0 device complete + * - U2 to U0 L1 resume of USB 2.1 device complete + * - U0 to U0 (???) L1 entry rejection by USB 2.1 device + * - U0 to disabled L1 entry error with USB 2.1 device + * - Any state to inactive Error on USB 3.0 port + */ +#define PORT_PLC (1 << 22) +/* port configure error change - port failed to configure its link partner */ +#define PORT_CEC (1 << 23) +#define PORT_CHANGE_MASK (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ + PORT_RC | PORT_PLC | PORT_CEC) + + +/* Cold Attach Status - xHC can set this bit to report device attached during + * Sx state. Warm port reset should be perfomed to clear this bit and move port + * to connected state. + */ +#define PORT_CAS (1 << 24) +/* wake on connect (enable) */ +#define PORT_WKCONN_E (1 << 25) +/* wake on disconnect (enable) */ +#define PORT_WKDISC_E (1 << 26) +/* wake on over-current (enable) */ +#define PORT_WKOC_E (1 << 27) +/* bits 28:29 reserved */ +/* true: device is non-removable - for USB 3.0 roothub emulation */ +#define PORT_DEV_REMOVE (1 << 30) +/* Initiate a warm port reset - complete when PORT_WRC is '1' */ +#define PORT_WR (1 << 31) + +/* We mark duplicate entries with -1 */ +#define DUPLICATE_ENTRY ((u8)(-1)) + +/* Port Power Management Status and Control - port_power_base bitmasks */ +/* Inactivity timer value for transitions into U1, in microseconds. + * Timeout can be up to 127us. 0xFF means an infinite timeout. + */ +#define PORT_U1_TIMEOUT(p) ((p) & 0xff) +#define PORT_U1_TIMEOUT_MASK 0xff +/* Inactivity timer value for transitions into U2 */ +#define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) +#define PORT_U2_TIMEOUT_MASK (0xff << 8) +/* Bits 24:31 for port testing */ + +/* USB2 Protocol PORTSPMSC */ +#define PORT_L1S_MASK 7 +#define PORT_L1S_SUCCESS 1 +#define PORT_RWE (1 << 3) +#define PORT_HIRD(p) (((p) & 0xf) << 4) +#define PORT_HIRD_MASK (0xf << 4) +#define PORT_L1DS_MASK (0xff << 8) +#define PORT_L1DS(p) (((p) & 0xff) << 8) +#define PORT_HLE (1 << 16) +#define PORT_TEST_MODE_SHIFT 28 + +/* USB3 Protocol PORTLI Port Link Information */ +#define PORT_RX_LANES(p) (((p) >> 16) & 0xf) +#define PORT_TX_LANES(p) (((p) >> 20) & 0xf) + +/* USB2 Protocol PORTHLPMC */ +#define PORT_HIRDM(p)((p) & 3) +#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2) +#define PORT_BESLD(p)(((p) & 0xf) << 10) + +/* use 512 microseconds as USB2 LPM L1 default timeout. */ +#define XHCI_L1_TIMEOUT 512 + +/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. + * Safe to use with mixed HIRD and BESL systems (host and device) and is used + * by other operating systems. + * + * XHCI 1.0 errata 8/14/12 Table 13 notes: + * "Software should choose xHC BESL/BESLD field values that do not violate a + * device's resume latency requirements, + * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, + * or not program values < '4' if BLC = '0' and a BESL device is attached. + */ +#define XHCI_DEFAULT_BESL 4 + +/* + * USB3 specification define a 360ms tPollingLFPSTiemout for USB3 ports + * to complete link training. usually link trainig completes much faster + * so check status 10 times with 36ms sleep in places we need to wait for + * polling to complete. + */ +#define XHCI_PORT_POLLING_LFPS_TIME 36 diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index b2868217de..575f0fd9c9 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -113,6 +113,12 @@ static bool last_td_in_urb(struct xhci_td *td) return urb_priv->num_tds_done == urb_priv->num_tds; } +static bool unhandled_event_trb(struct xhci_ring *ring) +{ + return ((le32_to_cpu(ring->dequeue->event_cmd.flags) & TRB_CYCLE) == + ring->cycle_state); +} + static void inc_td_cnt(struct urb *urb) { struct urb_priv *urb_priv = urb->hcpriv; @@ -1154,6 +1160,15 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, break; ep->ep_state &= ~EP_STOP_CMD_PENDING; return; + case EP_STATE_STOPPED: + /* + * NEC uPD720200 sometimes sets this state and fails with + * Context Error while continuing to process TRBs. + * Be conservative and trust EP_CTX_STATE on other chips. + */ + if (!(xhci->quirks & XHCI_NEC_HOST)) + break; + fallthrough; case EP_STATE_RUNNING: /* Race, HW handled stop ep cmd before ep was running */ xhci_dbg(xhci, "Stop ep completion ctx error, ep is running\n"); @@ -1870,7 +1885,6 @@ static void handle_port_status(struct xhci_hcd *xhci, u32 port_id; u32 portsc, cmd_reg; int max_ports; - int slot_id; unsigned int hcd_portnum; struct xhci_bus_state *bus_state; bool bogus_port_status = false; @@ -1922,9 +1936,8 @@ static void handle_port_status(struct xhci_hcd *xhci, if (hcd->speed >= HCD_USB3 && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) { - slot_id = xhci_find_slot_id_by_port(hcd, xhci, hcd_portnum + 1); - if (slot_id && xhci->devs[slot_id]) - xhci->devs[slot_id]->flags |= VDEV_PORT_ERROR; + if (port->slot_id && xhci->devs[port->slot_id]) + xhci->devs[port->slot_id]->flags |= VDEV_PORT_ERROR; } if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { @@ -1982,9 +1995,8 @@ static void handle_port_status(struct xhci_hcd *xhci, * so the roothub behavior is consistent with external * USB 3.0 hub behavior. */ - slot_id = xhci_find_slot_id_by_port(hcd, xhci, hcd_portnum + 1); - if (slot_id && xhci->devs[slot_id]) - xhci_ring_device(xhci, slot_id); + if (port->slot_id && xhci->devs[port->slot_id]) + xhci_ring_device(xhci, port->slot_id); if (bus_state->port_remote_wakeup & (1 << hcd_portnum)) { xhci_test_and_clear_bit(xhci, port, PORT_PLC); usb_wakeup_notification(hcd->self.root_hub, @@ -2816,7 +2828,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, td_num--; /* Is this a TRB in the currently executing TD? */ - ep_seg = trb_in_td(xhci, ep_ring->deq_seg, ep_ring->dequeue, + ep_seg = trb_in_td(xhci, td->start_seg, td->first_trb, td->last_trb, ep_trb_dma, false); /* @@ -2884,9 +2896,8 @@ static int handle_tx_event(struct xhci_hcd *xhci, "part of current TD ep_index %d " "comp_code %u\n", ep_index, trb_comp_code); - trb_in_td(xhci, ep_ring->deq_seg, - ep_ring->dequeue, td->last_trb, - ep_trb_dma, true); + trb_in_td(xhci, td->start_seg, td->first_trb, + td->last_trb, ep_trb_dma, true); return -ESHUTDOWN; } } @@ -2962,32 +2973,18 @@ err_out: } /* - * This function handles all OS-owned events on the event ring. It may drop + * This function handles one OS-owned event on the event ring. It may drop * xhci->lock between event processing (e.g. to pass up port status changes). - * Returns >0 for "possibly more events to process" (caller should call again), - * otherwise 0 if done. In future, <0 returns should indicate error code. */ -static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) +static int xhci_handle_event_trb(struct xhci_hcd *xhci, struct xhci_interrupter *ir, + union xhci_trb *event) { - union xhci_trb *event; u32 trb_type; - /* Event ring hasn't been allocated yet. */ - if (!ir || !ir->event_ring || !ir->event_ring->dequeue) { - xhci_err(xhci, "ERROR interrupter not ready\n"); - return -ENOMEM; - } - - event = ir->event_ring->dequeue; - /* Does the HC or OS own the TRB? */ - if ((le32_to_cpu(event->event_cmd.flags) & TRB_CYCLE) != - ir->event_ring->cycle_state) - return 0; - trace_xhci_handle_event(ir->event_ring, &event->generic); /* - * Barrier between reading the TRB_CYCLE (valid) flag above and any + * Barrier between reading the TRB_CYCLE (valid) flag before, and any * speculative reads of the event's flags/data below. */ rmb(); @@ -3017,15 +3014,11 @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) * to make sure a watchdog timer didn't mark the host as non-responsive. */ if (xhci->xhc_state & XHCI_STATE_DYING) { - xhci_dbg(xhci, "xHCI host dying, returning from " - "event handler.\n"); - return 0; + xhci_dbg(xhci, "xHCI host dying, returning from event handler.\n"); + return -ENODEV; } - /* Are there more items on the event ring? Caller will call us again to - * check. - */ - return 1; + return 0; } /* @@ -3075,13 +3068,24 @@ static void xhci_clear_interrupt_pending(struct xhci_hcd *xhci, } } +/* + * Handle all OS-owned events on an interrupter event ring. It may drop + * and reaquire xhci->lock between event processing. + */ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir) { int event_loop = 0; + int err; u64 temp; xhci_clear_interrupt_pending(xhci, ir); + /* Event ring hasn't been allocated yet. */ + if (!ir->event_ring || !ir->event_ring->dequeue) { + xhci_err(xhci, "ERROR interrupter event ring not ready\n"); + return -ENOMEM; + } + if (xhci->xhc_state & XHCI_STATE_DYING || xhci->xhc_state & XHCI_STATE_HALTED) { xhci_dbg(xhci, "xHCI dying, ignoring interrupt. Shouldn't IRQs be disabled?\n"); @@ -3092,7 +3096,10 @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir return -ENODEV; } - while (xhci_handle_event(xhci, ir) > 0) { + /* Process all OS owned event TRBs on this event ring */ + while (unhandled_event_trb(ir->event_ring)) { + err = xhci_handle_event_trb(xhci, ir, ir->event_ring->dequeue); + /* * If half a segment of events have been handled in one go then * update ERDP, and force isoc trbs to interrupt more often @@ -3108,6 +3115,9 @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir /* Update SW event ring dequeue pointer */ inc_deq(xhci, ir->event_ring); + + if (err) + break; } xhci_update_erst_dequeue(xhci, ir, true); diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index ac47b1c054..5762564b9d 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -172,8 +172,7 @@ DECLARE_EVENT_CLASS(xhci_log_free_virt_dev, __field(void *, vdev) __field(unsigned long long, out_ctx) __field(unsigned long long, in_ctx) - __field(u8, fake_port) - __field(u8, real_port) + __field(int, slot_id) __field(u16, current_mel) ), @@ -181,13 +180,12 @@ DECLARE_EVENT_CLASS(xhci_log_free_virt_dev, __entry->vdev = vdev; __entry->in_ctx = (unsigned long long) vdev->in_ctx->dma; __entry->out_ctx = (unsigned long long) vdev->out_ctx->dma; - __entry->fake_port = (u8) vdev->fake_port; - __entry->real_port = (u8) vdev->real_port; + __entry->slot_id = (int) vdev->slot_id; __entry->current_mel = (u16) vdev->current_mel; ), - TP_printk("vdev %p ctx %llx | %llx fake_port %d real_port %d current_mel %d", - __entry->vdev, __entry->in_ctx, __entry->out_ctx, - __entry->fake_port, __entry->real_port, __entry->current_mel + TP_printk("vdev %p slot %d ctx %llx | %llx current_mel %d", + __entry->vdev, __entry->slot_id, __entry->in_ctx, + __entry->out_ctx, __entry->current_mel ) ); diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index afccd58c9a..8579603eda 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -346,6 +346,23 @@ static int xhci_disable_interrupter(struct xhci_interrupter *ir) return 0; } +/* interrupt moderation interval imod_interval in nanoseconds */ +static int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, + u32 imod_interval) +{ + u32 imod; + + if (!ir || !ir->ir_set || imod_interval > U16_MAX * 250) + return -EINVAL; + + imod = readl(&ir->ir_set->irq_control); + imod &= ~ER_IRQ_INTERVAL_MASK; + imod |= (imod_interval / 250) & ER_IRQ_INTERVAL_MASK; + writel(imod, &ir->ir_set->irq_control); + + return 0; +} + static void compliance_mode_recovery(struct timer_list *t) { struct xhci_hcd *xhci; @@ -528,7 +545,6 @@ static int xhci_run_finished(struct xhci_hcd *xhci) */ int xhci_run(struct usb_hcd *hcd) { - u32 temp; u64 temp_64; int ret; struct xhci_hcd *xhci = hcd_to_xhci(hcd); @@ -551,12 +567,7 @@ int xhci_run(struct usb_hcd *hcd) xhci_dbg_trace(xhci, trace_xhci_dbg_init, "ERST deq = 64'h%0lx", (long unsigned int) temp_64); - xhci_dbg_trace(xhci, trace_xhci_dbg_init, - "// Set the interrupt modulation register"); - temp = readl(&ir->ir_set->irq_control); - temp &= ~ER_IRQ_INTERVAL_MASK; - temp |= (xhci->imod_interval / 250) & ER_IRQ_INTERVAL_MASK; - writel(temp, &ir->ir_set->irq_control); + xhci_set_interrupter_moderation(ir, xhci->imod_interval); if (xhci->quirks & XHCI_NEC_HOST) { struct xhci_command *command; @@ -783,19 +794,7 @@ static void xhci_clear_command_ring(struct xhci_hcd *xhci) seg = seg->next; } while (seg != ring->deq_seg); - /* Reset the software enqueue and dequeue pointers */ - ring->deq_seg = ring->first_seg; - ring->dequeue = ring->first_seg->trbs; - ring->enq_seg = ring->deq_seg; - ring->enqueue = ring->dequeue; - - ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1; - /* - * Ring is now zeroed, so the HW should look for change of ownership - * when the cycle bit is set to 1. - */ - ring->cycle_state = 1; - + xhci_initialize_ring_info(ring, 1); /* * Reset the hardware dequeue pointer. * Yes, this will need to be re-written after resume, but we're paranoid @@ -2264,7 +2263,7 @@ static int xhci_check_tt_bw_table(struct xhci_hcd *xhci, struct xhci_tt_bw_info *tt_info; /* Find the bandwidth table for the root port this TT is attached to. */ - bw_table = &xhci->rh_bw[virt_dev->real_port - 1].bw_table; + bw_table = &xhci->rh_bw[virt_dev->rhub_port->hw_portnum].bw_table; tt_info = virt_dev->tt_info; /* If this TT already had active endpoints, the bandwidth for this TT * has already been added. Removing all periodic endpoints (and thus @@ -2382,7 +2381,7 @@ static int xhci_check_bw_table(struct xhci_hcd *xhci, if (virt_dev->tt_info) { xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, "Recalculating BW for rootport %u", - virt_dev->real_port); + virt_dev->rhub_port->hw_portnum + 1); if (xhci_check_tt_bw_table(xhci, virt_dev, old_active_eps)) { xhci_warn(xhci, "Not enough bandwidth on HS bus for " "newly activated TT.\n"); @@ -2395,7 +2394,7 @@ static int xhci_check_bw_table(struct xhci_hcd *xhci, } else { xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, "Recalculating BW for rootport %u", - virt_dev->real_port); + virt_dev->rhub_port->hw_portnum + 1); } /* Add in how much bandwidth will be used for interval zero, or the @@ -2492,14 +2491,12 @@ static int xhci_check_bw_table(struct xhci_hcd *xhci, bw_used += overhead + packet_size; if (!virt_dev->tt_info && virt_dev->udev->speed == USB_SPEED_HIGH) { - unsigned int port_index = virt_dev->real_port - 1; - /* OK, we're manipulating a HS device attached to a * root port bandwidth domain. Include the number of active TTs * in the bandwidth used. */ bw_used += TT_HS_OVERHEAD * - xhci->rh_bw[port_index].num_active_tts; + xhci->rh_bw[virt_dev->rhub_port->hw_portnum].num_active_tts; } xhci_dbg_trace(xhci, trace_xhci_dbg_quirks, @@ -2686,7 +2683,7 @@ void xhci_update_tt_active_eps(struct xhci_hcd *xhci, if (!virt_dev->tt_info) return; - rh_bw_info = &xhci->rh_bw[virt_dev->real_port - 1]; + rh_bw_info = &xhci->rh_bw[virt_dev->rhub_port->hw_portnum]; if (old_active_eps == 0 && virt_dev->tt_info->active_eps != 0) { rh_bw_info->num_active_tts += 1; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index bf05103aa6..6f4bf98a62 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -22,6 +22,9 @@ #include "xhci-ext-caps.h" #include "pci-quirks.h" +#include "xhci-port.h" +#include "xhci-caps.h" + /* max buffer size for trace and debug messages */ #define XHCI_MSG_MAX 500 @@ -62,90 +65,6 @@ struct xhci_cap_regs { /* Reserved up to (CAPLENGTH - 0x1C) */ }; -/* hc_capbase bitmasks */ -/* bits 7:0 - how long is the Capabilities register */ -#define HC_LENGTH(p) XHCI_HC_LENGTH(p) -/* bits 31:16 */ -#define HC_VERSION(p) (((p) >> 16) & 0xffff) - -/* HCSPARAMS1 - hcs_params1 - bitmasks */ -/* bits 0:7, Max Device Slots */ -#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) -#define HCS_SLOTS_MASK 0xff -/* bits 8:18, Max Interrupters */ -#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) -/* bits 24:31, Max Ports - max value is 0x7F = 127 ports */ -#define HCS_MAX_PORTS(p) (((p) >> 24) & 0x7f) - -/* HCSPARAMS2 - hcs_params2 - bitmasks */ -/* bits 0:3, frames or uframes that SW needs to queue transactions - * ahead of the HW to meet periodic deadlines */ -#define HCS_IST(p) (((p) >> 0) & 0xf) -/* bits 4:7, max number of Event Ring segments */ -#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) -/* bits 21:25 Hi 5 bits of Scratchpad buffers SW must allocate for the HW */ -/* bit 26 Scratchpad restore - for save/restore HW state - not used yet */ -/* bits 27:31 Lo 5 bits of Scratchpad buffers SW must allocate for the HW */ -#define HCS_MAX_SCRATCHPAD(p) ((((p) >> 16) & 0x3e0) | (((p) >> 27) & 0x1f)) - -/* HCSPARAMS3 - hcs_params3 - bitmasks */ -/* bits 0:7, Max U1 to U0 latency for the roothub ports */ -#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) -/* bits 16:31, Max U2 to U0 latency for the roothub ports */ -#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) - -/* HCCPARAMS - hcc_params - bitmasks */ -/* true: HC can use 64-bit address pointers */ -#define HCC_64BIT_ADDR(p) ((p) & (1 << 0)) -/* true: HC can do bandwidth negotiation */ -#define HCC_BANDWIDTH_NEG(p) ((p) & (1 << 1)) -/* true: HC uses 64-byte Device Context structures - * FIXME 64-byte context structures aren't supported yet. - */ -#define HCC_64BYTE_CONTEXT(p) ((p) & (1 << 2)) -/* true: HC has port power switches */ -#define HCC_PPC(p) ((p) & (1 << 3)) -/* true: HC has port indicators */ -#define HCS_INDICATOR(p) ((p) & (1 << 4)) -/* true: HC has Light HC Reset Capability */ -#define HCC_LIGHT_RESET(p) ((p) & (1 << 5)) -/* true: HC supports latency tolerance messaging */ -#define HCC_LTC(p) ((p) & (1 << 6)) -/* true: no secondary Stream ID Support */ -#define HCC_NSS(p) ((p) & (1 << 7)) -/* true: HC supports Stopped - Short Packet */ -#define HCC_SPC(p) ((p) & (1 << 9)) -/* true: HC has Contiguous Frame ID Capability */ -#define HCC_CFC(p) ((p) & (1 << 11)) -/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15 */ -#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) -/* Extended Capabilities pointer from PCI base - section 5.3.6 */ -#define HCC_EXT_CAPS(p) XHCI_HCC_EXT_CAPS(p) - -#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32) - -/* db_off bitmask - bits 0:1 reserved */ -#define DBOFF_MASK (~0x3) - -/* run_regs_off bitmask - bits 0:4 reserved */ -#define RTSOFF_MASK (~0x1f) - -/* HCCPARAMS2 - hcc_params2 - bitmasks */ -/* true: HC supports U3 entry Capability */ -#define HCC2_U3C(p) ((p) & (1 << 0)) -/* true: HC supports Configure endpoint command Max exit latency too large */ -#define HCC2_CMC(p) ((p) & (1 << 1)) -/* true: HC supports Force Save context Capability */ -#define HCC2_FSC(p) ((p) & (1 << 2)) -/* true: HC supports Compliance Transition Capability */ -#define HCC2_CTC(p) ((p) & (1 << 3)) -/* true: HC support Large ESIT payload Capability > 48k */ -#define HCC2_LEC(p) ((p) & (1 << 4)) -/* true: HC support Configuration Information Capability */ -#define HCC2_CIC(p) ((p) & (1 << 5)) -/* true: HC support Extended TBC Capability, Isoc burst count > 65535 */ -#define HCC2_ETC(p) ((p) & (1 << 6)) - /* Number of registers per port */ #define NUM_PORT_REGS 4 @@ -291,181 +210,6 @@ struct xhci_op_regs { #define CONFIG_CIE (1 << 9) /* bits 10:31 - reserved and should be preserved */ -/* PORTSC - Port Status and Control Register - port_status_base bitmasks */ -/* true: device connected */ -#define PORT_CONNECT (1 << 0) -/* true: port enabled */ -#define PORT_PE (1 << 1) -/* bit 2 reserved and zeroed */ -/* true: port has an over-current condition */ -#define PORT_OC (1 << 3) -/* true: port reset signaling asserted */ -#define PORT_RESET (1 << 4) -/* Port Link State - bits 5:8 - * A read gives the current link PM state of the port, - * a write with Link State Write Strobe set sets the link state. - */ -#define PORT_PLS_MASK (0xf << 5) -#define XDEV_U0 (0x0 << 5) -#define XDEV_U1 (0x1 << 5) -#define XDEV_U2 (0x2 << 5) -#define XDEV_U3 (0x3 << 5) -#define XDEV_DISABLED (0x4 << 5) -#define XDEV_RXDETECT (0x5 << 5) -#define XDEV_INACTIVE (0x6 << 5) -#define XDEV_POLLING (0x7 << 5) -#define XDEV_RECOVERY (0x8 << 5) -#define XDEV_HOT_RESET (0x9 << 5) -#define XDEV_COMP_MODE (0xa << 5) -#define XDEV_TEST_MODE (0xb << 5) -#define XDEV_RESUME (0xf << 5) - -/* true: port has power (see HCC_PPC) */ -#define PORT_POWER (1 << 9) -/* bits 10:13 indicate device speed: - * 0 - undefined speed - port hasn't be initialized by a reset yet - * 1 - full speed - * 2 - low speed - * 3 - high speed - * 4 - super speed - * 5-15 reserved - */ -#define DEV_SPEED_MASK (0xf << 10) -#define XDEV_FS (0x1 << 10) -#define XDEV_LS (0x2 << 10) -#define XDEV_HS (0x3 << 10) -#define XDEV_SS (0x4 << 10) -#define XDEV_SSP (0x5 << 10) -#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) -#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) -#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_LS) -#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) -#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) -#define DEV_SUPERSPEEDPLUS(p) (((p) & DEV_SPEED_MASK) == XDEV_SSP) -#define DEV_SUPERSPEED_ANY(p) (((p) & DEV_SPEED_MASK) >= XDEV_SS) -#define DEV_PORT_SPEED(p) (((p) >> 10) & 0x0f) - -/* Bits 20:23 in the Slot Context are the speed for the device */ -#define SLOT_SPEED_FS (XDEV_FS << 10) -#define SLOT_SPEED_LS (XDEV_LS << 10) -#define SLOT_SPEED_HS (XDEV_HS << 10) -#define SLOT_SPEED_SS (XDEV_SS << 10) -#define SLOT_SPEED_SSP (XDEV_SSP << 10) -/* Port Indicator Control */ -#define PORT_LED_OFF (0 << 14) -#define PORT_LED_AMBER (1 << 14) -#define PORT_LED_GREEN (2 << 14) -#define PORT_LED_MASK (3 << 14) -/* Port Link State Write Strobe - set this when changing link state */ -#define PORT_LINK_STROBE (1 << 16) -/* true: connect status change */ -#define PORT_CSC (1 << 17) -/* true: port enable change */ -#define PORT_PEC (1 << 18) -/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port - * into an enabled state, and the device into the default state. A "warm" reset - * also resets the link, forcing the device through the link training sequence. - * SW can also look at the Port Reset register to see when warm reset is done. - */ -#define PORT_WRC (1 << 19) -/* true: over-current change */ -#define PORT_OCC (1 << 20) -/* true: reset change - 1 to 0 transition of PORT_RESET */ -#define PORT_RC (1 << 21) -/* port link status change - set on some port link state transitions: - * Transition Reason - * ------------------------------------------------------------------------------ - * - U3 to Resume Wakeup signaling from a device - * - Resume to Recovery to U0 USB 3.0 device resume - * - Resume to U0 USB 2.0 device resume - * - U3 to Recovery to U0 Software resume of USB 3.0 device complete - * - U3 to U0 Software resume of USB 2.0 device complete - * - U2 to U0 L1 resume of USB 2.1 device complete - * - U0 to U0 (???) L1 entry rejection by USB 2.1 device - * - U0 to disabled L1 entry error with USB 2.1 device - * - Any state to inactive Error on USB 3.0 port - */ -#define PORT_PLC (1 << 22) -/* port configure error change - port failed to configure its link partner */ -#define PORT_CEC (1 << 23) -#define PORT_CHANGE_MASK (PORT_CSC | PORT_PEC | PORT_WRC | PORT_OCC | \ - PORT_RC | PORT_PLC | PORT_CEC) - - -/* Cold Attach Status - xHC can set this bit to report device attached during - * Sx state. Warm port reset should be perfomed to clear this bit and move port - * to connected state. - */ -#define PORT_CAS (1 << 24) -/* wake on connect (enable) */ -#define PORT_WKCONN_E (1 << 25) -/* wake on disconnect (enable) */ -#define PORT_WKDISC_E (1 << 26) -/* wake on over-current (enable) */ -#define PORT_WKOC_E (1 << 27) -/* bits 28:29 reserved */ -/* true: device is non-removable - for USB 3.0 roothub emulation */ -#define PORT_DEV_REMOVE (1 << 30) -/* Initiate a warm port reset - complete when PORT_WRC is '1' */ -#define PORT_WR (1 << 31) - -/* We mark duplicate entries with -1 */ -#define DUPLICATE_ENTRY ((u8)(-1)) - -/* Port Power Management Status and Control - port_power_base bitmasks */ -/* Inactivity timer value for transitions into U1, in microseconds. - * Timeout can be up to 127us. 0xFF means an infinite timeout. - */ -#define PORT_U1_TIMEOUT(p) ((p) & 0xff) -#define PORT_U1_TIMEOUT_MASK 0xff -/* Inactivity timer value for transitions into U2 */ -#define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) -#define PORT_U2_TIMEOUT_MASK (0xff << 8) -/* Bits 24:31 for port testing */ - -/* USB2 Protocol PORTSPMSC */ -#define PORT_L1S_MASK 7 -#define PORT_L1S_SUCCESS 1 -#define PORT_RWE (1 << 3) -#define PORT_HIRD(p) (((p) & 0xf) << 4) -#define PORT_HIRD_MASK (0xf << 4) -#define PORT_L1DS_MASK (0xff << 8) -#define PORT_L1DS(p) (((p) & 0xff) << 8) -#define PORT_HLE (1 << 16) -#define PORT_TEST_MODE_SHIFT 28 - -/* USB3 Protocol PORTLI Port Link Information */ -#define PORT_RX_LANES(p) (((p) >> 16) & 0xf) -#define PORT_TX_LANES(p) (((p) >> 20) & 0xf) - -/* USB2 Protocol PORTHLPMC */ -#define PORT_HIRDM(p)((p) & 3) -#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2) -#define PORT_BESLD(p)(((p) & 0xf) << 10) - -/* use 512 microseconds as USB2 LPM L1 default timeout. */ -#define XHCI_L1_TIMEOUT 512 - -/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. - * Safe to use with mixed HIRD and BESL systems (host and device) and is used - * by other operating systems. - * - * XHCI 1.0 errata 8/14/12 Table 13 notes: - * "Software should choose xHC BESL/BESLD field values that do not violate a - * device's resume latency requirements, - * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, - * or not program values < '4' if BLC = '0' and a BESL device is attached. - */ -#define XHCI_DEFAULT_BESL 4 - -/* - * USB3 specification define a 360ms tPollingLFPSTiemout for USB3 ports - * to complete link training. usually link trainig completes much faster - * so check status 10 times with 36ms sleep in places we need to wait for - * polling to complete. - */ -#define XHCI_PORT_POLLING_LFPS_TIME 36 - /** * struct xhci_intr_reg - Interrupt Register Set * @irq_pending: IMAN - Interrupt Management Register. Used to enable @@ -995,8 +739,7 @@ struct xhci_virt_device { /* Used for addressing devices and configuration changes */ struct xhci_container_ctx *in_ctx; struct xhci_virt_ep eps[EP_CTX_PER_DEV]; - u8 fake_port; - u8 real_port; + struct xhci_port *rhub_port; struct xhci_interval_bw_table *bw_table; struct xhci_tt_bw_info *tt_info; /* @@ -1719,6 +1462,8 @@ struct xhci_port { unsigned int lpm_incapable:1; unsigned long resume_timestamp; bool rexit_active; + /* Slot ID is the index of the device directly connected to the port */ + int slot_id; struct completion rexit_done; struct completion u3exit_done; }; @@ -2201,8 +1946,6 @@ unsigned long xhci_get_resuming_ports(struct usb_hcd *hcd); #endif /* CONFIG_PM */ u32 xhci_port_state_to_neutral(u32 state); -int xhci_find_slot_id_by_port(struct usb_hcd *hcd, struct xhci_hcd *xhci, - u16 port); void xhci_ring_device(struct xhci_hcd *xhci, int slot_id); /* xHCI contexts */ diff --git a/drivers/usb/image/mdc800.c b/drivers/usb/image/mdc800.c index 67f098579f..7b7e1554ea 100644 --- a/drivers/usb/image/mdc800.c +++ b/drivers/usb/image/mdc800.c @@ -631,7 +631,6 @@ static int mdc800_device_open (struct inode* inode, struct file *file) mdc800->camera_busy=0; mdc800->camera_request_ready=0; - retval=0; mdc800->irq_urb->dev = mdc800->dev; retval = usb_submit_urb (mdc800->irq_urb, GFP_KERNEL); if (retval) { diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 76862ba40f..0e5e4cb74c 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -2521,21 +2521,19 @@ static const struct hc_driver isp1760_hc_driver = { int __init isp1760_init_kmem_once(void) { urb_listitem_cachep = kmem_cache_create("isp1760_urb_listitem", - sizeof(struct urb_listitem), 0, SLAB_TEMPORARY | - SLAB_MEM_SPREAD, NULL); + sizeof(struct urb_listitem), 0, SLAB_TEMPORARY, NULL); if (!urb_listitem_cachep) return -ENOMEM; qtd_cachep = kmem_cache_create("isp1760_qtd", - sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY | - SLAB_MEM_SPREAD, NULL); + sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY, NULL); if (!qtd_cachep) goto destroy_urb_listitem; qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh), - 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL); + 0, SLAB_TEMPORARY, NULL); if (!qh_cachep) goto destroy_qtd; diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c index e162838bec..d8049275a0 100644 --- a/drivers/usb/misc/onboard_usb_hub.c +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -264,7 +264,7 @@ static int onboard_hub_probe(struct platform_device *pdev) if (!hub) return -ENOMEM; - hub->pdata = device_get_match_data(&pdev->dev); + hub->pdata = device_get_match_data(dev); if (!hub->pdata) return -EINVAL; @@ -458,6 +458,8 @@ static const struct usb_device_id onboard_hub_id_table[] = { { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8025) }, /* TI USB8020B 3.0 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8027) }, /* TI USB8020B 2.0 */ { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */ { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */ { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 */ diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h index f360d5cf8d..b4b15d45f8 100644 --- a/drivers/usb/misc/onboard_usb_hub.h +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -26,6 +26,11 @@ static const struct onboard_hub_pdata realtek_rts5411_data = { .num_supplies = 1, }; +static const struct onboard_hub_pdata ti_tusb8020b_data = { + .reset_us = 3000, + .num_supplies = 1, +}; + static const struct onboard_hub_pdata ti_tusb8041_data = { .reset_us = 3000, .num_supplies = 1, @@ -62,6 +67,8 @@ static const struct of_device_id onboard_hub_match[] = { { .compatible = "usb424,2517", .data = µchip_usb424_data, }, { .compatible = "usb424,2744", .data = µchip_usb5744_data, }, { .compatible = "usb424,5744", .data = µchip_usb5744_data, }, + { .compatible = "usb451,8025", .data = &ti_tusb8020b_data, }, + { .compatible = "usb451,8027", .data = &ti_tusb8020b_data, }, { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, }, diff --git a/drivers/usb/mtu3/mtu3_host.c b/drivers/usb/mtu3/mtu3_host.c index 9f2be22af8..7c657ea2da 100644 --- a/drivers/usb/mtu3/mtu3_host.c +++ b/drivers/usb/mtu3/mtu3_host.c @@ -34,6 +34,18 @@ #define WC0_SSUSB0_CDEN BIT(6) #define WC0_IS_SPM_EN BIT(1) +/* mt8195 */ +#define PERI_WK_CTRL0_8195 0x04 +#define WC0_IS_P_95 BIT(30) /* polarity */ +#define WC0_IS_C_95(x) ((u32)(((x) & 0x7) << 27)) +#define WC0_IS_EN_P3_95 BIT(26) +#define WC0_IS_EN_P2_95 BIT(25) + +#define PERI_WK_CTRL1_8195 0x20 +#define WC1_IS_C_95(x) ((u32)(((x) & 0xf) << 28)) +#define WC1_IS_P_95 BIT(12) +#define WC1_IS_EN_P0_95 BIT(6) + /* mt2712 etc */ #define PERI_SSUSB_SPM_CTRL 0x0 #define SSC_IP_SLEEP_EN BIT(4) @@ -44,6 +56,9 @@ enum ssusb_uwk_vers { SSUSB_UWK_V2, SSUSB_UWK_V1_1 = 101, /* specific revision 1.01 */ SSUSB_UWK_V1_2, /* specific revision 1.02 */ + SSUSB_UWK_V1_3, /* mt8195 IP0 */ + SSUSB_UWK_V1_5 = 105, /* mt8195 IP2 */ + SSUSB_UWK_V1_6, /* mt8195 IP3 */ }; /* @@ -70,6 +85,21 @@ static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) msk = WC0_SSUSB0_CDEN | WC0_IS_SPM_EN; val = enable ? msk : 0; break; + case SSUSB_UWK_V1_3: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL1_8195; + msk = WC1_IS_EN_P0_95 | WC1_IS_C_95(0xf) | WC1_IS_P_95; + val = enable ? (WC1_IS_EN_P0_95 | WC1_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_5: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P2_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P2_95 | WC0_IS_C_95(0x1)) : 0; + break; + case SSUSB_UWK_V1_6: + reg = ssusb->uwk_reg_base + PERI_WK_CTRL0_8195; + msk = WC0_IS_EN_P3_95 | WC0_IS_C_95(0x7) | WC0_IS_P_95; + val = enable ? (WC0_IS_EN_P3_95 | WC0_IS_C_95(0x1)) : 0; + break; case SSUSB_UWK_V2: reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index 051c6da7cf..55df0ee413 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1744,7 +1744,6 @@ static inline void musb_g_init_endpoints(struct musb *musb) { u8 epnum; struct musb_hw_ep *hw_ep; - unsigned count = 0; /* initialize endpoint list just once */ INIT_LIST_HEAD(&(musb->g.ep_list)); @@ -1754,17 +1753,14 @@ static inline void musb_g_init_endpoints(struct musb *musb) epnum++, hw_ep++) { if (hw_ep->is_shared_fifo /* || !epnum */) { init_peripheral_ep(musb, &hw_ep->ep_in, epnum, 0); - count++; } else { if (hw_ep->max_packet_sz_tx) { init_peripheral_ep(musb, &hw_ep->ep_in, epnum, 1); - count++; } if (hw_ep->max_packet_sz_rx) { init_peripheral_ep(musb, &hw_ep->ep_out, epnum, 0); - count++; } } } diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c index 9ab50f26db..fdcffebf41 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -74,33 +74,26 @@ static void nop_reset(struct usb_phy_generic *nop) } /* interface to regulator framework */ -static void nop_set_vbus_draw(struct usb_phy_generic *nop, unsigned mA) +static int nop_set_vbus(struct usb_otg *otg, bool enable) { - struct regulator *vbus_draw = nop->vbus_draw; - int enabled; - int ret; + int ret = 0; + struct usb_phy_generic *nop = dev_get_drvdata(otg->usb_phy->dev); - if (!vbus_draw) - return; + if (!nop->vbus_draw) + return 0; - enabled = nop->vbus_draw_enabled; - if (mA) { - regulator_set_current_limit(vbus_draw, 0, 1000 * mA); - if (!enabled) { - ret = regulator_enable(vbus_draw); - if (ret < 0) - return; - nop->vbus_draw_enabled = 1; - } - } else { - if (enabled) { - ret = regulator_disable(vbus_draw); - if (ret < 0) - return; - nop->vbus_draw_enabled = 0; - } + if (enable && !nop->vbus_draw_enabled) { + ret = regulator_enable(nop->vbus_draw); + if (ret) + nop->vbus_draw_enabled = false; + else + nop->vbus_draw_enabled = true; + + } else if (!enable && nop->vbus_draw_enabled) { + ret = regulator_disable(nop->vbus_draw); + nop->vbus_draw_enabled = false; } - nop->mA = mA; + return ret; } @@ -120,14 +113,9 @@ static irqreturn_t nop_gpio_vbus_thread(int irq, void *data) otg->state = OTG_STATE_B_PERIPHERAL; nop->phy.last_event = status; - /* drawing a "unit load" is *always* OK, except for OTG */ - nop_set_vbus_draw(nop, 100); - atomic_notifier_call_chain(&nop->phy.notifier, status, otg->gadget); } else { - nop_set_vbus_draw(nop, 0); - status = USB_EVENT_NONE; otg->state = OTG_STATE_B_IDLE; nop->phy.last_event = status; @@ -284,6 +272,7 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop) nop->phy.otg->usb_phy = &nop->phy; nop->phy.otg->set_host = nop_set_host; nop->phy.otg->set_peripheral = nop_set_peripheral; + nop->phy.otg->set_vbus = nop_set_vbus; return 0; } @@ -341,6 +330,9 @@ static void usb_phy_generic_remove(struct platform_device *pdev) struct usb_phy_generic *nop = platform_get_drvdata(pdev); usb_remove_phy(&nop->phy); + + if (nop->vbus_draw && nop->vbus_draw_enabled) + regulator_disable(nop->vbus_draw); } static const struct of_device_id nop_xceiv_dt_ids[] = { diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index 4b468bde19..06e0fb2356 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -699,7 +699,7 @@ out: } EXPORT_SYMBOL_GPL(usb_add_phy); -static struct device_type usb_phy_dev_type = { +static const struct device_type usb_phy_dev_type = { .name = "usb_phy", .uevent = usb_phy_uevent, }; diff --git a/drivers/usb/roles/class.c b/drivers/usb/roles/class.c index 70165dd86b..d7aa913ceb 100644 --- a/drivers/usb/roles/class.c +++ b/drivers/usb/roles/class.c @@ -7,6 +7,7 @@ * Hans de Goede <hdegoede@redhat.com> */ +#include <linux/component.h> #include <linux/usb/role.h> #include <linux/property.h> #include <linux/device.h> @@ -36,6 +37,32 @@ struct usb_role_switch { #define to_role_switch(d) container_of(d, struct usb_role_switch, dev) +static int connector_bind(struct device *dev, struct device *connector, void *data) +{ + int ret; + + ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector"); + if (ret) + return ret; + + ret = sysfs_create_link(&connector->kobj, &dev->kobj, "usb-role-switch"); + if (ret) + sysfs_remove_link(&dev->kobj, "connector"); + + return ret; +} + +static void connector_unbind(struct device *dev, struct device *connector, void *data) +{ + sysfs_remove_link(&connector->kobj, "usb-role-switch"); + sysfs_remove_link(&dev->kobj, "connector"); +} + +static const struct component_ops connector_ops = { + .bind = connector_bind, + .unbind = connector_unbind, +}; + /** * usb_role_switch_set_role - Set USB role for a switch * @sw: USB role switch @@ -361,6 +388,12 @@ usb_role_switch_register(struct device *parent, return ERR_PTR(ret); } + if (dev_fwnode(&sw->dev)) { + ret = component_add(&sw->dev, &connector_ops); + if (ret) + dev_warn(&sw->dev, "failed to add component\n"); + } + sw->registered = true; /* TODO: Symlinks for the host port and the device controller. */ @@ -377,10 +410,12 @@ EXPORT_SYMBOL_GPL(usb_role_switch_register); */ void usb_role_switch_unregister(struct usb_role_switch *sw) { - if (!IS_ERR_OR_NULL(sw)) { - sw->registered = false; - device_unregister(&sw->dev); - } + if (IS_ERR_OR_NULL(sw)) + return; + sw->registered = false; + if (dev_fwnode(&sw->dev)) + component_del(&sw->dev, &connector_ops); + device_unregister(&sw->dev); } EXPORT_SYMBOL_GPL(usb_role_switch_unregister); diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 22d01a0f10..76a04ab411 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -2612,7 +2612,7 @@ static void ftdi_set_termios(struct tty_struct *tty, struct device *ddev = &port->dev; struct ftdi_private *priv = usb_get_serial_port_data(port); struct ktermios *termios = &tty->termios; - unsigned int cflag = termios->c_cflag; + unsigned int cflag; u16 value, index; int ret; diff --git a/drivers/usb/serial/keyspan.c b/drivers/usb/serial/keyspan.c index 93b17e0e05..0a78398519 100644 --- a/drivers/usb/serial/keyspan.c +++ b/drivers/usb/serial/keyspan.c @@ -921,7 +921,6 @@ static void usa28_indat_callback(struct urb *urb) port = urb->context; p_priv = usb_get_serial_port_data(port); - data = urb->transfer_buffer; if (urb != p_priv->in_urbs[p_priv->in_flip]) return; diff --git a/drivers/usb/serial/oti6858.c b/drivers/usb/serial/oti6858.c index 6365cfe540..fa07f6ff9e 100644 --- a/drivers/usb/serial/oti6858.c +++ b/drivers/usb/serial/oti6858.c @@ -409,7 +409,6 @@ static void oti6858_set_termios(struct tty_struct *tty, cflag = tty->termios.c_cflag; spin_lock_irqsave(&priv->lock, flags); - divisor = priv->pending_setup.divisor; frame_fmt = priv->pending_setup.frame_fmt; control = priv->pending_setup.control; spin_unlock_irqrestore(&priv->lock, flags); diff --git a/drivers/usb/storage/freecom.c b/drivers/usb/storage/freecom.c index 2b098b55c4..c3ce51c2da 100644 --- a/drivers/usb/storage/freecom.c +++ b/drivers/usb/storage/freecom.c @@ -534,7 +534,6 @@ static void pdump(struct us_data *us, void *ibuffer, int length) } line[offset] = 0; usb_stor_dbg(us, "%s\n", line); - offset = 0; } #endif diff --git a/drivers/usb/storage/sddr55.c b/drivers/usb/storage/sddr55.c index 15dc25801c..0aa079405d 100644 --- a/drivers/usb/storage/sddr55.c +++ b/drivers/usb/storage/sddr55.c @@ -196,7 +196,7 @@ static int sddr55_read_data(struct us_data *us, unsigned char *buffer; unsigned int pba; - unsigned long address; + unsigned int address; unsigned short pages; unsigned int len, offset; @@ -316,7 +316,7 @@ static int sddr55_write_data(struct us_data *us, unsigned int pba; unsigned int new_pba; - unsigned long address; + unsigned int address; unsigned short pages; int i; diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c index f8ea3054be..038dc51f42 100644 --- a/drivers/usb/typec/altmodes/displayport.c +++ b/drivers/usb/typec/altmodes/displayport.c @@ -50,13 +50,17 @@ enum { enum dp_state { DP_STATE_IDLE, DP_STATE_ENTER, + DP_STATE_ENTER_PRIME, DP_STATE_UPDATE, DP_STATE_CONFIGURE, + DP_STATE_CONFIGURE_PRIME, DP_STATE_EXIT, + DP_STATE_EXIT_PRIME, }; struct dp_altmode { struct typec_displayport_data data; + struct typec_displayport_data data_prime; enum dp_state state; bool hpd; @@ -67,6 +71,7 @@ struct dp_altmode { struct typec_altmode *alt; const struct typec_altmode *port; struct fwnode_handle *connector_fwnode; + struct typec_altmode *plug_prime; }; static int dp_altmode_notify(struct dp_altmode *dp) @@ -99,12 +104,18 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con) 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); + /* Account for active cable capabilities */ + if (dp->plug_prime) + pin_assign &= DP_CAP_DFP_D_PIN_ASSIGN(dp->plug_prime->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_PIN_ASSIGN_UFP_D(dp->alt->vdo) & DP_CAP_PIN_ASSIGN_DFP_D(dp->port->vdo); + /* Account for active cable capabilities */ + if (dp->plug_prime) + pin_assign &= DP_CAP_UFP_D_PIN_ASSIGN(dp->plug_prime->vdo); break; default: break; @@ -130,6 +141,8 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con) } dp->data.conf = conf; + if (dp->plug_prime) + dp->data_prime.conf = conf; return 0; } @@ -143,13 +156,16 @@ static int dp_altmode_status_update(struct dp_altmode *dp) if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) { dp->data.conf = 0; - dp->state = DP_STATE_CONFIGURE; + dp->data_prime.conf = 0; + dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : + 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; + dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : + DP_STATE_CONFIGURE; if (dp->hpd != hpd) { dp->hpd = hpd; dp->pending_hpd = true; @@ -209,6 +225,19 @@ static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf) return ret; } +static int dp_altmode_configure_vdm_cable(struct dp_altmode *dp, u32 conf) +{ + int svdm_version = typec_altmode_get_cable_svdm_version(dp->plug_prime); + u32 header; + + if (svdm_version < 0) + return svdm_version; + + header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE); + + return typec_cable_altmode_vdm(dp->plug_prime, TYPEC_PLUG_SOP_P, header, &conf, 2); +} + static void dp_altmode_work(struct work_struct *work) { struct dp_altmode *dp = container_of(work, struct dp_altmode, work); @@ -225,6 +254,19 @@ static void dp_altmode_work(struct work_struct *work) if (ret && ret != -EBUSY) dev_err(&dp->alt->dev, "failed to enter mode\n"); break; + case DP_STATE_ENTER_PRIME: + ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL); + /* + * If we fail to enter Alt Mode on SOP', then we should drop the + * plug from the driver and attempt to run the driver without + * it. + */ + if (ret && ret != -EBUSY) { + dev_err(&dp->alt->dev, "plug failed to enter mode\n"); + dp->state = DP_STATE_ENTER; + goto disable_prime; + } + break; case DP_STATE_UPDATE: svdm_version = typec_altmode_get_svdm_version(dp->alt); if (svdm_version < 0) @@ -243,10 +285,24 @@ static void dp_altmode_work(struct work_struct *work) dev_err(&dp->alt->dev, "unable to send Configure command (%d)\n", ret); break; + case DP_STATE_CONFIGURE_PRIME: + ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf); + if (ret) { + dev_err(&dp->plug_prime->dev, + "unable to send Configure command (%d)\n", + ret); + dp->state = DP_STATE_CONFIGURE; + goto disable_prime; + } + break; case DP_STATE_EXIT: if (typec_altmode_exit(dp->alt)) dev_err(&dp->alt->dev, "Exit Mode Failed!\n"); break; + case DP_STATE_EXIT_PRIME: + if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P)) + dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n"); + break; default: break; } @@ -254,6 +310,13 @@ static void dp_altmode_work(struct work_struct *work) dp->state = DP_STATE_IDLE; mutex_unlock(&dp->lock); + return; + +disable_prime: + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + schedule_work(&dp->work); + mutex_unlock(&dp->lock); } static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo) @@ -314,6 +377,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt, dp->hpd = false; sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd"); } + if (dp->plug_prime) + dp->state = DP_STATE_EXIT_PRIME; break; case DP_CMD_STATUS_UPDATE: dp->data.status = *vdo; @@ -348,10 +413,84 @@ err_unlock: return ret; } +static int dp_cable_altmode_vdm(struct typec_altmode *alt, enum typec_plug_index sop, + 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: + typec_altmode_update_active(dp->plug_prime, true); + dp->state = DP_STATE_ENTER; + break; + case CMD_EXIT_MODE: + dp->data_prime.status = 0; + dp->data_prime.conf = 0; + typec_altmode_update_active(dp->plug_prime, false); + break; + case DP_CMD_CONFIGURE: + dp->state = DP_STATE_CONFIGURE; + break; + default: + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case DP_CMD_CONFIGURE: + dp->data_prime.conf = 0; + /* Attempt to configure on SOP, drop plug */ + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + dp->state = DP_STATE_CONFIGURE; + 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, NULL) : - typec_altmode_exit(alt); + struct dp_altmode *dp = typec_altmode_get_drvdata(alt); + int ret; + + if (activate) { + if (dp->plug_prime) { + ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL); + if (ret < 0) { + typec_altmode_put_plug(dp->plug_prime); + dp->plug_prime = NULL; + } else { + return ret; + } + } + return typec_altmode_enter(alt, NULL); + } else { + return typec_altmode_exit(alt); + } } static const struct typec_altmode_ops dp_altmode_ops = { @@ -360,6 +499,10 @@ static const struct typec_altmode_ops dp_altmode_ops = { .activate = dp_altmode_activate, }; +static const struct typec_cable_ops dp_cable_ops = { + .vdm = dp_cable_altmode_vdm, +}; + static const char * const configurations[] = { [DP_CONF_USB] = "USB", [DP_CONF_DFP_D] = "source", @@ -501,6 +644,7 @@ pin_assignment_store(struct device *dev, struct device_attribute *attr, /* Only send Configure command if a configuration has been set */ if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) { + /* todo: send manual configure over SOP'*/ ret = dp_altmode_configure_vdm(dp, conf); if (ret) goto out_unlock; @@ -579,6 +723,7 @@ static const struct attribute_group *displayport_groups[] = { int dp_altmode_probe(struct typec_altmode *alt) { const struct typec_altmode *port = typec_altmode_get_partner(alt); + struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P); struct fwnode_handle *fwnode; struct dp_altmode *dp; @@ -603,6 +748,13 @@ int dp_altmode_probe(struct typec_altmode *alt) alt->desc = "DisplayPort"; alt->ops = &dp_altmode_ops; + if (plug) { + plug->desc = "Displayport"; + plug->cable_ops = &dp_cable_ops; + } + + dp->plug_prime = plug; + fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */ if (fwnode_property_present(fwnode, "displayport")) dp->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0); @@ -612,8 +764,10 @@ int dp_altmode_probe(struct typec_altmode *alt) dp->connector_fwnode = NULL; typec_altmode_set_drvdata(alt, dp); + if (plug) + typec_altmode_set_drvdata(plug, dp); - dp->state = DP_STATE_ENTER; + dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; schedule_work(&dp->work); return 0; @@ -625,6 +779,7 @@ void dp_altmode_remove(struct typec_altmode *alt) struct dp_altmode *dp = typec_altmode_get_drvdata(alt); cancel_work_sync(&dp->work); + typec_altmode_put_plug(dp->plug_prime); if (dp->connector_fwnode) { drm_connector_oob_hotplug_event(dp->connector_fwnode, diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c index e95ec7e382..6ea103e1ab 100644 --- a/drivers/usb/typec/bus.c +++ b/drivers/usb/typec/bus.c @@ -245,6 +245,108 @@ typec_altmode_get_partner(struct typec_altmode *adev) EXPORT_SYMBOL_GPL(typec_altmode_get_partner); /* -------------------------------------------------------------------------- */ +/* API for cable alternate modes */ + +/** + * typec_cable_altmode_enter - Enter Mode + * @adev: The alternate mode + * @sop: Cable plug target for Enter Mode command + * @vdo: VDO for the Enter Mode command + * + * Alternate mode drivers use this function to enter mode on the cable plug. + * If the alternate mode does not require VDO, @vdo must be NULL. + */ +int typec_cable_altmode_enter(struct typec_altmode *adev, enum typec_plug_index sop, u32 *vdo) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev; + + if (!adev || adev->active) + return 0; + + if (!partner) + return -ENODEV; + + pdev = &partner->adev; + + if (!pdev->active) + return -EPERM; + + if (!pdev->cable_ops || !pdev->cable_ops->enter) + return -EOPNOTSUPP; + + return pdev->cable_ops->enter(pdev, sop, vdo); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_enter); + +/** + * typec_cable_altmode_exit - Exit Mode + * @adev: The alternate mode + * @sop: Cable plug target for Exit Mode command + * + * The alternate mode drivers use this function to exit mode on the cable plug. + */ +int typec_cable_altmode_exit(struct typec_altmode *adev, enum typec_plug_index sop) +{ + struct altmode *partner = to_altmode(adev)->partner; + struct typec_altmode *pdev; + + if (!adev || !adev->active) + return 0; + + if (!partner) + return -ENODEV; + + pdev = &partner->adev; + + if (!pdev->cable_ops || !pdev->cable_ops->exit) + return -EOPNOTSUPP; + + return pdev->cable_ops->exit(pdev, sop); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_exit); + +/** + * typec_cable_altmode_vdm - Send Vendor Defined Messages (VDM) between the cable plug and port. + * @adev: Alternate mode handle + * @sop: Cable plug target for VDM + * @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 cable plugs. The port drivers use it to deliver the Structured VDMs + * received from the cable plugs to the alternate mode drivers. + */ +int typec_cable_altmode_vdm(struct typec_altmode *adev, enum typec_plug_index sop, + const u32 header, const u32 *vdo, int count) +{ + struct altmode *altmode; + struct typec_altmode *pdev; + + if (!adev) + return 0; + + altmode = to_altmode(adev); + + if (is_typec_plug(adev->dev.parent)) { + if (!altmode->partner) + return -ENODEV; + pdev = &altmode->partner->adev; + } else { + if (!altmode->plug[sop]) + return -ENODEV; + pdev = &altmode->plug[sop]->adev; + } + + if (!pdev->cable_ops || !pdev->cable_ops->vdm) + return -EOPNOTSUPP; + + return pdev->cable_ops->vdm(pdev, sop, header, vdo, count); +} +EXPORT_SYMBOL_GPL(typec_cable_altmode_vdm); + +/* -------------------------------------------------------------------------- */ /* API for the alternate mode drivers */ /** diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index e6a100376f..9610e647a8 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -21,7 +21,7 @@ static DEFINE_IDA(typec_index_ida); -struct class typec_class = { +const struct class typec_class = { .name = "typec", }; @@ -2137,6 +2137,46 @@ int typec_get_negotiated_svdm_version(struct typec_port *port) EXPORT_SYMBOL_GPL(typec_get_negotiated_svdm_version); /** + * typec_get_cable_svdm_version - Get cable negotiated SVDM Version + * @port: USB Type-C Port. + * + * Get the negotiated SVDM Version for the cable. The Version is set to the port + * default value based on the PD Revision during cable registration, and updated + * after a successful Discover Identity if the negotiated value is less than the + * default. + * + * Returns usb_pd_svdm_ver if the cable has been registered otherwise -ENODEV. + */ +int typec_get_cable_svdm_version(struct typec_port *port) +{ + enum usb_pd_svdm_ver svdm_version; + struct device *cable_dev; + + cable_dev = device_find_child(&port->dev, NULL, cable_match); + if (!cable_dev) + return -ENODEV; + + svdm_version = to_typec_cable(cable_dev)->svdm_version; + put_device(cable_dev); + + return svdm_version; +} +EXPORT_SYMBOL_GPL(typec_get_cable_svdm_version); + +/** + * typec_cable_set_svdm_version - Set negotiated Structured VDM (SVDM) Version + * @cable: USB Type-C Active Cable that supports SVDM + * @svdm_version: Negotiated SVDM Version + * + * This routine is used to save the negotiated SVDM Version. + */ +void typec_cable_set_svdm_version(struct typec_cable *cable, enum usb_pd_svdm_ver svdm_version) +{ + cable->svdm_version = svdm_version; +} +EXPORT_SYMBOL_GPL(typec_cable_set_svdm_version); + +/** * typec_get_drvdata - Return private driver data pointer * @port: USB Type-C port */ @@ -2286,6 +2326,25 @@ void typec_port_register_altmodes(struct typec_port *port, EXPORT_SYMBOL_GPL(typec_port_register_altmodes); /** + * typec_port_register_cable_ops - Register typec_cable_ops to port altmodes + * @altmodes: USB Type-C Port's altmode vector + * @max_altmodes: The maximum number of alt modes supported by the port + * @ops: Cable alternate mode vector + */ +void typec_port_register_cable_ops(struct typec_altmode **altmodes, int max_altmodes, + const struct typec_cable_ops *ops) +{ + int i; + + for (i = 0; i < max_altmodes; i++) { + if (!altmodes[i]) + return; + altmodes[i]->cable_ops = ops; + } +} +EXPORT_SYMBOL_GPL(typec_port_register_cable_ops); + +/** * typec_register_port - Register a USB Type-C Port * @parent: Parent device * @cap: Description of the port diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h index c36761ba3f..7485cdb9dd 100644 --- a/drivers/usb/typec/class.h +++ b/drivers/usb/typec/class.h @@ -23,6 +23,7 @@ struct typec_cable { struct usb_pd_identity *identity; unsigned int active:1; u16 pd_revision; /* 0300H = "3.0" */ + enum usb_pd_svdm_ver svdm_version; }; struct typec_partner { @@ -92,9 +93,9 @@ extern const struct device_type typec_port_dev_type; #define is_typec_plug(dev) ((dev)->type == &typec_plug_dev_type) #define is_typec_port(dev) ((dev)->type == &typec_port_dev_type) -extern struct class typec_mux_class; -extern struct class retimer_class; -extern struct class typec_class; +extern const struct class typec_mux_class; +extern const struct class retimer_class; +extern const struct class typec_class; #if defined(CONFIG_ACPI) int typec_link_ports(struct typec_port *connector); diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 80dd91938d..49926d6e72 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -469,6 +469,6 @@ void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev) } EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); -struct class typec_mux_class = { +const struct class typec_mux_class = { .name = "typec_mux", }; diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index d2cb5e733e..399c7b0983 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -36,6 +36,16 @@ config TYPEC_MUX_INTEL_PMC control the USB role switch and also the multiplexer/demultiplexer switches used with USB Type-C Alternate Modes. +config TYPEC_MUX_IT5205 + tristate "ITE IT5205 Type-C USB Alt Mode Passive MUX driver" + depends on I2C + select REGMAP_I2C + help + Driver for the ITE IT5205 Type-C USB Alternate Mode Passive MUX + which provides support for muxing DisplayPort and sideband signals + on a common USB Type-C connector. + If compiled as a module, the module will be named it5205. + config TYPEC_MUX_NB7VPQ904M tristate "On Semiconductor NB7VPQ904M Type-C redriver driver" depends on I2C diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 57dc9ac6f8..bb96f30267 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_TYPEC_MUX_FSA4480) += fsa4480.o obj-$(CONFIG_TYPEC_MUX_GPIO_SBU) += gpio-sbu-mux.o obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o +obj-$(CONFIG_TYPEC_MUX_IT5205) += it5205.o obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M) += nb7vpq904m.o obj-$(CONFIG_TYPEC_MUX_PTN36502) += ptn36502.o obj-$(CONFIG_TYPEC_MUX_WCD939X_USBSS) += wcd939x-usbss.o diff --git a/drivers/usb/typec/mux/it5205.c b/drivers/usb/typec/mux/it5205.c new file mode 100644 index 0000000000..4357cc67a8 --- /dev/null +++ b/drivers/usb/typec/mux/it5205.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ITE IT5205 Type-C USB alternate mode passive mux + * + * Copyright (c) 2020 MediaTek Inc. + * Copyright (c) 2024 Collabora Ltd. + * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> + * + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec.h> +#include <linux/usb/typec_dp.h> +#include <linux/usb/typec_mux.h> + +#define IT5205_REG_CHIP_ID(x) (0x4 + (x)) +#define IT5205FN_CHIP_ID 0x35303235 /* "5025" -> "5205" */ + +/* MUX power down register */ +#define IT5205_REG_MUXPDR 0x10 +#define IT5205_MUX_POWER_DOWN BIT(0) + +/* MUX control register */ +#define IT5205_REG_MUXCR 0x11 +#define IT5205_POLARITY_INVERTED BIT(4) +#define IT5205_DP_USB_CTRL_MASK GENMASK(3, 0) +#define IT5205_DP 0x0f +#define IT5205_DP_USB 0x03 +#define IT5205_USB 0x07 + +/* Vref Select Register */ +#define IT5205_REG_VSR 0x10 +#define IT5205_VREF_SELECT_MASK GENMASK(5, 4) +#define IT5205_VREF_SELECT_3_3V 0x00 +#define IT5205_VREF_SELECT_OFF 0x20 + +/* CSBU Over Voltage Protection Register */ +#define IT5205_REG_CSBUOVPSR 0x1e +#define IT5205_OVP_SELECT_MASK GENMASK(5, 4) +#define IT5205_OVP_3_90V 0x00 +#define IT5205_OVP_3_68V 0x10 +#define IT5205_OVP_3_62V 0x20 +#define IT5205_OVP_3_57V 0x30 + +/* CSBU Switch Register */ +#define IT5205_REG_CSBUSR 0x22 +#define IT5205_CSBUSR_SWITCH BIT(0) + +/* Interrupt Switch Register */ +#define IT5205_REG_ISR 0x25 +#define IT5205_ISR_CSBU_MASK BIT(4) +#define IT5205_ISR_CSBU_OVP BIT(0) + +struct it5205 { + struct i2c_client *client; + struct regmap *regmap; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; +}; + +static int it5205_switch_set(struct typec_switch_dev *sw, enum typec_orientation orientation) +{ + struct it5205 *it = typec_switch_get_drvdata(sw); + + switch (orientation) { + case TYPEC_ORIENTATION_NORMAL: + regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_POLARITY_INVERTED, 0); + break; + case TYPEC_ORIENTATION_REVERSE: + regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_POLARITY_INVERTED, IT5205_POLARITY_INVERTED); + break; + case TYPEC_ORIENTATION_NONE: + fallthrough; + default: + regmap_write(it->regmap, IT5205_REG_MUXCR, 0); + break; + } + + return 0; +} + +static int it5205_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + struct it5205 *it = typec_mux_get_drvdata(mux); + u8 val; + + if (state->mode >= TYPEC_STATE_MODAL && + state->alt->svid != USB_TYPEC_DP_SID) + return -EINVAL; + + switch (state->mode) { + case TYPEC_STATE_USB: + val = IT5205_USB; + break; + case TYPEC_DP_STATE_C: + fallthrough; + case TYPEC_DP_STATE_E: + val = IT5205_DP; + break; + case TYPEC_DP_STATE_D: + val = IT5205_DP_USB; + break; + case TYPEC_STATE_SAFE: + fallthrough; + default: + val = 0; + break; + } + + return regmap_update_bits(it->regmap, IT5205_REG_MUXCR, + IT5205_DP_USB_CTRL_MASK, val); +} + +static irqreturn_t it5205_irq_handler(int irq, void *data) +{ + struct it5205 *it = data; + int ret; + u32 val; + + ret = regmap_read(it->regmap, IT5205_REG_ISR, &val); + if (ret) + return IRQ_NONE; + + if (val & IT5205_ISR_CSBU_OVP) { + dev_warn(&it->client->dev, "Overvoltage detected!\n"); + + /* Reset CSBU */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, 0); + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH); + } + + return IRQ_HANDLED; +} + +static void it5205_enable_ovp(struct it5205 *it) +{ + /* Select Vref 3.3v */ + regmap_update_bits(it->regmap, IT5205_REG_VSR, + IT5205_VREF_SELECT_MASK, IT5205_VREF_SELECT_3_3V); + + /* Trigger OVP at 3.68V */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUOVPSR, + IT5205_OVP_SELECT_MASK, IT5205_OVP_3_68V); + + /* Unmask OVP interrupt */ + regmap_update_bits(it->regmap, IT5205_REG_ISR, + IT5205_ISR_CSBU_MASK, 0); + + /* Enable CSBU Interrupt */ + regmap_update_bits(it->regmap, IT5205_REG_CSBUSR, + IT5205_CSBUSR_SWITCH, IT5205_CSBUSR_SWITCH); +} + +static const struct regmap_config it5205_regmap = { + .max_register = 0x2f, + .reg_bits = 8, + .val_bits = 8, +}; + +static int it5205_probe(struct i2c_client *client) +{ + struct typec_switch_desc sw_desc = { }; + struct typec_mux_desc mux_desc = { }; + struct device *dev = &client->dev; + struct it5205 *it; + u32 val, chipid = 0; + int i, ret; + + it = devm_kzalloc(dev, sizeof(*it), GFP_KERNEL); + if (!it) + return -ENOMEM; + + ret = devm_regulator_get_enable(dev, "vcc"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + it->client = client; + + it->regmap = devm_regmap_init_i2c(client, &it5205_regmap); + if (IS_ERR(it->regmap)) + return dev_err_probe(dev, PTR_ERR(it->regmap), + "Failed to init regmap\n"); + + /* IT5205 needs a long time to power up after enabling regulator */ + msleep(50); + + /* Unset poweroff bit */ + ret = regmap_write(it->regmap, IT5205_REG_MUXPDR, 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to set power on\n"); + + /* Read the 32 bits ChipID */ + for (i = 3; i >= 0; i--) { + ret = regmap_read(it->regmap, IT5205_REG_CHIP_ID(i), &val); + if (ret) + return ret; + + chipid |= val << (i * 8); + } + + if (chipid != IT5205FN_CHIP_ID) + return dev_err_probe(dev, -EINVAL, + "Unknown ChipID 0x%x\n", chipid); + + /* Initialize as USB mode with default (non-inverted) polarity */ + ret = regmap_write(it->regmap, IT5205_REG_MUXCR, IT5205_USB); + if (ret) + return dev_err_probe(dev, ret, "Cannot set mode to USB\n"); + + sw_desc.drvdata = it; + sw_desc.fwnode = dev_fwnode(dev); + sw_desc.set = it5205_switch_set; + + it->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(it->sw)) + return dev_err_probe(dev, PTR_ERR(it->sw), + "failed to register typec switch\n"); + + mux_desc.drvdata = it; + mux_desc.fwnode = dev_fwnode(dev); + mux_desc.set = it5205_mux_set; + + it->mux = typec_mux_register(dev, &mux_desc); + if (IS_ERR(it->mux)) { + typec_switch_unregister(it->sw); + return dev_err_probe(dev, PTR_ERR(it->mux), + "failed to register typec mux\n"); + } + + i2c_set_clientdata(client, it); + + if (of_property_read_bool(dev->of_node, "ite,ovp-enable") && client->irq) { + it5205_enable_ovp(it); + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + it5205_irq_handler, + IRQF_ONESHOT, dev_name(dev), it); + if (ret) { + typec_mux_unregister(it->mux); + typec_switch_unregister(it->sw); + return dev_err_probe(dev, ret, "Failed to request irq\n"); + } + } + + return 0; +} + +static void it5205_remove(struct i2c_client *client) +{ + struct it5205 *it = i2c_get_clientdata(client); + + typec_mux_unregister(it->mux); + typec_switch_unregister(it->sw); +} + +static const struct i2c_device_id it5205_table[] = { + { "it5205" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, it5205_table); + +static const struct of_device_id it5205_of_table[] = { + { .compatible = "ite,it5205" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, it5205_of_table); + +static struct i2c_driver it5205_driver = { + .driver = { + .name = "it5205", + .of_match_table = it5205_of_table, + }, + .probe = it5205_probe, + .remove = it5205_remove, + .id_table = it5205_table, +}; +module_i2c_driver(it5205_driver); + +MODULE_AUTHOR("Tianping Fang <tianping.fang@mediatek.com>"); +MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); +MODULE_DESCRIPTION("ITE IT5205 alternate mode passive MUX driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/pd.c b/drivers/usb/typec/pd.c index b9cca2be76..d78c04a421 100644 --- a/drivers/usb/typec/pd.c +++ b/drivers/usb/typec/pd.c @@ -157,7 +157,7 @@ static const struct attribute_group source_fixed_supply_group = { }; __ATTRIBUTE_GROUPS(source_fixed_supply); -static struct device_type source_fixed_supply_type = { +static const struct device_type source_fixed_supply_type = { .name = "pdo", .release = pdo_release, .groups = source_fixed_supply_groups, @@ -182,7 +182,7 @@ static const struct attribute_group sink_fixed_supply_group = { }; __ATTRIBUTE_GROUPS(sink_fixed_supply); -static struct device_type sink_fixed_supply_type = { +static const struct device_type sink_fixed_supply_type = { .name = "pdo", .release = pdo_release, .groups = sink_fixed_supply_groups, @@ -213,7 +213,7 @@ static struct attribute *source_variable_supply_attrs[] = { }; ATTRIBUTE_GROUPS(source_variable_supply); -static struct device_type source_variable_supply_type = { +static const struct device_type source_variable_supply_type = { .name = "pdo", .release = pdo_release, .groups = source_variable_supply_groups, @@ -227,7 +227,7 @@ static struct attribute *sink_variable_supply_attrs[] = { }; ATTRIBUTE_GROUPS(sink_variable_supply); -static struct device_type sink_variable_supply_type = { +static const struct device_type sink_variable_supply_type = { .name = "pdo", .release = pdo_release, .groups = sink_variable_supply_groups, @@ -258,7 +258,7 @@ static struct attribute *source_battery_attrs[] = { }; ATTRIBUTE_GROUPS(source_battery); -static struct device_type source_battery_type = { +static const struct device_type source_battery_type = { .name = "pdo", .release = pdo_release, .groups = source_battery_groups, @@ -272,7 +272,7 @@ static struct attribute *sink_battery_attrs[] = { }; ATTRIBUTE_GROUPS(sink_battery); -static struct device_type sink_battery_type = { +static const struct device_type sink_battery_type = { .name = "pdo", .release = pdo_release, .groups = sink_battery_groups, @@ -339,7 +339,7 @@ static struct attribute *source_pps_attrs[] = { }; ATTRIBUTE_GROUPS(source_pps); -static struct device_type source_pps_type = { +static const struct device_type source_pps_type = { .name = "pdo", .release = pdo_release, .groups = source_pps_groups, @@ -353,7 +353,7 @@ static struct attribute *sink_pps_attrs[] = { }; ATTRIBUTE_GROUPS(sink_pps); -static struct device_type sink_pps_type = { +static const struct device_type sink_pps_type = { .name = "pdo", .release = pdo_release, .groups = sink_pps_groups, @@ -371,30 +371,30 @@ static const char * const apdo_supply_name[] = { [APDO_TYPE_PPS] = "programmable_supply", }; -static struct device_type *source_type[] = { +static const struct device_type *source_type[] = { [PDO_TYPE_FIXED] = &source_fixed_supply_type, [PDO_TYPE_BATT] = &source_battery_type, [PDO_TYPE_VAR] = &source_variable_supply_type, }; -static struct device_type *source_apdo_type[] = { +static const struct device_type *source_apdo_type[] = { [APDO_TYPE_PPS] = &source_pps_type, }; -static struct device_type *sink_type[] = { +static const struct device_type *sink_type[] = { [PDO_TYPE_FIXED] = &sink_fixed_supply_type, [PDO_TYPE_BATT] = &sink_battery_type, [PDO_TYPE_VAR] = &sink_variable_supply_type, }; -static struct device_type *sink_apdo_type[] = { +static const struct device_type *sink_apdo_type[] = { [APDO_TYPE_PPS] = &sink_pps_type, }; /* REVISIT: Export when EPR_*_Capabilities need to be supported. */ static int add_pdo(struct usb_power_delivery_capabilities *cap, u32 pdo, int position) { - struct device_type *type; + const struct device_type *type; const char *name; struct pdo *p; int ret; @@ -460,7 +460,7 @@ static void pd_capabilities_release(struct device *dev) kfree(to_usb_power_delivery_capabilities(dev)); } -static struct device_type pd_capabilities_type = { +static const struct device_type pd_capabilities_type = { .name = "capabilities", .release = pd_capabilities_release, }; @@ -575,7 +575,7 @@ static void pd_release(struct device *dev) kfree(pd); } -static struct device_type pd_type = { +static const struct device_type pd_type = { .name = "usb_power_delivery", .release = pd_release, .groups = pd_groups, diff --git a/drivers/usb/typec/retimer.c b/drivers/usb/typec/retimer.c index 4a7d1b5c4d..b519fcf358 100644 --- a/drivers/usb/typec/retimer.c +++ b/drivers/usb/typec/retimer.c @@ -155,6 +155,6 @@ void *typec_retimer_get_drvdata(struct typec_retimer *retimer) } EXPORT_SYMBOL_GPL(typec_retimer_get_drvdata); -struct class retimer_class = { +const struct class retimer_class = { .name = "retimer", }; diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index bc21006e97..ef18a448b7 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -1467,7 +1467,7 @@ static int fusb302_pd_read_message(struct fusb302_chip *chip, if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC)) tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); else - tcpm_pd_receive(chip->tcpm_port, msg); + tcpm_pd_receive(chip->tcpm_port, msg, TCPC_TX_SOP); return ret; } diff --git a/drivers/usb/typec/tcpm/qcom/Makefile b/drivers/usb/typec/tcpm/qcom/Makefile index dc1e8832e1..cc23042b94 100644 --- a/drivers/usb/typec/tcpm/qcom/Makefile +++ b/drivers/usb/typec/tcpm/qcom/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom_pmic_tcpm.o qcom_pmic_tcpm-y += qcom_pmic_typec.o \ qcom_pmic_typec_port.o \ - qcom_pmic_typec_pdphy.o + qcom_pmic_typec_pdphy.o \ + qcom_pmic_typec_pdphy_stub.o \ diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c index 1a2b4bddaa..d3958c061a 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c @@ -20,130 +20,15 @@ #include <drm/bridge/aux-bridge.h> +#include "qcom_pmic_typec.h" #include "qcom_pmic_typec_pdphy.h" #include "qcom_pmic_typec_port.h" struct pmic_typec_resources { - struct pmic_typec_pdphy_resources *pdphy_res; - struct pmic_typec_port_resources *port_res; + const struct pmic_typec_pdphy_resources *pdphy_res; + const struct pmic_typec_port_resources *port_res; }; -struct pmic_typec { - struct device *dev; - struct tcpm_port *tcpm_port; - struct tcpc_dev tcpc; - struct pmic_typec_pdphy *pmic_typec_pdphy; - struct pmic_typec_port *pmic_typec_port; - bool vbus_enabled; - struct mutex lock; /* VBUS state serialization */ -}; - -#define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) - -static int qcom_pmic_typec_get_vbus(struct tcpc_dev *tcpc) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - int ret; - - mutex_lock(&tcpm->lock); - ret = tcpm->vbus_enabled || qcom_pmic_typec_port_get_vbus(tcpm->pmic_typec_port); - mutex_unlock(&tcpm->lock); - - return ret; -} - -static int qcom_pmic_typec_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - int ret = 0; - - mutex_lock(&tcpm->lock); - if (tcpm->vbus_enabled == on) - goto done; - - ret = qcom_pmic_typec_port_set_vbus(tcpm->pmic_typec_port, on); - if (ret) - goto done; - - tcpm->vbus_enabled = on; - tcpm_vbus_change(tcpm->tcpm_port); - -done: - dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret); - mutex_unlock(&tcpm->lock); - - return ret; -} - -static int qcom_pmic_typec_set_vconn(struct tcpc_dev *tcpc, bool on) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_set_vconn(tcpm->pmic_typec_port, on); -} - -static int qcom_pmic_typec_get_cc(struct tcpc_dev *tcpc, - enum typec_cc_status *cc1, - enum typec_cc_status *cc2) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_get_cc(tcpm->pmic_typec_port, cc1, cc2); -} - -static int qcom_pmic_typec_set_cc(struct tcpc_dev *tcpc, - enum typec_cc_status cc) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_set_cc(tcpm->pmic_typec_port, cc); -} - -static int qcom_pmic_typec_set_polarity(struct tcpc_dev *tcpc, - enum typec_cc_polarity pol) -{ - /* Polarity is set separately by phy-qcom-qmp.c */ - return 0; -} - -static int qcom_pmic_typec_start_toggling(struct tcpc_dev *tcpc, - enum typec_port_type port_type, - enum typec_cc_status cc) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_port_start_toggling(tcpm->pmic_typec_port, - port_type, cc); -} - -static int qcom_pmic_typec_set_roles(struct tcpc_dev *tcpc, bool attached, - enum typec_role power_role, - enum typec_data_role data_role) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_pdphy_set_roles(tcpm->pmic_typec_pdphy, - data_role, power_role); -} - -static int qcom_pmic_typec_set_pd_rx(struct tcpc_dev *tcpc, bool on) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_pdphy_set_pd_rx(tcpm->pmic_typec_pdphy, on); -} - -static int qcom_pmic_typec_pd_transmit(struct tcpc_dev *tcpc, - enum tcpm_transmit_type type, - const struct pd_message *msg, - unsigned int negotiated_rev) -{ - struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); - - return qcom_pmic_typec_pdphy_pd_transmit(tcpm->pmic_typec_pdphy, type, - msg, negotiated_rev); -} - static int qcom_pmic_typec_init(struct tcpc_dev *tcpc) { return 0; @@ -157,7 +42,7 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) const struct pmic_typec_resources *res; struct regmap *regmap; struct device *bridge_dev; - u32 base[2]; + u32 base; int ret; res = of_device_get_match_data(dev); @@ -170,16 +55,6 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) tcpm->dev = dev; tcpm->tcpc.init = qcom_pmic_typec_init; - tcpm->tcpc.get_vbus = qcom_pmic_typec_get_vbus; - tcpm->tcpc.set_vbus = qcom_pmic_typec_set_vbus; - tcpm->tcpc.set_cc = qcom_pmic_typec_set_cc; - tcpm->tcpc.get_cc = qcom_pmic_typec_get_cc; - tcpm->tcpc.set_polarity = qcom_pmic_typec_set_polarity; - tcpm->tcpc.set_vconn = qcom_pmic_typec_set_vconn; - tcpm->tcpc.start_toggling = qcom_pmic_typec_start_toggling; - tcpm->tcpc.set_pd_rx = qcom_pmic_typec_set_pd_rx; - tcpm->tcpc.set_roles = qcom_pmic_typec_set_roles; - tcpm->tcpc.pd_transmit = qcom_pmic_typec_pd_transmit; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) { @@ -187,29 +62,30 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) return -ENODEV; } - ret = of_property_read_u32_array(np, "reg", base, 2); + ret = of_property_read_u32_index(np, "reg", 0, &base); if (ret) return ret; - tcpm->pmic_typec_port = qcom_pmic_typec_port_alloc(dev); - if (IS_ERR(tcpm->pmic_typec_port)) - return PTR_ERR(tcpm->pmic_typec_port); - - tcpm->pmic_typec_pdphy = qcom_pmic_typec_pdphy_alloc(dev); - if (IS_ERR(tcpm->pmic_typec_pdphy)) - return PTR_ERR(tcpm->pmic_typec_pdphy); - - ret = qcom_pmic_typec_port_probe(pdev, tcpm->pmic_typec_port, - res->port_res, regmap, base[0]); + ret = qcom_pmic_typec_port_probe(pdev, tcpm, + res->port_res, regmap, base); if (ret) return ret; - ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm->pmic_typec_pdphy, - res->pdphy_res, regmap, base[1]); - if (ret) - return ret; + if (res->pdphy_res) { + ret = of_property_read_u32_index(np, "reg", 1, &base); + if (ret) + return ret; + + ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm, + res->pdphy_res, regmap, base); + if (ret) + return ret; + } else { + ret = qcom_pmic_typec_pdphy_stub_probe(pdev, tcpm); + if (ret) + return ret; + } - mutex_init(&tcpm->lock); platform_set_drvdata(pdev, tcpm); tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); @@ -226,18 +102,20 @@ static int qcom_pmic_typec_probe(struct platform_device *pdev) goto fwnode_remove; } - ret = qcom_pmic_typec_port_start(tcpm->pmic_typec_port, - tcpm->tcpm_port); + ret = tcpm->port_start(tcpm, tcpm->tcpm_port); if (ret) - goto fwnode_remove; + goto port_unregister; - ret = qcom_pmic_typec_pdphy_start(tcpm->pmic_typec_pdphy, - tcpm->tcpm_port); + ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port); if (ret) - goto fwnode_remove; + goto port_stop; return 0; +port_stop: + tcpm->port_stop(tcpm); +port_unregister: + tcpm_unregister_port(tcpm->tcpm_port); fwnode_remove: fwnode_remove_software_node(tcpm->tcpc.fwnode); @@ -248,91 +126,25 @@ static void qcom_pmic_typec_remove(struct platform_device *pdev) { struct pmic_typec *tcpm = platform_get_drvdata(pdev); - qcom_pmic_typec_pdphy_stop(tcpm->pmic_typec_pdphy); - qcom_pmic_typec_port_stop(tcpm->pmic_typec_port); + tcpm->pdphy_stop(tcpm); + tcpm->port_stop(tcpm); tcpm_unregister_port(tcpm->tcpm_port); fwnode_remove_software_node(tcpm->tcpc.fwnode); } -static struct pmic_typec_pdphy_resources pm8150b_pdphy_res = { - .irq_params = { - { - .virq = PMIC_PDPHY_SIG_TX_IRQ, - .irq_name = "sig-tx", - }, - { - .virq = PMIC_PDPHY_SIG_RX_IRQ, - .irq_name = "sig-rx", - }, - { - .virq = PMIC_PDPHY_MSG_TX_IRQ, - .irq_name = "msg-tx", - }, - { - .virq = PMIC_PDPHY_MSG_RX_IRQ, - .irq_name = "msg-rx", - }, - { - .virq = PMIC_PDPHY_MSG_TX_FAIL_IRQ, - .irq_name = "msg-tx-failed", - }, - { - .virq = PMIC_PDPHY_MSG_TX_DISCARD_IRQ, - .irq_name = "msg-tx-discarded", - }, - { - .virq = PMIC_PDPHY_MSG_RX_DISCARD_IRQ, - .irq_name = "msg-rx-discarded", - }, - }, - .nr_irqs = 7, -}; - -static struct pmic_typec_port_resources pm8150b_port_res = { - .irq_params = { - { - .irq_name = "vpd-detect", - .virq = PMIC_TYPEC_VPD_IRQ, - }, - - { - .irq_name = "cc-state-change", - .virq = PMIC_TYPEC_CC_STATE_IRQ, - }, - { - .irq_name = "vconn-oc", - .virq = PMIC_TYPEC_VCONN_OC_IRQ, - }, - - { - .irq_name = "vbus-change", - .virq = PMIC_TYPEC_VBUS_IRQ, - }, - - { - .irq_name = "attach-detach", - .virq = PMIC_TYPEC_ATTACH_DETACH_IRQ, - }, - { - .irq_name = "legacy-cable-detect", - .virq = PMIC_TYPEC_LEGACY_CABLE_IRQ, - }, - - { - .irq_name = "try-snk-src-detect", - .virq = PMIC_TYPEC_TRY_SNK_SRC_IRQ, - }, - }, - .nr_irqs = 7, +static const struct pmic_typec_resources pm8150b_typec_res = { + .pdphy_res = &pm8150b_pdphy_res, + .port_res = &pm8150b_port_res, }; -static struct pmic_typec_resources pm8150b_typec_res = { - .pdphy_res = &pm8150b_pdphy_res, +static const struct pmic_typec_resources pmi632_typec_res = { + /* PD PHY not present */ .port_res = &pm8150b_port_res, }; static const struct of_device_id qcom_pmic_typec_table[] = { { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res }, + { .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res }, { } }; MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table); diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h new file mode 100644 index 0000000000..3c75820c91 --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2023, Linaro Ltd. All rights reserved. + */ + +#ifndef __QCOM_PMIC_TYPEC_H__ +#define __QCOM_PMIC_TYPEC_H__ + +struct pmic_typec { + struct device *dev; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc; + struct pmic_typec_pdphy *pmic_typec_pdphy; + struct pmic_typec_port *pmic_typec_port; + + int (*pdphy_start)(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port); + void (*pdphy_stop)(struct pmic_typec *tcpm); + + int (*port_start)(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port); + void (*port_stop)(struct pmic_typec *tcpm); +}; + +#define tcpc_to_tcpm(_tcpc_) container_of(_tcpc_, struct pmic_typec, tcpc) + +#endif diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c index 52c81378e3..5b7f52b74a 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.c @@ -14,8 +14,74 @@ #include <linux/slab.h> #include <linux/usb/pd.h> #include <linux/usb/tcpm.h> +#include "qcom_pmic_typec.h" #include "qcom_pmic_typec_pdphy.h" +/* PD PHY register offsets and bit fields */ +#define USB_PDPHY_MSG_CONFIG_REG 0x40 +#define MSG_CONFIG_PORT_DATA_ROLE BIT(3) +#define MSG_CONFIG_PORT_POWER_ROLE BIT(2) +#define MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0)) + +#define USB_PDPHY_EN_CONTROL_REG 0x46 +#define CONTROL_ENABLE BIT(0) + +#define USB_PDPHY_RX_STATUS_REG 0x4A +#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2)) + +#define USB_PDPHY_FRAME_FILTER_REG 0x4C +#define FRAME_FILTER_EN_HARD_RESET BIT(5) +#define FRAME_FILTER_EN_SOP BIT(0) + +#define USB_PDPHY_TX_SIZE_REG 0x42 +#define TX_SIZE_MASK 0xF + +#define USB_PDPHY_TX_CONTROL_REG 0x44 +#define TX_CONTROL_RETRY_COUNT(n) (((n) & 0x3) << 5) +#define TX_CONTROL_FRAME_TYPE(n) (((n) & 0x7) << 2) +#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2) +#define TX_CONTROL_SEND_SIGNAL BIT(1) +#define TX_CONTROL_SEND_MSG BIT(0) + +#define USB_PDPHY_RX_SIZE_REG 0x48 + +#define USB_PDPHY_RX_ACKNOWLEDGE_REG 0x4B +#define RX_BUFFER_TOKEN BIT(0) + +#define USB_PDPHY_BIST_MODE_REG 0x4E +#define BIST_MODE_MASK 0xF +#define BIST_ENABLE BIT(7) +#define PD_MSG_BIST 0x3 +#define PD_BIST_TEST_DATA_MODE 0x8 + +#define USB_PDPHY_TX_BUFFER_HDR_REG 0x60 +#define USB_PDPHY_TX_BUFFER_DATA_REG 0x62 + +#define USB_PDPHY_RX_BUFFER_REG 0x80 + +/* VDD regulator */ +#define VDD_PDPHY_VOL_MIN 2800000 /* uV */ +#define VDD_PDPHY_VOL_MAX 3300000 /* uV */ +#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ + +/* Message Spec Rev field */ +#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3) + +/* timers */ +#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */ +#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */ + +/* Interrupt numbers */ +#define PMIC_PDPHY_SIG_TX_IRQ 0x0 +#define PMIC_PDPHY_SIG_RX_IRQ 0x1 +#define PMIC_PDPHY_MSG_TX_IRQ 0x2 +#define PMIC_PDPHY_MSG_RX_IRQ 0x3 +#define PMIC_PDPHY_MSG_TX_FAIL_IRQ 0x4 +#define PMIC_PDPHY_MSG_TX_DISCARD_IRQ 0x5 +#define PMIC_PDPHY_MSG_RX_DISCARD_IRQ 0x6 +#define PMIC_PDPHY_FR_SWAP_IRQ 0x7 + + struct pmic_typec_pdphy_irq_data { int virq; int irq; @@ -231,11 +297,13 @@ done: return ret; } -int qcom_pmic_typec_pdphy_pd_transmit(struct pmic_typec_pdphy *pmic_typec_pdphy, - enum tcpm_transmit_type type, - const struct pd_message *msg, - unsigned int negotiated_rev) +static int qcom_pmic_typec_pdphy_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; struct device *dev = pmic_typec_pdphy->dev; int ret; @@ -299,7 +367,7 @@ done: if (!ret) { dev_vdbg(dev, "pd_receive: handing %d bytes to tcpm\n", size); - tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg); + tcpm_pd_receive(pmic_typec_pdphy->tcpm_port, &msg, TCPC_TX_SOP); } } @@ -336,8 +404,10 @@ static irqreturn_t qcom_pmic_typec_pdphy_isr(int irq, void *dev_id) return IRQ_HANDLED; } -int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, bool on) +static int qcom_pmic_typec_pdphy_set_pd_rx(struct tcpc_dev *tcpc, bool on) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; unsigned long flags; int ret; @@ -353,9 +423,12 @@ int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, b return ret; } -int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, - bool data_role_host, bool power_role_src) +static int qcom_pmic_typec_pdphy_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role power_role, + enum typec_data_role data_role) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; struct device *dev = pmic_typec_pdphy->dev; unsigned long flags; int ret; @@ -366,12 +439,13 @@ int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, pmic_typec_pdphy->base + USB_PDPHY_MSG_CONFIG_REG, MSG_CONFIG_PORT_DATA_ROLE | MSG_CONFIG_PORT_POWER_ROLE, - data_role_host << 3 | power_role_src << 2); + (data_role == TYPEC_HOST ? MSG_CONFIG_PORT_DATA_ROLE : 0) | + (power_role == TYPEC_SOURCE ? MSG_CONFIG_PORT_POWER_ROLE : 0)); spin_unlock_irqrestore(&pmic_typec_pdphy->lock, flags); dev_dbg(dev, "pdphy_set_roles: data_role_host=%d power_role_src=%d\n", - data_role_host, power_role_src); + data_role, power_role); return ret; } @@ -401,10 +475,8 @@ static int qcom_pmic_typec_pdphy_enable(struct pmic_typec_pdphy *pmic_typec_pdph qcom_pmic_typec_pdphy_reset_off(pmic_typec_pdphy); done: - if (ret) { - regulator_disable(pmic_typec_pdphy->vdd_pdphy); + if (ret) dev_err(dev, "pdphy_enable fail %d\n", ret); - } return ret; } @@ -435,9 +507,10 @@ done: return ret; } -int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, - struct tcpm_port *tcpm_port) +static int qcom_pmic_typec_pdphy_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) { + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; int i; int ret; @@ -449,16 +522,22 @@ int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, ret = pmic_typec_pdphy_reset(pmic_typec_pdphy); if (ret) - return ret; + goto err_disable_vdd_pdhy; for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) enable_irq(pmic_typec_pdphy->irq_data[i].irq); return 0; + +err_disable_vdd_pdhy: + regulator_disable(pmic_typec_pdphy->vdd_pdphy); + + return ret; } -void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy) +static void qcom_pmic_typec_pdphy_stop(struct pmic_typec *tcpm) { + struct pmic_typec_pdphy *pmic_typec_pdphy = tcpm->pmic_typec_pdphy; int i; for (i = 0; i < pmic_typec_pdphy->nr_irqs; i++) @@ -469,21 +548,21 @@ void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy) regulator_disable(pmic_typec_pdphy->vdd_pdphy); } -struct pmic_typec_pdphy *qcom_pmic_typec_pdphy_alloc(struct device *dev) -{ - return devm_kzalloc(dev, sizeof(struct pmic_typec_pdphy), GFP_KERNEL); -} - int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, - struct pmic_typec_pdphy *pmic_typec_pdphy, - struct pmic_typec_pdphy_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_pdphy_resources *res, struct regmap *regmap, u32 base) { + struct pmic_typec_pdphy *pmic_typec_pdphy; struct device *dev = &pdev->dev; struct pmic_typec_pdphy_irq_data *irq_data; int i, ret, irq; + pmic_typec_pdphy = devm_kzalloc(dev, sizeof(*pmic_typec_pdphy), GFP_KERNEL); + if (!pmic_typec_pdphy) + return -ENOMEM; + if (!res->nr_irqs || res->nr_irqs > PMIC_PDPHY_MAX_IRQS) return -EINVAL; @@ -522,5 +601,48 @@ int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, return ret; } + tcpm->pmic_typec_pdphy = pmic_typec_pdphy; + + tcpm->tcpc.set_pd_rx = qcom_pmic_typec_pdphy_set_pd_rx; + tcpm->tcpc.set_roles = qcom_pmic_typec_pdphy_set_roles; + tcpm->tcpc.pd_transmit = qcom_pmic_typec_pdphy_pd_transmit; + + tcpm->pdphy_start = qcom_pmic_typec_pdphy_start; + tcpm->pdphy_stop = qcom_pmic_typec_pdphy_stop; + return 0; } + +const struct pmic_typec_pdphy_resources pm8150b_pdphy_res = { + .irq_params = { + { + .virq = PMIC_PDPHY_SIG_TX_IRQ, + .irq_name = "sig-tx", + }, + { + .virq = PMIC_PDPHY_SIG_RX_IRQ, + .irq_name = "sig-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_IRQ, + .irq_name = "msg-tx", + }, + { + .virq = PMIC_PDPHY_MSG_RX_IRQ, + .irq_name = "msg-rx", + }, + { + .virq = PMIC_PDPHY_MSG_TX_FAIL_IRQ, + .irq_name = "msg-tx-failed", + }, + { + .virq = PMIC_PDPHY_MSG_TX_DISCARD_IRQ, + .irq_name = "msg-tx-discarded", + }, + { + .virq = PMIC_PDPHY_MSG_RX_DISCARD_IRQ, + .irq_name = "msg-rx-discarded", + }, + }, + .nr_irqs = 7, +}; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h index e67954e31b..04dee20293 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy.h @@ -8,74 +8,6 @@ #include <linux/platform_device.h> #include <linux/regmap.h> -#include <linux/usb/tcpm.h> - -#define USB_PDPHY_MAX_DATA_OBJ_LEN 28 -#define USB_PDPHY_MSG_HDR_LEN 2 - -/* PD PHY register offsets and bit fields */ -#define USB_PDPHY_MSG_CONFIG_REG 0x40 -#define MSG_CONFIG_PORT_DATA_ROLE BIT(3) -#define MSG_CONFIG_PORT_POWER_ROLE BIT(2) -#define MSG_CONFIG_SPEC_REV_MASK (BIT(1) | BIT(0)) - -#define USB_PDPHY_EN_CONTROL_REG 0x46 -#define CONTROL_ENABLE BIT(0) - -#define USB_PDPHY_RX_STATUS_REG 0x4A -#define RX_FRAME_TYPE (BIT(0) | BIT(1) | BIT(2)) - -#define USB_PDPHY_FRAME_FILTER_REG 0x4C -#define FRAME_FILTER_EN_HARD_RESET BIT(5) -#define FRAME_FILTER_EN_SOP BIT(0) - -#define USB_PDPHY_TX_SIZE_REG 0x42 -#define TX_SIZE_MASK 0xF - -#define USB_PDPHY_TX_CONTROL_REG 0x44 -#define TX_CONTROL_RETRY_COUNT(n) (((n) & 0x3) << 5) -#define TX_CONTROL_FRAME_TYPE(n) (((n) & 0x7) << 2) -#define TX_CONTROL_FRAME_TYPE_CABLE_RESET (0x1 << 2) -#define TX_CONTROL_SEND_SIGNAL BIT(1) -#define TX_CONTROL_SEND_MSG BIT(0) - -#define USB_PDPHY_RX_SIZE_REG 0x48 - -#define USB_PDPHY_RX_ACKNOWLEDGE_REG 0x4B -#define RX_BUFFER_TOKEN BIT(0) - -#define USB_PDPHY_BIST_MODE_REG 0x4E -#define BIST_MODE_MASK 0xF -#define BIST_ENABLE BIT(7) -#define PD_MSG_BIST 0x3 -#define PD_BIST_TEST_DATA_MODE 0x8 - -#define USB_PDPHY_TX_BUFFER_HDR_REG 0x60 -#define USB_PDPHY_TX_BUFFER_DATA_REG 0x62 - -#define USB_PDPHY_RX_BUFFER_REG 0x80 - -/* VDD regulator */ -#define VDD_PDPHY_VOL_MIN 2800000 /* uV */ -#define VDD_PDPHY_VOL_MAX 3300000 /* uV */ -#define VDD_PDPHY_HPM_LOAD 3000 /* uA */ - -/* Message Spec Rev field */ -#define PD_MSG_HDR_REV(hdr) (((hdr) >> 6) & 3) - -/* timers */ -#define RECEIVER_RESPONSE_TIME 15 /* tReceiverResponse */ -#define HARD_RESET_COMPLETE_TIME 5 /* tHardResetComplete */ - -/* Interrupt numbers */ -#define PMIC_PDPHY_SIG_TX_IRQ 0x0 -#define PMIC_PDPHY_SIG_RX_IRQ 0x1 -#define PMIC_PDPHY_MSG_TX_IRQ 0x2 -#define PMIC_PDPHY_MSG_RX_IRQ 0x3 -#define PMIC_PDPHY_MSG_TX_FAIL_IRQ 0x4 -#define PMIC_PDPHY_MSG_TX_DISCARD_IRQ 0x5 -#define PMIC_PDPHY_MSG_RX_DISCARD_IRQ 0x6 -#define PMIC_PDPHY_FR_SWAP_IRQ 0x7 /* Resources */ #define PMIC_PDPHY_MAX_IRQS 0x08 @@ -87,33 +19,19 @@ struct pmic_typec_pdphy_irq_params { struct pmic_typec_pdphy_resources { unsigned int nr_irqs; - struct pmic_typec_pdphy_irq_params irq_params[PMIC_PDPHY_MAX_IRQS]; + const struct pmic_typec_pdphy_irq_params irq_params[PMIC_PDPHY_MAX_IRQS]; }; /* API */ struct pmic_typec_pdphy; -struct pmic_typec_pdphy *qcom_pmic_typec_pdphy_alloc(struct device *dev); - +extern const struct pmic_typec_pdphy_resources pm8150b_pdphy_res; int qcom_pmic_typec_pdphy_probe(struct platform_device *pdev, - struct pmic_typec_pdphy *pmic_typec_pdphy, - struct pmic_typec_pdphy_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_pdphy_resources *res, struct regmap *regmap, u32 base); - -int qcom_pmic_typec_pdphy_start(struct pmic_typec_pdphy *pmic_typec_pdphy, - struct tcpm_port *tcpm_port); - -void qcom_pmic_typec_pdphy_stop(struct pmic_typec_pdphy *pmic_typec_pdphy); - -int qcom_pmic_typec_pdphy_set_roles(struct pmic_typec_pdphy *pmic_typec_pdphy, - bool power_role_src, bool data_role_host); - -int qcom_pmic_typec_pdphy_set_pd_rx(struct pmic_typec_pdphy *pmic_typec_pdphy, bool on); - -int qcom_pmic_typec_pdphy_pd_transmit(struct pmic_typec_pdphy *pmic_typec_pdphy, - enum tcpm_transmit_type type, - const struct pd_message *msg, - unsigned int negotiated_rev); +int qcom_pmic_typec_pdphy_stub_probe(struct platform_device *pdev, + struct pmic_typec *tcpm); #endif /* __QCOM_PMIC_TYPEC_PDPHY_H__ */ diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c new file mode 100644 index 0000000000..df79059cda --- /dev/null +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_pdphy_stub.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2024, Linaro Ltd. All rights reserved. + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/usb/pd.h> +#include <linux/usb/tcpm.h> +#include "qcom_pmic_typec.h" +#include "qcom_pmic_typec_pdphy.h" + +static int qcom_pmic_typec_pdphy_stub_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg, + unsigned int negotiated_rev) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct device *dev = tcpm->dev; + + dev_dbg(dev, "pdphy_transmit: type=%d\n", type); + + tcpm_pd_transmit_complete(tcpm->tcpm_port, + TCPC_TX_SUCCESS); + + return 0; +} + +static int qcom_pmic_typec_pdphy_stub_set_pd_rx(struct tcpc_dev *tcpc, bool on) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct device *dev = tcpm->dev; + + dev_dbg(dev, "set_pd_rx: %s\n", on ? "on" : "off"); + + return 0; +} + +static int qcom_pmic_typec_pdphy_stub_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role power_role, + enum typec_data_role data_role) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct device *dev = tcpm->dev; + + dev_dbg(dev, "pdphy_set_roles: data_role_host=%d power_role_src=%d\n", + data_role, power_role); + + return 0; +} + +static int qcom_pmic_typec_pdphy_stub_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) +{ + return 0; +} + +static void qcom_pmic_typec_pdphy_stub_stop(struct pmic_typec *tcpm) +{ +} + +int qcom_pmic_typec_pdphy_stub_probe(struct platform_device *pdev, + struct pmic_typec *tcpm) +{ + tcpm->tcpc.set_pd_rx = qcom_pmic_typec_pdphy_stub_set_pd_rx; + tcpm->tcpc.set_roles = qcom_pmic_typec_pdphy_stub_set_roles; + tcpm->tcpc.pd_transmit = qcom_pmic_typec_pdphy_stub_pd_transmit; + + tcpm->pdphy_start = qcom_pmic_typec_pdphy_stub_start; + tcpm->pdphy_stop = qcom_pmic_typec_pdphy_stub_stop; + + return 0; +} diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c index a8f3f4d3a4..a747baa297 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.c @@ -16,8 +16,147 @@ #include <linux/usb/tcpm.h> #include <linux/usb/typec_mux.h> #include <linux/workqueue.h> + +#include "qcom_pmic_typec.h" #include "qcom_pmic_typec_port.h" +#define TYPEC_SNK_STATUS_REG 0x06 +#define DETECTED_SNK_TYPE_MASK GENMASK(6, 0) +#define SNK_DAM_MASK GENMASK(6, 4) +#define SNK_DAM_500MA BIT(6) +#define SNK_DAM_1500MA BIT(5) +#define SNK_DAM_3000MA BIT(4) +#define SNK_RP_STD BIT(3) +#define SNK_RP_1P5 BIT(2) +#define SNK_RP_3P0 BIT(1) +#define SNK_RP_SHORT BIT(0) + +#define TYPEC_SRC_STATUS_REG 0x08 +#define DETECTED_SRC_TYPE_MASK GENMASK(4, 0) +#define SRC_HIGH_BATT BIT(5) +#define SRC_DEBUG_ACCESS BIT(4) +#define SRC_RD_OPEN BIT(3) +#define SRC_RD_RA_VCONN BIT(2) +#define SRC_RA_OPEN BIT(1) +#define AUDIO_ACCESS_RA_RA BIT(0) + +#define TYPEC_STATE_MACHINE_STATUS_REG 0x09 +#define TYPEC_ATTACH_DETACH_STATE BIT(5) + +#define TYPEC_SM_STATUS_REG 0x0A +#define TYPEC_SM_VBUS_VSAFE5V BIT(5) +#define TYPEC_SM_VBUS_VSAFE0V BIT(6) +#define TYPEC_SM_USBIN_LT_LV BIT(7) + +#define TYPEC_MISC_STATUS_REG 0x0B +#define TYPEC_WATER_DETECTION_STATUS BIT(7) +#define SNK_SRC_MODE BIT(6) +#define TYPEC_VBUS_DETECT BIT(5) +#define TYPEC_VBUS_ERROR_STATUS BIT(4) +#define TYPEC_DEBOUNCE_DONE BIT(3) +#define CC_ORIENTATION BIT(1) +#define CC_ATTACHED BIT(0) + +#define LEGACY_CABLE_STATUS_REG 0x0D +#define TYPEC_LEGACY_CABLE_STATUS BIT(1) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS BIT(0) + +#define TYPEC_U_USB_STATUS_REG 0x0F +#define U_USB_GROUND_NOVBUS BIT(6) +#define U_USB_GROUND BIT(4) +#define U_USB_FMB1 BIT(3) +#define U_USB_FLOAT1 BIT(2) +#define U_USB_FMB2 BIT(1) +#define U_USB_FLOAT2 BIT(0) + +#define TYPEC_MODE_CFG_REG 0x44 +#define TYPEC_TRY_MODE_MASK GENMASK(4, 3) +#define EN_TRY_SNK BIT(4) +#define EN_TRY_SRC BIT(3) +#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) +#define EN_SRC_ONLY BIT(2) +#define EN_SNK_ONLY BIT(1) +#define TYPEC_DISABLE_CMD BIT(0) + +#define TYPEC_VCONN_CONTROL_REG 0x46 +#define VCONN_EN_ORIENTATION BIT(2) +#define VCONN_EN_VALUE BIT(1) +#define VCONN_EN_SRC BIT(0) + +#define TYPEC_CCOUT_CONTROL_REG 0x48 +#define TYPEC_CCOUT_BUFFER_EN BIT(2) +#define TYPEC_CCOUT_VALUE BIT(1) +#define TYPEC_CCOUT_SRC BIT(0) + +#define DEBUG_ACCESS_SRC_CFG_REG 0x4C +#define EN_UNORIENTED_DEBUG_ACCESS_SRC BIT(0) + +#define TYPE_C_CRUDE_SENSOR_CFG_REG 0x4e +#define EN_SRC_CRUDE_SENSOR BIT(1) +#define EN_SNK_CRUDE_SENSOR BIT(0) + +#define TYPEC_EXIT_STATE_CFG_REG 0x50 +#define BYPASS_VSAFE0V_DURING_ROLE_SWAP BIT(3) +#define SEL_SRC_UPPER_REF BIT(2) +#define USE_TPD_FOR_EXITING_ATTACHSRC BIT(1) +#define EXIT_SNK_BASED_ON_CC BIT(0) + +#define TYPEC_CURRSRC_CFG_REG 0x52 +#define TYPEC_SRC_RP_SEL_330UA BIT(1) +#define TYPEC_SRC_RP_SEL_180UA BIT(0) +#define TYPEC_SRC_RP_SEL_80UA 0 +#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0) + +#define TYPEC_INTERRUPT_EN_CFG_1_REG 0x5E +#define TYPEC_LEGACY_CABLE_INT_EN BIT(7) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN BIT(6) +#define TYPEC_TRYSOURCE_DETECT_INT_EN BIT(5) +#define TYPEC_TRYSINK_DETECT_INT_EN BIT(4) +#define TYPEC_CCOUT_DETACH_INT_EN BIT(3) +#define TYPEC_CCOUT_ATTACH_INT_EN BIT(2) +#define TYPEC_VBUS_DEASSERT_INT_EN BIT(1) +#define TYPEC_VBUS_ASSERT_INT_EN BIT(0) + +#define TYPEC_INTERRUPT_EN_CFG_2_REG 0x60 +#define TYPEC_SRC_BATT_HPWR_INT_EN BIT(6) +#define MICRO_USB_STATE_CHANGE_INT_EN BIT(5) +#define TYPEC_STATE_MACHINE_CHANGE_INT_EN BIT(4) +#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN BIT(3) +#define TYPEC_WATER_DETECTION_INT_EN BIT(2) +#define TYPEC_VBUS_ERROR_INT_EN BIT(1) +#define TYPEC_DEBOUNCE_DONE_INT_EN BIT(0) + +#define TYPEC_DEBOUNCE_OPTION_REG 0x62 +#define REDUCE_TCCDEBOUNCE_TO_2MS BIT(2) + +#define TYPE_C_SBU_CFG_REG 0x6A +#define SEL_SBU1_ISRC_VAL 0x04 +#define SEL_SBU2_ISRC_VAL 0x01 + +#define TYPEC_U_USB_CFG_REG 0x70 +#define EN_MICRO_USB_FACTORY_MODE BIT(1) +#define EN_MICRO_USB_MODE BIT(0) + +#define TYPEC_PMI632_U_USB_WATER_PROTECTION_CFG_REG 0x72 + +#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG 0x73 +#define EN_MICRO_USB_WATER_PROTECTION BIT(4) +#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2) +#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0) + +#define TYPEC_PMI632_MICRO_USB_MODE_REG 0x73 +#define MICRO_USB_MODE_ONLY BIT(0) + +/* Interrupt numbers */ +#define PMIC_TYPEC_OR_RID_IRQ 0x0 +#define PMIC_TYPEC_VPD_IRQ 0x1 +#define PMIC_TYPEC_CC_STATE_IRQ 0x2 +#define PMIC_TYPEC_VCONN_OC_IRQ 0x3 +#define PMIC_TYPEC_VBUS_IRQ 0x4 +#define PMIC_TYPEC_ATTACH_DETACH_IRQ 0x5 +#define PMIC_TYPEC_LEGACY_CABLE_IRQ 0x6 +#define PMIC_TYPEC_TRY_SNK_SRC_IRQ 0x7 + struct pmic_typec_port_irq_data { int virq; int irq; @@ -33,6 +172,8 @@ struct pmic_typec_port { struct pmic_typec_port_irq_data *irq_data; struct regulator *vdd_vbus; + bool vbus_enabled; + struct mutex vbus_lock; /* VBUS state serialization */ int cc; bool debouncing_cc; @@ -131,7 +272,7 @@ done: return IRQ_HANDLED; } -int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port) +static int qcom_pmic_typec_port_vbus_detect(struct pmic_typec_port *pmic_typec_port) { struct device *dev = pmic_typec_port->dev; unsigned int misc; @@ -148,7 +289,7 @@ int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port) return !!(misc & TYPEC_VBUS_DETECT); } -int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool on) +static int qcom_pmic_typec_port_vbus_toggle(struct pmic_typec_port *pmic_typec_port, bool on) { u32 sm_stat; u32 val; @@ -179,10 +320,49 @@ int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool return 0; } -int qcom_pmic_typec_port_get_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status *cc1, - enum typec_cc_status *cc2) +static int qcom_pmic_typec_port_get_vbus(struct tcpc_dev *tcpc) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int ret; + + mutex_lock(&pmic_typec_port->vbus_lock); + ret = pmic_typec_port->vbus_enabled || qcom_pmic_typec_port_vbus_detect(pmic_typec_port); + mutex_unlock(&pmic_typec_port->vbus_lock); + + return ret; +} + +static int qcom_pmic_typec_port_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) +{ + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; + int ret = 0; + + mutex_lock(&pmic_typec_port->vbus_lock); + if (pmic_typec_port->vbus_enabled == on) + goto done; + + ret = qcom_pmic_typec_port_vbus_toggle(pmic_typec_port, on); + if (ret) + goto done; + + pmic_typec_port->vbus_enabled = on; + tcpm_vbus_change(tcpm->tcpm_port); + +done: + dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret); + mutex_unlock(&pmic_typec_port->vbus_lock); + + return ret; +} + +static int qcom_pmic_typec_port_get_cc(struct tcpc_dev *tcpc, + enum typec_cc_status *cc1, + enum typec_cc_status *cc2) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int misc, val; bool attached; @@ -275,9 +455,11 @@ static void qcom_pmic_set_cc_debounce(struct pmic_typec_port *pmic_typec_port) msecs_to_jiffies(2)); } -int qcom_pmic_typec_port_set_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status cc) +static int qcom_pmic_typec_port_set_cc(struct tcpc_dev *tcpc, + enum typec_cc_status cc) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int mode, currsrc; unsigned int misc; @@ -341,8 +523,17 @@ done: return ret; } -int qcom_pmic_typec_port_set_vconn(struct pmic_typec_port *pmic_typec_port, bool on) +static int qcom_pmic_typec_port_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity pol) +{ + /* Polarity is set separately by phy-qcom-qmp.c */ + return 0; +} + +static int qcom_pmic_typec_port_set_vconn(struct tcpc_dev *tcpc, bool on) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int orientation, misc, mask, value; unsigned long flags; @@ -377,10 +568,12 @@ done: return ret; } -int qcom_pmic_typec_port_start_toggling(struct pmic_typec_port *pmic_typec_port, - enum typec_port_type port_type, - enum typec_cc_status cc) +static int qcom_pmic_typec_port_start_toggling(struct tcpc_dev *tcpc, + enum typec_port_type port_type, + enum typec_cc_status cc) { + struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc); + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; struct device *dev = pmic_typec_port->dev; unsigned int misc; u8 mode = 0; @@ -441,9 +634,10 @@ done: (TYPEC_STATE_MACHINE_CHANGE_INT_EN | TYPEC_VBUS_ERROR_INT_EN | \ TYPEC_DEBOUNCE_DONE_INT_EN) -int qcom_pmic_typec_port_start(struct pmic_typec_port *pmic_typec_port, - struct tcpm_port *tcpm_port) +static int qcom_pmic_typec_port_start(struct pmic_typec *tcpm, + struct tcpm_port *tcpm_port) { + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; int i; int mask; int ret; @@ -491,29 +685,30 @@ done: return ret; } -void qcom_pmic_typec_port_stop(struct pmic_typec_port *pmic_typec_port) +static void qcom_pmic_typec_port_stop(struct pmic_typec *tcpm) { + struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port; int i; for (i = 0; i < pmic_typec_port->nr_irqs; i++) disable_irq(pmic_typec_port->irq_data[i].irq); } -struct pmic_typec_port *qcom_pmic_typec_port_alloc(struct device *dev) -{ - return devm_kzalloc(dev, sizeof(struct pmic_typec_port), GFP_KERNEL); -} - int qcom_pmic_typec_port_probe(struct platform_device *pdev, - struct pmic_typec_port *pmic_typec_port, - struct pmic_typec_port_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_port_resources *res, struct regmap *regmap, u32 base) { struct device *dev = &pdev->dev; struct pmic_typec_port_irq_data *irq_data; + struct pmic_typec_port *pmic_typec_port; int i, ret, irq; + pmic_typec_port = devm_kzalloc(dev, sizeof(*pmic_typec_port), GFP_KERNEL); + if (!pmic_typec_port) + return -ENOMEM; + if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS) return -EINVAL; @@ -522,6 +717,8 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, if (!irq_data) return -ENOMEM; + mutex_init(&pmic_typec_port->vbus_lock); + pmic_typec_port->vdd_vbus = devm_regulator_get(dev, "vdd-vbus"); if (IS_ERR(pmic_typec_port->vdd_vbus)) return PTR_ERR(pmic_typec_port->vdd_vbus); @@ -556,5 +753,56 @@ int qcom_pmic_typec_port_probe(struct platform_device *pdev, return ret; } + tcpm->pmic_typec_port = pmic_typec_port; + + tcpm->tcpc.get_vbus = qcom_pmic_typec_port_get_vbus; + tcpm->tcpc.set_vbus = qcom_pmic_typec_port_set_vbus; + tcpm->tcpc.set_cc = qcom_pmic_typec_port_set_cc; + tcpm->tcpc.get_cc = qcom_pmic_typec_port_get_cc; + tcpm->tcpc.set_polarity = qcom_pmic_typec_port_set_polarity; + tcpm->tcpc.set_vconn = qcom_pmic_typec_port_set_vconn; + tcpm->tcpc.start_toggling = qcom_pmic_typec_port_start_toggling; + + tcpm->port_start = qcom_pmic_typec_port_start; + tcpm->port_stop = qcom_pmic_typec_port_stop; + return 0; } + +const struct pmic_typec_port_resources pm8150b_port_res = { + .irq_params = { + { + .irq_name = "vpd-detect", + .virq = PMIC_TYPEC_VPD_IRQ, + }, + + { + .irq_name = "cc-state-change", + .virq = PMIC_TYPEC_CC_STATE_IRQ, + }, + { + .irq_name = "vconn-oc", + .virq = PMIC_TYPEC_VCONN_OC_IRQ, + }, + + { + .irq_name = "vbus-change", + .virq = PMIC_TYPEC_VBUS_IRQ, + }, + + { + .irq_name = "attach-detach", + .virq = PMIC_TYPEC_ATTACH_DETACH_IRQ, + }, + { + .irq_name = "legacy-cable-detect", + .virq = PMIC_TYPEC_LEGACY_CABLE_IRQ, + }, + + { + .irq_name = "try-snk-src-detect", + .virq = PMIC_TYPEC_TRY_SNK_SRC_IRQ, + }, + }, + .nr_irqs = 7, +}; diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h index d4d358c680..2ca83a46cf 100644 --- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h +++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port.h @@ -3,149 +3,12 @@ * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. * Copyright (c) 2023, Linaro Ltd. All rights reserved. */ -#ifndef __QCOM_PMIC_TYPEC_H__ -#define __QCOM_PMIC_TYPEC_H__ +#ifndef __QCOM_PMIC_TYPEC_PORT_H__ +#define __QCOM_PMIC_TYPEC_PORT_H__ #include <linux/platform_device.h> #include <linux/usb/tcpm.h> -#define TYPEC_SNK_STATUS_REG 0x06 -#define DETECTED_SNK_TYPE_MASK GENMASK(6, 0) -#define SNK_DAM_MASK GENMASK(6, 4) -#define SNK_DAM_500MA BIT(6) -#define SNK_DAM_1500MA BIT(5) -#define SNK_DAM_3000MA BIT(4) -#define SNK_RP_STD BIT(3) -#define SNK_RP_1P5 BIT(2) -#define SNK_RP_3P0 BIT(1) -#define SNK_RP_SHORT BIT(0) - -#define TYPEC_SRC_STATUS_REG 0x08 -#define DETECTED_SRC_TYPE_MASK GENMASK(4, 0) -#define SRC_HIGH_BATT BIT(5) -#define SRC_DEBUG_ACCESS BIT(4) -#define SRC_RD_OPEN BIT(3) -#define SRC_RD_RA_VCONN BIT(2) -#define SRC_RA_OPEN BIT(1) -#define AUDIO_ACCESS_RA_RA BIT(0) - -#define TYPEC_STATE_MACHINE_STATUS_REG 0x09 -#define TYPEC_ATTACH_DETACH_STATE BIT(5) - -#define TYPEC_SM_STATUS_REG 0x0A -#define TYPEC_SM_VBUS_VSAFE5V BIT(5) -#define TYPEC_SM_VBUS_VSAFE0V BIT(6) -#define TYPEC_SM_USBIN_LT_LV BIT(7) - -#define TYPEC_MISC_STATUS_REG 0x0B -#define TYPEC_WATER_DETECTION_STATUS BIT(7) -#define SNK_SRC_MODE BIT(6) -#define TYPEC_VBUS_DETECT BIT(5) -#define TYPEC_VBUS_ERROR_STATUS BIT(4) -#define TYPEC_DEBOUNCE_DONE BIT(3) -#define CC_ORIENTATION BIT(1) -#define CC_ATTACHED BIT(0) - -#define LEGACY_CABLE_STATUS_REG 0x0D -#define TYPEC_LEGACY_CABLE_STATUS BIT(1) -#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS BIT(0) - -#define TYPEC_U_USB_STATUS_REG 0x0F -#define U_USB_GROUND_NOVBUS BIT(6) -#define U_USB_GROUND BIT(4) -#define U_USB_FMB1 BIT(3) -#define U_USB_FLOAT1 BIT(2) -#define U_USB_FMB2 BIT(1) -#define U_USB_FLOAT2 BIT(0) - -#define TYPEC_MODE_CFG_REG 0x44 -#define TYPEC_TRY_MODE_MASK GENMASK(4, 3) -#define EN_TRY_SNK BIT(4) -#define EN_TRY_SRC BIT(3) -#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) -#define EN_SRC_ONLY BIT(2) -#define EN_SNK_ONLY BIT(1) -#define TYPEC_DISABLE_CMD BIT(0) - -#define TYPEC_VCONN_CONTROL_REG 0x46 -#define VCONN_EN_ORIENTATION BIT(2) -#define VCONN_EN_VALUE BIT(1) -#define VCONN_EN_SRC BIT(0) - -#define TYPEC_CCOUT_CONTROL_REG 0x48 -#define TYPEC_CCOUT_BUFFER_EN BIT(2) -#define TYPEC_CCOUT_VALUE BIT(1) -#define TYPEC_CCOUT_SRC BIT(0) - -#define DEBUG_ACCESS_SRC_CFG_REG 0x4C -#define EN_UNORIENTED_DEBUG_ACCESS_SRC BIT(0) - -#define TYPE_C_CRUDE_SENSOR_CFG_REG 0x4e -#define EN_SRC_CRUDE_SENSOR BIT(1) -#define EN_SNK_CRUDE_SENSOR BIT(0) - -#define TYPEC_EXIT_STATE_CFG_REG 0x50 -#define BYPASS_VSAFE0V_DURING_ROLE_SWAP BIT(3) -#define SEL_SRC_UPPER_REF BIT(2) -#define USE_TPD_FOR_EXITING_ATTACHSRC BIT(1) -#define EXIT_SNK_BASED_ON_CC BIT(0) - -#define TYPEC_CURRSRC_CFG_REG 0x52 -#define TYPEC_SRC_RP_SEL_330UA BIT(1) -#define TYPEC_SRC_RP_SEL_180UA BIT(0) -#define TYPEC_SRC_RP_SEL_80UA 0 -#define TYPEC_SRC_RP_SEL_MASK GENMASK(1, 0) - -#define TYPEC_INTERRUPT_EN_CFG_1_REG 0x5E -#define TYPEC_LEGACY_CABLE_INT_EN BIT(7) -#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN BIT(6) -#define TYPEC_TRYSOURCE_DETECT_INT_EN BIT(5) -#define TYPEC_TRYSINK_DETECT_INT_EN BIT(4) -#define TYPEC_CCOUT_DETACH_INT_EN BIT(3) -#define TYPEC_CCOUT_ATTACH_INT_EN BIT(2) -#define TYPEC_VBUS_DEASSERT_INT_EN BIT(1) -#define TYPEC_VBUS_ASSERT_INT_EN BIT(0) - -#define TYPEC_INTERRUPT_EN_CFG_2_REG 0x60 -#define TYPEC_SRC_BATT_HPWR_INT_EN BIT(6) -#define MICRO_USB_STATE_CHANGE_INT_EN BIT(5) -#define TYPEC_STATE_MACHINE_CHANGE_INT_EN BIT(4) -#define TYPEC_DEBUG_ACCESS_DETECT_INT_EN BIT(3) -#define TYPEC_WATER_DETECTION_INT_EN BIT(2) -#define TYPEC_VBUS_ERROR_INT_EN BIT(1) -#define TYPEC_DEBOUNCE_DONE_INT_EN BIT(0) - -#define TYPEC_DEBOUNCE_OPTION_REG 0x62 -#define REDUCE_TCCDEBOUNCE_TO_2MS BIT(2) - -#define TYPE_C_SBU_CFG_REG 0x6A -#define SEL_SBU1_ISRC_VAL 0x04 -#define SEL_SBU2_ISRC_VAL 0x01 - -#define TYPEC_U_USB_CFG_REG 0x70 -#define EN_MICRO_USB_FACTORY_MODE BIT(1) -#define EN_MICRO_USB_MODE BIT(0) - -#define TYPEC_PMI632_U_USB_WATER_PROTECTION_CFG_REG 0x72 - -#define TYPEC_U_USB_WATER_PROTECTION_CFG_REG 0x73 -#define EN_MICRO_USB_WATER_PROTECTION BIT(4) -#define MICRO_USB_DETECTION_ON_TIME_CFG_MASK GENMASK(3, 2) -#define MICRO_USB_DETECTION_PERIOD_CFG_MASK GENMASK(1, 0) - -#define TYPEC_PMI632_MICRO_USB_MODE_REG 0x73 -#define MICRO_USB_MODE_ONLY BIT(0) - -/* Interrupt numbers */ -#define PMIC_TYPEC_OR_RID_IRQ 0x0 -#define PMIC_TYPEC_VPD_IRQ 0x1 -#define PMIC_TYPEC_CC_STATE_IRQ 0x2 -#define PMIC_TYPEC_VCONN_OC_IRQ 0x3 -#define PMIC_TYPEC_VBUS_IRQ 0x4 -#define PMIC_TYPEC_ATTACH_DETACH_IRQ 0x5 -#define PMIC_TYPEC_LEGACY_CABLE_IRQ 0x6 -#define PMIC_TYPEC_TRY_SNK_SRC_IRQ 0x7 - /* Resources */ #define PMIC_TYPEC_MAX_IRQS 0x08 @@ -156,40 +19,17 @@ struct pmic_typec_port_irq_params { struct pmic_typec_port_resources { unsigned int nr_irqs; - struct pmic_typec_port_irq_params irq_params[PMIC_TYPEC_MAX_IRQS]; + const struct pmic_typec_port_irq_params irq_params[PMIC_TYPEC_MAX_IRQS]; }; /* API */ -struct pmic_typec; -struct pmic_typec_port *qcom_pmic_typec_port_alloc(struct device *dev); +extern const struct pmic_typec_port_resources pm8150b_port_res; int qcom_pmic_typec_port_probe(struct platform_device *pdev, - struct pmic_typec_port *pmic_typec_port, - struct pmic_typec_port_resources *res, + struct pmic_typec *tcpm, + const struct pmic_typec_port_resources *res, struct regmap *regmap, u32 base); -int qcom_pmic_typec_port_start(struct pmic_typec_port *pmic_typec_port, - struct tcpm_port *tcpm_port); - -void qcom_pmic_typec_port_stop(struct pmic_typec_port *pmic_typec_port); - -int qcom_pmic_typec_port_get_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status *cc1, - enum typec_cc_status *cc2); - -int qcom_pmic_typec_port_set_cc(struct pmic_typec_port *pmic_typec_port, - enum typec_cc_status cc); - -int qcom_pmic_typec_port_get_vbus(struct pmic_typec_port *pmic_typec_port); - -int qcom_pmic_typec_port_set_vconn(struct pmic_typec_port *pmic_typec_port, bool on); - -int qcom_pmic_typec_port_start_toggling(struct pmic_typec_port *pmic_typec_port, - enum typec_port_type port_type, - enum typec_cc_status cc); - -int qcom_pmic_typec_port_set_vbus(struct pmic_typec_port *pmic_typec_port, bool on); - #endif /* __QCOM_PMIC_TYPE_C_PORT_H__ */ diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 7118551827..c962014bba 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -445,8 +445,11 @@ static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) unsigned int reg = 0; int ret; - if (enable) + if (enable) { reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET; + if (tcpci->data->cable_comm_capable) + reg |= TCPC_RX_DETECT_SOP1; + } ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); if (ret < 0) return ret; @@ -584,6 +587,23 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type return 0; } +static bool tcpci_cable_comm_capable(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + return tcpci->data->cable_comm_capable; +} + +static bool tcpci_attempt_vconn_swap_discovery(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + + if (tcpci->data->attempt_vconn_swap_discovery) + return tcpci->data->attempt_vconn_swap_discovery(tcpci, tcpci->data); + + return false; +} + static int tcpci_init(struct tcpc_dev *tcpc) { struct tcpci *tcpci = tcpc_to_tcpci(tcpc); @@ -712,7 +732,7 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) /* Read complete, clear RX status alert bit */ tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); - tcpm_pd_receive(tcpci->port, &msg); + tcpm_pd_receive(tcpci->port, &msg, TCPC_TX_SOP); } if (tcpci->data->vbus_vsafe0v && (status & TCPC_ALERT_EXTENDED_STATUS)) { @@ -793,6 +813,8 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) tcpci->tcpc.enable_frs = tcpci_enable_frs; tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus; tcpci->tcpc.set_partner_usb_comm_capable = tcpci_set_partner_usb_comm_capable; + tcpci->tcpc.cable_comm_capable = tcpci_cable_comm_capable; + tcpci->tcpc.attempt_vconn_swap_discovery = tcpci_attempt_vconn_swap_discovery; if (tcpci->data->check_contaminant) tcpci->tcpc.check_contaminant = tcpci_check_contaminant; diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h index 2c1c4d161b..78ff3b73ee 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.h +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h @@ -62,6 +62,7 @@ struct max_tcpci_chip { struct i2c_client *client; struct tcpm_port *port; enum contamiant_state contaminant_state; + bool veto_vconn_swap; }; static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 7fb966fd63..eec3bcec11 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -128,6 +128,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) u8 count, frame_type, rx_buf[TCPC_RECEIVE_BUFFER_LEN]; int ret, payload_index; u8 *rx_buf_ptr; + enum tcpm_transmit_type rx_type; /* * READABLE_BYTE_COUNT: Indicates the number of bytes in the RX_BUF_BYTE_x registers @@ -143,10 +144,23 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) count = rx_buf[TCPC_RECEIVE_BUFFER_COUNT_OFFSET]; frame_type = rx_buf[TCPC_RECEIVE_BUFFER_FRAME_TYPE_OFFSET]; - if (count == 0 || frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP) { + switch (frame_type) { + case TCPC_RX_BUF_FRAME_TYPE_SOP1: + rx_type = TCPC_TX_SOP_PRIME; + break; + case TCPC_RX_BUF_FRAME_TYPE_SOP: + rx_type = TCPC_TX_SOP; + break; + default: + rx_type = TCPC_TX_SOP; + break; + } + + if (count == 0 || (frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP && + frame_type != TCPC_RX_BUF_FRAME_TYPE_SOP1)) { max_tcpci_write16(chip, TCPC_ALERT, TCPC_ALERT_RX_STATUS); dev_err(chip->dev, "%s\n", count == 0 ? "error: count is 0" : - "error frame_type is not SOP"); + "error frame_type is not SOP/SOP'"); return; } @@ -183,7 +197,7 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) if (ret < 0) return; - tcpm_pd_receive(chip->port, &msg); + tcpm_pd_receive(chip->port, &msg, rx_type); } static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) @@ -309,8 +323,10 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status) if (ret < 0) return ret; - if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) + if (reg_status & TCPC_FAULT_STATUS_VCONN_OC) { + chip->veto_vconn_swap = true; tcpm_port_error_recovery(chip->port); + } } if (status & TCPC_ALERT_EXTND) { @@ -444,6 +460,18 @@ static void max_tcpci_check_contaminant(struct tcpci *tcpci, struct tcpci_data * tcpm_port_clean(chip->port); } +static bool max_tcpci_attempt_vconn_swap_discovery(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); + + if (chip->veto_vconn_swap) { + chip->veto_vconn_swap = false; + return false; + } + + return true; +} + static int max_tcpci_probe(struct i2c_client *client) { int ret; @@ -478,6 +506,8 @@ static int max_tcpci_probe(struct i2c_client *client) chip->data.vbus_vsafe0v = true; chip->data.set_partner_usb_comm_capable = max_tcpci_set_partner_usb_comm_capable; chip->data.check_contaminant = max_tcpci_check_contaminant; + chip->data.cable_comm_capable = true; + chip->data.attempt_vconn_swap_discovery = max_tcpci_attempt_vconn_swap_discovery; max_tcpci_init_regs(chip); chip->tcpci = tcpci_register_port(chip->dev, &chip->data); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 3f7200a227..8a1af08f71 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -108,6 +108,7 @@ S(VCONN_SWAP_WAIT_FOR_VCONN), \ S(VCONN_SWAP_TURN_ON_VCONN), \ S(VCONN_SWAP_TURN_OFF_VCONN), \ + S(VCONN_SWAP_SEND_SOFT_RESET), \ \ S(FR_SWAP_SEND), \ S(FR_SWAP_SEND_TIMEOUT), \ @@ -145,7 +146,9 @@ S(PORT_RESET_WAIT_OFF), \ \ S(AMS_START), \ - S(CHUNK_NOT_SUPP) + S(CHUNK_NOT_SUPP), \ + \ + S(SRC_VDM_IDENTITY_REQUEST) #define FOREACH_AMS(S) \ S(NONE_AMS), \ @@ -327,6 +330,12 @@ struct tcpm_port { struct typec_partner_desc partner_desc; struct typec_partner *partner; + struct usb_pd_identity cable_ident; + struct typec_cable_desc cable_desc; + struct typec_cable *cable; + struct typec_plug_desc plug_prime_desc; + struct typec_plug *plug_prime; + enum typec_cc_status cc_req; enum typec_cc_status src_rp; /* work only if pd_supported == false */ @@ -468,7 +477,9 @@ struct tcpm_port { /* Alternate mode data */ struct pd_mode_data mode_data; + struct pd_mode_data mode_data_prime; struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX]; + struct typec_altmode *plug_prime_altmode[ALTMODE_DISCOVERY_MAX]; struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX]; /* Deadline in jiffies to exit src_try_wait state */ @@ -505,6 +516,41 @@ struct tcpm_port { * transitions. */ bool potential_contaminant; + + /* SOP* Related Fields */ + /* + * Flag to determine if SOP' Discover Identity is available. The flag + * is set if Discover Identity on SOP' does not immediately follow + * Discover Identity on SOP. + */ + bool send_discover_prime; + /* + * tx_sop_type determines which SOP* a message is being sent on. + * For messages that are queued and not sent immediately such as in + * tcpm_queue_message or messages that send after state changes, + * the tx_sop_type is set accordingly. + */ + enum tcpm_transmit_type tx_sop_type; + /* + * Prior to discovering the port partner's Specification Revision, the + * Vconn source and cable plug will use the lower of their two revisions. + * + * When the port partner's Specification Revision is discovered, the following + * rules are put in place. + * 1. If the cable revision (1) is lower than the revision negotiated + * between the port and partner (2), the port and partner will communicate + * on revision (2), but the port and cable will communicate on revision (1). + * 2. If the cable revision (1) is higher than the revision negotiated + * between the port and partner (2), the port and partner will communicate + * on revision (2), and the port and cable will communicate on revision (2) + * as well. + */ + unsigned int negotiated_rev_prime; + /* + * Each SOP* type must maintain their own tx and rx message IDs + */ + unsigned int message_id_prime; + unsigned int rx_msgid_prime; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct mutex logbuffer_lock; /* log buffer access lock */ @@ -518,6 +564,7 @@ struct pd_rx_event { struct kthread_work work; struct tcpm_port *port; struct pd_message msg; + enum tcpm_transmit_type rx_sop_type; }; static const char * const pd_rev[] = { @@ -893,19 +940,30 @@ static void tcpm_ams_finish(struct tcpm_port *port) } static int tcpm_pd_transmit(struct tcpm_port *port, - enum tcpm_transmit_type type, + enum tcpm_transmit_type tx_sop_type, const struct pd_message *msg) { unsigned long timeout; int ret; + unsigned int negotiated_rev; + + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + negotiated_rev = port->negotiated_rev_prime; + break; + case TCPC_TX_SOP: + default: + negotiated_rev = port->negotiated_rev; + break; + } if (msg) tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header)); else - tcpm_log(port, "PD TX, type: %#x", type); + tcpm_log(port, "PD TX, type: %#x", tx_sop_type); reinit_completion(&port->tx_complete); - ret = port->tcpc->pd_transmit(port->tcpc, type, msg, port->negotiated_rev); + ret = port->tcpc->pd_transmit(port->tcpc, tx_sop_type, msg, negotiated_rev); if (ret < 0) return ret; @@ -918,7 +976,17 @@ static int tcpm_pd_transmit(struct tcpm_port *port, switch (port->tx_status) { case TCPC_TX_SUCCESS: - port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK; + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + port->message_id_prime = (port->message_id_prime + 1) & + PD_HEADER_ID_MASK; + break; + case TCPC_TX_SOP: + default: + port->message_id = (port->message_id + 1) & + PD_HEADER_ID_MASK; + break; + } /* * USB PD rev 2.0, 8.3.2.2.1: * USB PD rev 3.0, 8.3.2.1.3: @@ -1099,6 +1167,12 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached, if (ret < 0) return ret; + if (port->tcpc->set_orientation) { + ret = port->tcpc->set_orientation(port->tcpc, orientation); + if (ret < 0) + return ret; + } + port->pwr_role = role; port->data_role = data; typec_set_data_role(port->typec_port, data); @@ -1456,7 +1530,7 @@ static int tcpm_ams_start(struct tcpm_port *port, enum tcpm_ams ams) * VDM/VDO handling functions */ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, - const u32 *data, int cnt) + const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) { u32 vdo_hdr = port->vdo_data[0]; @@ -1464,7 +1538,10 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, /* If is sending discover_identity, handle received message first */ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) { - port->send_discover = true; + if (tx_sop_type == TCPC_TX_SOP_PRIME) + port->send_discover_prime = true; + else + port->send_discover = true; mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); } else { /* Make sure we are not still processing a previous VDM packet */ @@ -1479,14 +1556,20 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, port->vdm_state = VDM_STATE_READY; port->vdm_sm_running = true; + port->tx_sop_type = tx_sop_type; + mod_vdm_delayed_work(port, 0); } static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header, - const u32 *data, int cnt) + const u32 *data, int cnt, enum tcpm_transmit_type tx_sop_type) { + if (port->state != SRC_READY && port->state != SNK_READY && + port->state != SRC_VDM_IDENTITY_REQUEST) + return; + mutex_lock(&port->lock); - tcpm_queue_vdm(port, header, data, cnt); + tcpm_queue_vdm(port, header, data, cnt, tx_sop_type); mutex_unlock(&port->lock); } @@ -1509,9 +1592,68 @@ static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt) PD_PRODUCT_PID(product), product & 0xffff); } -static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt) +static void svdm_consume_identity_sop_prime(struct tcpm_port *port, const u32 *p, int cnt) { - struct pd_mode_data *pmdata = &port->mode_data; + u32 idh = p[VDO_INDEX_IDH]; + u32 product = p[VDO_INDEX_PRODUCT]; + int svdm_version; + + /* + * Attempt to consume identity only if cable currently is not set + */ + if (!IS_ERR_OR_NULL(port->cable)) + goto register_plug; + + /* Reset cable identity */ + memset(&port->cable_ident, 0, sizeof(port->cable_ident)); + + /* Fill out id header, cert, product, cable VDO 1 */ + port->cable_ident.id_header = idh; + port->cable_ident.cert_stat = p[VDO_INDEX_CSTAT]; + port->cable_ident.product = product; + port->cable_ident.vdo[0] = p[VDO_INDEX_CABLE_1]; + + /* Fill out cable desc, infer svdm_version from pd revision */ + port->cable_desc.type = (enum typec_plug_type) (VDO_TYPEC_CABLE_TYPE(p[VDO_INDEX_CABLE_1]) + + USB_PLUG_TYPE_A); + port->cable_desc.active = PD_IDH_PTYPE(idh) == IDH_PTYPE_ACABLE ? 1 : 0; + /* Log PD Revision and additional cable VDO from negotiated revision */ + switch (port->negotiated_rev_prime) { + case PD_REV30: + port->cable_desc.pd_revision = 0x0300; + if (port->cable_desc.active) + port->cable_ident.vdo[1] = p[VDO_INDEX_CABLE_2]; + break; + case PD_REV20: + port->cable_desc.pd_revision = 0x0200; + break; + default: + port->cable_desc.pd_revision = 0x0200; + break; + } + port->cable_desc.identity = &port->cable_ident; + /* Register Cable, set identity and svdm_version */ + port->cable = typec_register_cable(port->typec_port, &port->cable_desc); + if (IS_ERR_OR_NULL(port->cable)) + return; + typec_cable_set_identity(port->cable); + /* Get SVDM version */ + svdm_version = PD_VDO_SVDM_VER(p[VDO_INDEX_HDR]); + typec_cable_set_svdm_version(port->cable, svdm_version); + +register_plug: + if (IS_ERR_OR_NULL(port->plug_prime)) { + port->plug_prime_desc.index = TYPEC_PLUG_SOP_P; + port->plug_prime = typec_register_plug(port->cable, + &port->plug_prime_desc); + } +} + +static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt, + enum tcpm_transmit_type rx_sop_type) +{ + struct pd_mode_data *pmdata = rx_sop_type == TCPC_TX_SOP_PRIME ? + &port->mode_data_prime : &port->mode_data; int i; for (i = 1; i < cnt; i++) { @@ -1557,14 +1699,29 @@ abort: return false; } -static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt) +static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt, + enum tcpm_transmit_type rx_sop_type) { struct pd_mode_data *pmdata = &port->mode_data; struct typec_altmode_desc *paltmode; int i; - if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { - /* Already logged in svdm_consume_svids() */ + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + pmdata = &port->mode_data_prime; + if (pmdata->altmodes >= ARRAY_SIZE(port->plug_prime_altmode)) { + /* Already logged in svdm_consume_svids() */ + return; + } + break; + case TCPC_TX_SOP: + pmdata = &port->mode_data; + if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { + /* Already logged in svdm_consume_svids() */ + return; + } + break; + default: return; } @@ -1605,20 +1762,129 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port) } } +static void tcpm_register_plug_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data_prime; + struct typec_altmode *altmode; + int i; + + typec_plug_set_num_altmodes(port->plug_prime, modep->altmodes); + + for (i = 0; i < modep->altmodes; i++) { + altmode = typec_plug_register_altmode(port->plug_prime, + &modep->altmode_desc[i]); + if (IS_ERR(altmode)) { + tcpm_log(port, "Failed to register plug SVID 0x%04x", + modep->altmode_desc[i].svid); + altmode = NULL; + } + port->plug_prime_altmode[i] = altmode; + } +} + #define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header) +#define supports_modal_cable(port) PD_IDH_MODAL_SUPP((port)->cable_ident.id_header) +#define supports_host(port) PD_IDH_HOST_SUPP((port->partner_ident.id_header)) + +/* + * Helper to determine whether the port is capable of SOP' communication at the + * current point in time. + */ +static bool tcpm_can_communicate_sop_prime(struct tcpm_port *port) +{ + /* Check to see if tcpc supports SOP' communication */ + if (!port->tcpc->cable_comm_capable || !port->tcpc->cable_comm_capable(port->tcpc)) + return false; + /* + * Power Delivery 2.0 Section 6.3.11 + * Before communicating with a Cable Plug a Port Should ensure that it + * is the Vconn Source and that the Cable Plugs are powered by + * performing a Vconn swap if necessary. Since it cannot be guaranteed + * that the present Vconn Source is supplying Vconn, the only means to + * ensure that the Cable Plugs are powered is for a Port wishing to + * communicate with a Cable Plug is to become the Vconn Source. + * + * Power Delivery 3.0 Section 6.3.11 + * Before communicating with a Cable Plug a Port Shall ensure that it + * is the Vconn source. + */ + if (port->vconn_role != TYPEC_SOURCE) + return false; + /* + * Power Delivery 2.0 Section 2.4.4 + * When no Contract or an Implicit Contract is in place the Source can + * communicate with a Cable Plug using SOP' packets in order to discover + * its characteristics. + * + * Power Delivery 3.0 Section 2.4.4 + * When no Contract or an Implicit Contract is in place only the Source + * port that is supplying Vconn is allowed to send packets to a Cable + * Plug and is allowed to respond to packets from the Cable Plug. + */ + if (!port->explicit_contract) + return port->pwr_role == TYPEC_SOURCE; + if (port->negotiated_rev == PD_REV30) + return true; + /* + * Power Delivery 2.0 Section 2.4.4 + * + * When an Explicit Contract is in place the DFP (either the Source or + * the Sink) can communicate with the Cable Plug(s) using SOP’/SOP” + * Packets (see Figure 2-3). + */ + if (port->negotiated_rev == PD_REV20) + return port->data_role == TYPEC_HOST; + return false; +} + +static bool tcpm_attempt_vconn_swap_discovery(struct tcpm_port *port) +{ + if (!port->tcpc->attempt_vconn_swap_discovery) + return false; + + /* Port is already source, no need to perform swap */ + if (port->vconn_role == TYPEC_SOURCE) + return false; + + /* + * Partner needs to support Alternate Modes with modal support. If + * partner is also capable of being a USB Host, it could be a device + * that supports Alternate Modes as the DFP. + */ + if (!supports_modal(port) || supports_host(port)) + return false; + + if ((port->negotiated_rev == PD_REV20 && port->data_role == TYPEC_HOST) || + port->negotiated_rev == PD_REV30) + return port->tcpc->attempt_vconn_swap_discovery(port->tcpc); + + return false; +} + + +static bool tcpm_cable_vdm_supported(struct tcpm_port *port) +{ + return !IS_ERR_OR_NULL(port->cable) && + typec_cable_is_active(port->cable) && + supports_modal_cable(port) && + tcpm_can_communicate_sop_prime(port); +} static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, const u32 *p, int cnt, u32 *response, - enum adev_actions *adev_action) + enum adev_actions *adev_action, + enum tcpm_transmit_type rx_sop_type, + enum tcpm_transmit_type *response_tx_sop_type) { struct typec_port *typec = port->typec_port; - struct typec_altmode *pdev; - struct pd_mode_data *modep; + struct typec_altmode *pdev, *pdev_prime; + struct pd_mode_data *modep, *modep_prime; int svdm_version; int rlen = 0; int cmd_type; int cmd; int i; + int ret; cmd_type = PD_VDO_CMDT(p[0]); cmd = PD_VDO_CMD(p[0]); @@ -1626,17 +1892,54 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d", p[0], cmd_type, cmd, cnt); - modep = &port->mode_data; - - pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX, - PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); - - svdm_version = typec_get_negotiated_svdm_version(typec); - if (svdm_version < 0) - return 0; + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + modep_prime = &port->mode_data_prime; + pdev_prime = typec_match_altmode(port->plug_prime_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_cable_svdm_version(typec); + /* + * Update SVDM version if cable was discovered before port partner. + */ + if (!IS_ERR_OR_NULL(port->cable) && + PD_VDO_SVDM_VER(p[0]) < svdm_version) + typec_cable_set_svdm_version(port->cable, svdm_version); + break; + case TCPC_TX_SOP: + modep = &port->mode_data; + pdev = typec_match_altmode(port->partner_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_negotiated_svdm_version(typec); + if (svdm_version < 0) + return 0; + break; + default: + modep = &port->mode_data; + pdev = typec_match_altmode(port->partner_altmode, + ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), + PD_VDO_OPOS(p[0])); + svdm_version = typec_get_negotiated_svdm_version(typec); + if (svdm_version < 0) + return 0; + break; + } switch (cmd_type) { case CMDT_INIT: + /* + * Only the port or port partner is allowed to initialize SVDM + * commands over SOP'. In case the port partner initializes a + * sequence when it is not allowed to send SOP' messages, drop + * the message should the TCPM port try to process it. + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME) + return 0; + switch (cmd) { case CMD_DISCOVER_IDENT: if (PD_VDO_VID(p[0]) != USB_SID_PD) @@ -1703,55 +2006,186 @@ static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev, (VDO_SVDM_VERS(typec_get_negotiated_svdm_version(typec))); break; case CMDT_RSP_ACK: - /* silently drop message if we are not connected */ - if (IS_ERR_OR_NULL(port->partner)) + /* + * Silently drop message if we are not connected, but can process + * if SOP' Discover Identity prior to explicit contract. + */ + if (IS_ERR_OR_NULL(port->partner) && + !(rx_sop_type == TCPC_TX_SOP_PRIME && cmd == CMD_DISCOVER_IDENT)) break; tcpm_ams_finish(port); switch (cmd) { + /* + * SVDM Command Flow for SOP and SOP': + * SOP Discover Identity + * SOP' Discover Identity + * SOP Discover SVIDs + * Discover Modes + * (Active Cables) + * SOP' Discover SVIDs + * Discover Modes + * + * Perform Discover SOP' if the port can communicate with cable + * plug. + */ case CMD_DISCOVER_IDENT: - if (PD_VDO_SVDM_VER(p[0]) < svdm_version) - typec_partner_set_svdm_version(port->partner, - PD_VDO_SVDM_VER(p[0])); - /* 6.4.4.3.1 */ - svdm_consume_identity(port, p, cnt); - response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version(typec), - CMD_DISCOVER_SVID); - rlen = 1; + switch (rx_sop_type) { + case TCPC_TX_SOP: + if (PD_VDO_SVDM_VER(p[0]) < svdm_version) { + typec_partner_set_svdm_version(port->partner, + PD_VDO_SVDM_VER(p[0])); + /* If cable is discovered before partner, downgrade svdm */ + if (!IS_ERR_OR_NULL(port->cable) && + (typec_get_cable_svdm_version(port->typec_port) > + svdm_version)) + typec_cable_set_svdm_version(port->cable, + svdm_version); + } + /* 6.4.4.3.1 */ + svdm_consume_identity(port, p, cnt); + /* Attempt Vconn swap, delay SOP' discovery if necessary */ + if (tcpm_attempt_vconn_swap_discovery(port)) { + port->send_discover_prime = true; + port->upcoming_state = VCONN_SWAP_SEND; + ret = tcpm_ams_start(port, VCONN_SWAP); + if (!ret) + return 0; + /* Cannot perform Vconn swap */ + port->upcoming_state = INVALID_STATE; + port->send_discover_prime = false; + } + + /* + * Attempt Discover Identity on SOP' if the + * cable was not discovered previously, and use + * the SVDM version of the partner to probe. + */ + if (IS_ERR_OR_NULL(port->cable) && + tcpm_can_communicate_sop_prime(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + port->send_discover_prime = true; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_IDENT); + rlen = 1; + } else { + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + } + break; + case TCPC_TX_SOP_PRIME: + /* + * svdm_consume_identity_sop_prime will determine + * the svdm_version for the cable moving forward. + */ + svdm_consume_identity_sop_prime(port, p, cnt); + + /* + * If received in SRC_VDM_IDENTITY_REQUEST, continue + * to SRC_SEND_CAPABILITIES + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + return 0; + } + + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + break; + default: + return 0; + } break; case CMD_DISCOVER_SVID: + *response_tx_sop_type = rx_sop_type; /* 6.4.4.3.2 */ - if (svdm_consume_svids(port, p, cnt)) { + if (svdm_consume_svids(port, p, cnt, rx_sop_type)) { response[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID); rlen = 1; - } else if (modep->nsvids && supports_modal(port)) { - response[0] = VDO(modep->svids[0], 1, svdm_version, - CMD_DISCOVER_MODES); - rlen = 1; + } else { + if (rx_sop_type == TCPC_TX_SOP) { + if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (modep_prime->nsvids) { + response[0] = VDO(modep_prime->svids[0], 1, + svdm_version, CMD_DISCOVER_MODES); + rlen = 1; + } + } } break; case CMD_DISCOVER_MODES: - /* 6.4.4.3.3 */ - svdm_consume_modes(port, p, cnt); - modep->svid_index++; - if (modep->svid_index < modep->nsvids) { - u16 svid = modep->svids[modep->svid_index]; - response[0] = VDO(svid, 1, svdm_version, CMD_DISCOVER_MODES); - rlen = 1; - } else { - tcpm_register_partner_altmodes(port); + if (rx_sop_type == TCPC_TX_SOP) { + /* 6.4.4.3.3 */ + svdm_consume_modes(port, p, cnt, rx_sop_type); + modep->svid_index++; + if (modep->svid_index < modep->nsvids) { + u16 svid = modep->svids[modep->svid_index]; + *response_tx_sop_type = TCPC_TX_SOP; + response[0] = VDO(svid, 1, svdm_version, + CMD_DISCOVER_MODES); + rlen = 1; + } else if (tcpm_cable_vdm_supported(port)) { + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + response[0] = VDO(USB_SID_PD, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_SVID); + rlen = 1; + } else { + tcpm_register_partner_altmodes(port); + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + /* 6.4.4.3.3 */ + svdm_consume_modes(port, p, cnt, rx_sop_type); + modep_prime->svid_index++; + if (modep_prime->svid_index < modep_prime->nsvids) { + u16 svid = modep_prime->svids[modep_prime->svid_index]; + *response_tx_sop_type = TCPC_TX_SOP_PRIME; + response[0] = VDO(svid, 1, + typec_get_cable_svdm_version(typec), + CMD_DISCOVER_MODES); + rlen = 1; + } else { + tcpm_register_plug_altmodes(port); + tcpm_register_partner_altmodes(port); + } } break; case CMD_ENTER_MODE: - if (adev && pdev) - *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + *response_tx_sop_type = rx_sop_type; + if (rx_sop_type == TCPC_TX_SOP) { + if (adev && pdev) { + typec_altmode_update_active(pdev, true); + *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + } + } else if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (adev && pdev_prime) { + typec_altmode_update_active(pdev_prime, true); + *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL; + } + } return 0; case CMD_EXIT_MODE: - if (adev && pdev) { - /* Back to USB Operation */ - *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; - return 0; + *response_tx_sop_type = rx_sop_type; + if (rx_sop_type == TCPC_TX_SOP) { + if (adev && pdev) { + typec_altmode_update_active(pdev, false); + /* Back to USB Operation */ + *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM; + return 0; + } } break; case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15): @@ -1804,13 +2238,15 @@ static void tcpm_pd_handle_msg(struct tcpm_port *port, enum tcpm_ams ams); static void tcpm_handle_vdm_request(struct tcpm_port *port, - const __le32 *payload, int cnt) + const __le32 *payload, int cnt, + enum tcpm_transmit_type rx_sop_type) { enum adev_actions adev_action = ADEV_NONE; struct typec_altmode *adev; u32 p[PD_MAX_PAYLOAD]; u32 response[8] = { }; int i, rlen = 0; + enum tcpm_transmit_type response_tx_sop_type = TCPC_TX_SOP; for (i = 0; i < cnt; i++) p[i] = le32_to_cpu(payload[i]); @@ -1845,7 +2281,8 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, * - We will send NAK and the flag will be cleared in the state machine. */ port->vdm_sm_running = true; - rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action); + rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action, + rx_sop_type, &response_tx_sop_type); } else { if (port->negotiated_rev >= PD_REV30) tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS); @@ -1881,19 +2318,37 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, typec_altmode_vdm(adev, p[0], &p[1], cnt); break; case ADEV_QUEUE_VDM: - typec_altmode_vdm(adev, p[0], &p[1], cnt); + if (response_tx_sop_type == TCPC_TX_SOP_PRIME) + typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, p[0], &p[1], cnt); + else + typec_altmode_vdm(adev, p[0], &p[1], cnt); break; case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL: - if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { - int svdm_version = typec_get_negotiated_svdm_version( - port->typec_port); - if (svdm_version < 0) - break; - - response[0] = VDO(adev->svid, 1, svdm_version, - CMD_EXIT_MODE); - response[0] |= VDO_OPOS(adev->mode); - rlen = 1; + if (response_tx_sop_type == TCPC_TX_SOP_PRIME) { + if (typec_cable_altmode_vdm(adev, TYPEC_PLUG_SOP_P, + p[0], &p[1], cnt)) { + int svdm_version = typec_get_cable_svdm_version( + port->typec_port); + if (svdm_version < 0) + break; + + response[0] = VDO(adev->svid, 1, svdm_version, + CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + rlen = 1; + } + } else { + if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { + int svdm_version = typec_get_negotiated_svdm_version( + port->typec_port); + if (svdm_version < 0) + break; + + response[0] = VDO(adev->svid, 1, svdm_version, + CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + rlen = 1; + } } break; case ADEV_ATTENTION: @@ -1913,19 +2368,38 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port, mutex_lock(&port->lock); if (rlen > 0) - tcpm_queue_vdm(port, response[0], &response[1], rlen - 1); + tcpm_queue_vdm(port, response[0], &response[1], rlen - 1, response_tx_sop_type); else port->vdm_sm_running = false; } static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, - const u32 *data, int count) + const u32 *data, int count, enum tcpm_transmit_type tx_sop_type) { - int svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + int svdm_version; u32 header; - if (svdm_version < 0) - return; + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + /* + * If the port partner is discovered, then the port partner's + * SVDM Version will be returned + */ + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + svdm_version = SVDM_VER_MAX; + break; + case TCPC_TX_SOP: + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return; + break; + default: + svdm_version = typec_get_negotiated_svdm_version(port->typec_port); + if (svdm_version < 0) + return; + break; + } if (WARN_ON(count > VDO_MAX_SIZE - 1)) count = VDO_MAX_SIZE - 1; @@ -1934,7 +2408,7 @@ static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), svdm_version, cmd); - tcpm_queue_vdm(port, header, data, count); + tcpm_queue_vdm(port, header, data, count, tx_sop_type); } static unsigned int vdm_ready_timeout(u32 vdm_hdr) @@ -1968,6 +2442,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) struct pd_message msg; int i, res = 0; u32 vdo_hdr = port->vdo_data[0]; + u32 response[8] = { }; switch (port->vdm_state) { case VDM_STATE_READY: @@ -1981,7 +2456,8 @@ static void vdm_run_state_machine(struct tcpm_port *port) * if there's traffic or we're not in PDO ready state don't send * a VDM. */ - if (port->state != SRC_READY && port->state != SNK_READY) { + if (port->state != SRC_READY && port->state != SNK_READY && + port->state != SRC_VDM_IDENTITY_REQUEST) { port->vdm_sm_running = false; break; } @@ -1992,7 +2468,17 @@ static void vdm_run_state_machine(struct tcpm_port *port) case CMD_DISCOVER_IDENT: res = tcpm_ams_start(port, DISCOVER_IDENTITY); if (res == 0) { - port->send_discover = false; + switch (port->tx_sop_type) { + case TCPC_TX_SOP_PRIME: + port->send_discover_prime = false; + break; + case TCPC_TX_SOP: + port->send_discover = false; + break; + default: + port->send_discover = false; + break; + } } else if (res == -EAGAIN) { port->vdo_data[0] = 0; mod_send_discover_delayed_work(port, @@ -2048,12 +2534,21 @@ static void vdm_run_state_machine(struct tcpm_port *port) break; case VDM_STATE_ERR_SEND: /* + * When sending Discover Identity to SOP' before establishing an + * explicit contract, do not retry. Instead, weave sending + * Source_Capabilities over SOP and Discover Identity over SOP'. + */ + if (port->state == SRC_VDM_IDENTITY_REQUEST) { + tcpm_ams_finish(port); + port->vdm_state = VDM_STATE_DONE; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + /* * A partner which does not support USB PD will not reply, * so this is not a fatal error. At the same time, some * devices may not return GoodCRC under some circumstances, * so we need to retry. */ - if (port->vdm_retries < 3) { + } else if (port->vdm_retries < 3) { tcpm_log(port, "VDM Tx error, retry"); port->vdm_retries++; port->vdm_state = VDM_STATE_READY; @@ -2061,19 +2556,59 @@ static void vdm_run_state_machine(struct tcpm_port *port) tcpm_ams_finish(port); } else { tcpm_ams_finish(port); + if (port->tx_sop_type == TCPC_TX_SOP) + break; + /* Handle SOP' Transmission Errors */ + switch (PD_VDO_CMD(vdo_hdr)) { + /* + * If Discover Identity fails on SOP', then resume + * discovery process on SOP only. + */ + case CMD_DISCOVER_IDENT: + port->vdo_data[0] = 0; + response[0] = VDO(USB_SID_PD, 1, + typec_get_negotiated_svdm_version( + port->typec_port), + CMD_DISCOVER_SVID); + tcpm_queue_vdm(port, response[0], &response[1], + 0, TCPC_TX_SOP); + break; + /* + * If Discover SVIDs or Discover Modes fail, then + * proceed with Alt Mode discovery process on SOP. + */ + case CMD_DISCOVER_SVID: + tcpm_register_partner_altmodes(port); + break; + case CMD_DISCOVER_MODES: + tcpm_register_partner_altmodes(port); + break; + default: + break; + } } break; case VDM_STATE_SEND_MESSAGE: /* Prepare and send VDM */ memset(&msg, 0, sizeof(msg)); - msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, - port->pwr_role, - port->data_role, - port->negotiated_rev, - port->message_id, port->vdo_count); + if (port->tx_sop_type == TCPC_TX_SOP_PRIME) { + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + 0, /* Cable Plug Indicator for DFP/UFP */ + 0, /* Reserved */ + port->negotiated_rev_prime, + port->message_id_prime, + port->vdo_count); + } else { + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + port->vdo_count); + } for (i = 0; i < port->vdo_count; i++) msg.payload[i] = cpu_to_le32(port->vdo_data[i]); - res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + res = tcpm_pd_transmit(port, port->tx_sop_type, &msg); if (res < 0) { port->vdm_state = VDM_STATE_ERR_SEND; } else { @@ -2248,7 +2783,7 @@ static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo) header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0); + tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP); return 0; } @@ -2265,7 +2800,7 @@ static int tcpm_altmode_exit(struct typec_altmode *altmode) header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); header |= VDO_OPOS(altmode->mode); - tcpm_queue_vdm_unlocked(port, header, NULL, 0); + tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP); return 0; } @@ -2274,7 +2809,7 @@ static int tcpm_altmode_vdm(struct typec_altmode *altmode, { struct tcpm_port *port = typec_altmode_get_drvdata(altmode); - tcpm_queue_vdm_unlocked(port, header, data, count - 1); + tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP); return 0; } @@ -2285,6 +2820,58 @@ static const struct typec_altmode_ops tcpm_altmode_ops = { .vdm = tcpm_altmode_vdm, }; + +static int tcpm_cable_altmode_enter(struct typec_altmode *altmode, enum typec_plug_index sop, + u32 *vdo) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0, TCPC_TX_SOP_PRIME); + return 0; +} + +static int tcpm_cable_altmode_exit(struct typec_altmode *altmode, enum typec_plug_index sop) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + int svdm_version; + u32 header; + + svdm_version = typec_get_cable_svdm_version(port->typec_port); + if (svdm_version < 0) + return svdm_version; + + header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm_unlocked(port, header, NULL, 0, TCPC_TX_SOP_PRIME); + return 0; +} + +static int tcpm_cable_altmode_vdm(struct typec_altmode *altmode, enum typec_plug_index sop, + u32 header, const u32 *data, int count) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + + tcpm_queue_vdm_unlocked(port, header, data, count - 1, TCPC_TX_SOP_PRIME); + + return 0; +} + +static const struct typec_cable_ops tcpm_cable_ops = { + .enter = tcpm_cable_altmode_enter, + .exit = tcpm_cable_altmode_exit, + .vdm = tcpm_cable_altmode_vdm, +}; + /* * PD (data, control) command handling functions */ @@ -2297,7 +2884,8 @@ static inline enum tcpm_state ready_state(struct tcpm_port *port) } static int tcpm_pd_send_control(struct tcpm_port *port, - enum pd_ctrl_msg_type type); + enum pd_ctrl_msg_type type, + enum tcpm_transmit_type tx_sop_type); static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload, int cnt) @@ -2416,7 +3004,7 @@ static int tcpm_register_source_caps(struct tcpm_port *port) { struct usb_power_delivery_desc desc = { port->negotiated_rev }; struct usb_power_delivery_capabilities_desc caps = { }; - struct usb_power_delivery_capabilities *cap; + struct usb_power_delivery_capabilities *cap = port->partner_source_caps; if (!port->partner_pd) port->partner_pd = usb_power_delivery_register(NULL, &desc); @@ -2426,6 +3014,9 @@ static int tcpm_register_source_caps(struct tcpm_port *port) memcpy(caps.pdo, port->source_caps, sizeof(u32) * port->nr_source_caps); caps.role = TYPEC_SOURCE; + if (cap) + usb_power_delivery_unregister_capabilities(cap); + cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); if (IS_ERR(cap)) return PTR_ERR(cap); @@ -2439,7 +3030,7 @@ static int tcpm_register_sink_caps(struct tcpm_port *port) { struct usb_power_delivery_desc desc = { port->negotiated_rev }; struct usb_power_delivery_capabilities_desc caps = { }; - struct usb_power_delivery_capabilities *cap = port->partner_source_caps; + struct usb_power_delivery_capabilities *cap; if (!port->partner_pd) port->partner_pd = usb_power_delivery_register(NULL, &desc); @@ -2449,9 +3040,6 @@ static int tcpm_register_sink_caps(struct tcpm_port *port) memcpy(caps.pdo, port->sink_caps, sizeof(u32) * port->nr_sink_caps); caps.role = TYPEC_SINK; - if (cap) - usb_power_delivery_unregister_capabilities(cap); - cap = usb_power_delivery_register_capabilities(port->partner_pd, &caps); if (IS_ERR(cap)) return PTR_ERR(cap); @@ -2462,7 +3050,8 @@ static int tcpm_register_sink_caps(struct tcpm_port *port) } static void tcpm_pd_data_request(struct tcpm_port *port, - const struct pd_message *msg) + const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) { enum pd_data_msg_type type = pd_header_type_le(msg->header); unsigned int cnt = pd_header_cnt_le(msg->header); @@ -2503,8 +3092,11 @@ static void tcpm_pd_data_request(struct tcpm_port *port, break; } - if (rev < PD_MAX_REV) + if (rev < PD_MAX_REV) { port->negotiated_rev = rev; + if (port->negotiated_rev_prime > port->negotiated_rev) + port->negotiated_rev_prime = port->negotiated_rev; + } if (port->pwr_role == TYPEC_SOURCE) { if (port->ams == GET_SOURCE_CAPABILITIES) @@ -2555,8 +3147,11 @@ static void tcpm_pd_data_request(struct tcpm_port *port, break; } - if (rev < PD_MAX_REV) + if (rev < PD_MAX_REV) { port->negotiated_rev = rev; + if (port->negotiated_rev_prime > port->negotiated_rev) + port->negotiated_rev_prime = port->negotiated_rev; + } if (port->pwr_role != TYPEC_SOURCE || cnt != 1) { tcpm_pd_handle_msg(port, @@ -2612,7 +3207,7 @@ static void tcpm_pd_data_request(struct tcpm_port *port, NONE_AMS); break; case PD_DATA_VENDOR_DEF: - tcpm_handle_vdm_request(port, msg->payload, cnt); + tcpm_handle_vdm_request(port, msg->payload, cnt, rx_sop_type); break; case PD_DATA_BIST: port->bist_request = le32_to_cpu(msg->payload[0]); @@ -2654,10 +3249,12 @@ static void tcpm_pps_complete(struct tcpm_port *port, int result) } static void tcpm_pd_ctrl_request(struct tcpm_port *port, - const struct pd_message *msg) + const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) { enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); enum tcpm_state next_state; + unsigned int rev = pd_header_rev_le(msg->header); /* * Stop VDM state machine if interrupted by other Messages while NOT_SUPP is allowed in @@ -2822,6 +3419,16 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case SOFT_RESET_SEND: if (port->ams == SOFT_RESET_AMS) tcpm_ams_finish(port); + /* + * SOP' Soft Reset is done after Vconn Swap, + * which returns to ready state + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME) { + if (rev < port->negotiated_rev_prime) + port->negotiated_rev_prime = rev; + tcpm_set_state(port, ready_state(port), 0); + break; + } if (port->pwr_role == TYPEC_SOURCE) { port->upcoming_state = SRC_SEND_CAPABILITIES; tcpm_ams_start(port, POWER_NEGOTIATION); @@ -2988,6 +3595,7 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) const struct pd_message *msg = &event->msg; unsigned int cnt = pd_header_cnt_le(msg->header); struct tcpm_port *port = event->port; + enum tcpm_transmit_type rx_sop_type = event->rx_sop_type; mutex_lock(&port->lock); @@ -2999,6 +3607,14 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) unsigned int msgid = pd_header_msgid_le(msg->header); /* + * Drop SOP' messages if cannot receive via + * tcpm_can_communicate_sop_prime + */ + if (rx_sop_type == TCPC_TX_SOP_PRIME && + !tcpm_can_communicate_sop_prime(port)) + goto done; + + /* * USB PD standard, 6.6.1.2: * "... if MessageID value in a received Message is the * same as the stored value, the receiver shall return a @@ -3007,16 +3623,26 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) * Message). Note: this shall not apply to the Soft_Reset * Message which always has a MessageID value of zero." */ - if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET) - goto done; - port->rx_msgid = msgid; + switch (rx_sop_type) { + case TCPC_TX_SOP_PRIME: + if (msgid == port->rx_msgid_prime) + goto done; + port->rx_msgid_prime = msgid; + break; + case TCPC_TX_SOP: + default: + if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET) + goto done; + port->rx_msgid = msgid; + break; + } /* * If both ends believe to be DFP/host, we have a data role * mismatch. */ if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) == - (port->data_role == TYPEC_HOST)) { + (port->data_role == TYPEC_HOST) && rx_sop_type == TCPC_TX_SOP) { tcpm_log(port, "Data role mismatch, initiating error recovery"); tcpm_set_state(port, ERROR_RECOVERY, 0); @@ -3024,9 +3650,9 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) if (le16_to_cpu(msg->header) & PD_HEADER_EXT_HDR) tcpm_pd_ext_msg_request(port, msg); else if (cnt) - tcpm_pd_data_request(port, msg); + tcpm_pd_data_request(port, msg, rx_sop_type); else - tcpm_pd_ctrl_request(port, msg); + tcpm_pd_ctrl_request(port, msg, rx_sop_type); } } @@ -3035,7 +3661,8 @@ done: kfree(event); } -void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) +void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg, + enum tcpm_transmit_type rx_sop_type) { struct pd_rx_event *event; @@ -3045,23 +3672,47 @@ void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) kthread_init_work(&event->work, tcpm_pd_rx_handler); event->port = port; + event->rx_sop_type = rx_sop_type; memcpy(&event->msg, msg, sizeof(*msg)); kthread_queue_work(port->wq, &event->work); } EXPORT_SYMBOL_GPL(tcpm_pd_receive); static int tcpm_pd_send_control(struct tcpm_port *port, - enum pd_ctrl_msg_type type) + enum pd_ctrl_msg_type type, + enum tcpm_transmit_type tx_sop_type) { struct pd_message msg; memset(&msg, 0, sizeof(msg)); - msg.header = PD_HEADER_LE(type, port->pwr_role, - port->data_role, - port->negotiated_rev, - port->message_id, 0); + switch (tx_sop_type) { + case TCPC_TX_SOP_PRIME: + msg.header = PD_HEADER_LE(type, + 0, /* Cable Plug Indicator for DFP/UFP */ + 0, /* Reserved */ + port->negotiated_rev, + port->message_id_prime, + 0); + break; + case TCPC_TX_SOP: + msg.header = PD_HEADER_LE(type, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 0); + break; + default: + msg.header = PD_HEADER_LE(type, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, + 0); + break; + } - return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + return tcpm_pd_transmit(port, tx_sop_type, &msg); } /* @@ -3080,13 +3731,13 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) switch (queued_message) { case PD_MSG_CTRL_WAIT: - tcpm_pd_send_control(port, PD_CTRL_WAIT); + tcpm_pd_send_control(port, PD_CTRL_WAIT, TCPC_TX_SOP); break; case PD_MSG_CTRL_REJECT: - tcpm_pd_send_control(port, PD_CTRL_REJECT); + tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP); break; case PD_MSG_CTRL_NOT_SUPP: - tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); break; case PD_MSG_DATA_SINK_CAP: ret = tcpm_pd_send_sink_caps(port); @@ -3663,6 +4314,7 @@ static int tcpm_src_attach(struct tcpm_port *port) port->attached = true; port->send_discover = true; + port->send_discover_prime = false; return 0; @@ -3679,6 +4331,15 @@ out_disable_mux: static void tcpm_typec_disconnect(struct tcpm_port *port) { + /* + * Unregister plug/cable outside of port->connected because cable can + * be discovered before SRC_READY/SNK_READY states where port->connected + * is set. + */ + typec_unregister_plug(port->plug_prime); + typec_unregister_cable(port->cable); + port->plug_prime = NULL; + port->cable = NULL; if (port->connected) { if (port->partner) { typec_partner_set_usb_power_delivery(port->partner, NULL); @@ -3692,14 +4353,20 @@ static void tcpm_typec_disconnect(struct tcpm_port *port) static void tcpm_unregister_altmodes(struct tcpm_port *port) { struct pd_mode_data *modep = &port->mode_data; + struct pd_mode_data *modep_prime = &port->mode_data_prime; int i; for (i = 0; i < modep->altmodes; i++) { typec_unregister_altmode(port->partner_altmode[i]); port->partner_altmode[i] = NULL; } + for (i = 0; i < modep_prime->altmodes; i++) { + typec_unregister_altmode(port->plug_prime_altmode[i]); + port->plug_prime_altmode[i] = NULL; + } memset(modep, 0, sizeof(*modep)); + memset(modep_prime, 0, sizeof(*modep_prime)); } static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capable) @@ -3728,6 +4395,7 @@ static void tcpm_reset_port(struct tcpm_port *port) * we can check tcpm_pd_rx_handler() if we had seen it before. */ port->rx_msgid = -1; + port->rx_msgid_prime = -1; port->tcpc->set_pd_rx(port->tcpc, false); tcpm_init_vbus(port); /* also disables charging */ @@ -3799,6 +4467,7 @@ static int tcpm_snk_attach(struct tcpm_port *port) port->attached = true; port->send_discover = true; + port->send_discover_prime = false; return 0; } @@ -4042,8 +4711,11 @@ static void run_state_machine(struct tcpm_port *port) port->pwr_opmode = TYPEC_PWR_MODE_USB; port->caps_count = 0; port->negotiated_rev = PD_MAX_REV; + port->negotiated_rev_prime = PD_MAX_REV; port->message_id = 0; + port->message_id_prime = 0; port->rx_msgid = -1; + port->rx_msgid_prime = -1; port->explicit_contract = false; /* SNK -> SRC POWER/FAST_ROLE_SWAP finished */ if (port->ams == POWER_ROLE_SWAP || @@ -4064,8 +4736,12 @@ static void run_state_machine(struct tcpm_port *port) } ret = tcpm_pd_send_source_caps(port); if (ret < 0) { - tcpm_set_state(port, SRC_SEND_CAPABILITIES, - PD_T_SEND_SOURCE_CAP); + if (tcpm_can_communicate_sop_prime(port) && + IS_ERR_OR_NULL(port->cable)) + tcpm_set_state(port, SRC_VDM_IDENTITY_REQUEST, 0); + else + tcpm_set_state(port, SRC_SEND_CAPABILITIES, + PD_T_SEND_SOURCE_CAP); } else { /* * Per standard, we should clear the reset counter here. @@ -4106,7 +4782,7 @@ static void run_state_machine(struct tcpm_port *port) case SRC_NEGOTIATE_CAPABILITIES: ret = tcpm_pd_check_request(port); if (ret < 0) { - tcpm_pd_send_control(port, PD_CTRL_REJECT); + tcpm_pd_send_control(port, PD_CTRL_REJECT, TCPC_TX_SOP); if (!port->explicit_contract) { tcpm_set_state(port, SRC_WAIT_NEW_CAPABILITIES, 0); @@ -4114,7 +4790,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SRC_READY, 0); } } else { - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_set_partner_usb_comm_capable(port, !!(port->sink_request & RDO_USB_COMM)); tcpm_set_state(port, SRC_TRANSITION_SUPPLY, @@ -4123,7 +4799,7 @@ static void run_state_machine(struct tcpm_port *port) break; case SRC_TRANSITION_SUPPLY: /* XXX: regulator_set_voltage(vbus, ...) */ - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); port->explicit_contract = true; typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); port->pwr_opmode = TYPEC_PWR_MODE_PD; @@ -4160,14 +4836,23 @@ static void run_state_machine(struct tcpm_port *port) * 6.4.4.3.1 Discover Identity * "The Discover Identity Command Shall only be sent to SOP when there is an * Explicit Contract." - * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using - * port->explicit_contract to decide whether to send the command. + * + * Discover Identity on SOP' should be discovered prior to the + * ready state, but if done after a Vconn Swap following Discover + * Identity on SOP then the discovery process can be run here + * as well. */ if (port->explicit_contract) { - tcpm_set_initial_svdm_version(port); + if (port->send_discover_prime) { + port->tx_sop_type = TCPC_TX_SOP_PRIME; + } else { + port->tx_sop_type = TCPC_TX_SOP; + tcpm_set_initial_svdm_version(port); + } mod_send_discover_delayed_work(port, 0); } else { port->send_discover = false; + port->send_discover_prime = false; } /* @@ -4283,8 +4968,11 @@ static void run_state_machine(struct tcpm_port *port) typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; port->negotiated_rev = PD_MAX_REV; + port->negotiated_rev_prime = PD_MAX_REV; port->message_id = 0; + port->message_id_prime = 0; port->rx_msgid = -1; + port->rx_msgid_prime = -1; port->explicit_contract = false; if (port->ams == POWER_ROLE_SWAP || @@ -4456,14 +5144,23 @@ static void run_state_machine(struct tcpm_port *port) * 6.4.4.3.1 Discover Identity * "The Discover Identity Command Shall only be sent to SOP when there is an * Explicit Contract." - * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using - * port->explicit_contract. + * + * Discover Identity on SOP' should be discovered prior to the + * ready state, but if done after a Vconn Swap following Discover + * Identity on SOP then the discovery process can be run here + * as well. */ if (port->explicit_contract) { - tcpm_set_initial_svdm_version(port); + if (port->send_discover_prime) { + port->tx_sop_type = TCPC_TX_SOP_PRIME; + } else { + port->tx_sop_type = TCPC_TX_SOP; + tcpm_set_initial_svdm_version(port); + } mod_send_discover_delayed_work(port, 0); } else { port->send_discover = false; + port->send_discover_prime = false; } power_supply_changed(port->psy); @@ -4504,6 +5201,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_unregister_altmodes(port); port->nr_sink_caps = 0; port->send_discover = true; + port->send_discover_prime = false; if (port->pwr_role == TYPEC_SOURCE) tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, PD_T_PS_HARD_RESET); @@ -4605,7 +5303,7 @@ static void run_state_machine(struct tcpm_port *port) /* remove existing capabilities */ usb_power_delivery_unregister_capabilities(port->partner_source_caps); port->partner_source_caps = NULL; - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_ams_finish(port); if (port->pwr_role == TYPEC_SOURCE) { port->upcoming_state = SRC_SEND_CAPABILITIES; @@ -4622,35 +5320,53 @@ static void run_state_machine(struct tcpm_port *port) tcpm_ams_start(port, SOFT_RESET_AMS); break; case SOFT_RESET_SEND: - port->message_id = 0; - port->rx_msgid = -1; - /* remove existing capabilities */ - usb_power_delivery_unregister_capabilities(port->partner_source_caps); - port->partner_source_caps = NULL; - if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET)) - tcpm_set_state_cond(port, hard_reset_state(port), 0); - else - tcpm_set_state_cond(port, hard_reset_state(port), - PD_T_SENDER_RESPONSE); + /* + * Power Delivery 3.0 Section 6.3.13 + * + * A Soft_Reset Message Shall be targeted at a specific entity + * depending on the type of SOP* packet used. + */ + if (port->tx_sop_type == TCPC_TX_SOP_PRIME) { + port->message_id_prime = 0; + port->rx_msgid_prime = -1; + tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP_PRIME); + tcpm_set_state_cond(port, ready_state(port), PD_T_SENDER_RESPONSE); + } else { + port->message_id = 0; + port->rx_msgid = -1; + /* remove existing capabilities */ + usb_power_delivery_unregister_capabilities(port->partner_source_caps); + port->partner_source_caps = NULL; + if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP)) + tcpm_set_state_cond(port, hard_reset_state(port), 0); + else + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } break; /* DR_Swap states */ case DR_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_DR_SWAP); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP, TCPC_TX_SOP); + if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { port->send_discover = true; + port->send_discover_prime = false; + } tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; case DR_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); + if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20) { port->send_discover = true; + port->send_discover_prime = false; + } tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); break; case DR_SWAP_SEND_TIMEOUT: tcpm_swap_complete(port, -ETIMEDOUT); port->send_discover = false; + port->send_discover_prime = false; tcpm_ams_finish(port); tcpm_set_state(port, ready_state(port), 0); break; @@ -4667,7 +5383,7 @@ static void run_state_machine(struct tcpm_port *port) break; case FR_SWAP_SEND: - if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP)) { + if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP, TCPC_TX_SOP)) { tcpm_set_state(port, ERROR_RECOVERY, 0); break; } @@ -4687,7 +5403,7 @@ static void run_state_machine(struct tcpm_port *port) break; case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED: tcpm_set_pwr_role(port, TYPEC_SOURCE); - if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) { + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) { tcpm_set_state(port, ERROR_RECOVERY, 0); break; } @@ -4697,11 +5413,11 @@ static void run_state_machine(struct tcpm_port *port) /* PR_Swap states */ case PR_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_set_state(port, PR_SWAP_START, 0); break; case PR_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_PR_SWAP); + tcpm_pd_send_control(port, PD_CTRL_PR_SWAP, TCPC_TX_SOP); tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4743,7 +5459,7 @@ static void run_state_machine(struct tcpm_port *port) * supply is turned off" */ tcpm_set_pwr_role(port, TYPEC_SINK); - if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) { + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP)) { tcpm_set_state(port, ERROR_RECOVERY, 0); break; } @@ -4790,17 +5506,17 @@ static void run_state_machine(struct tcpm_port *port) * Source." */ tcpm_set_pwr_role(port, TYPEC_SOURCE); - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START); break; case VCONN_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP); tcpm_ams_finish(port); tcpm_set_state(port, VCONN_SWAP_START, 0); break; case VCONN_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP); + tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP, TCPC_TX_SOP); tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4819,14 +5535,34 @@ static void run_state_machine(struct tcpm_port *port) PD_T_VCONN_SOURCE_ON); break; case VCONN_SWAP_TURN_ON_VCONN: - tcpm_set_vconn(port, true); - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - tcpm_set_state(port, ready_state(port), 0); + ret = tcpm_set_vconn(port, true); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY, TCPC_TX_SOP); + /* + * USB PD 3.0 Section 6.4.4.3.1 + * + * Note that a Cable Plug or VPD will not be ready for PD + * Communication until tVCONNStable after VCONN has been applied + */ + if (!ret) + tcpm_set_state(port, VCONN_SWAP_SEND_SOFT_RESET, + PD_T_VCONN_STABLE); + else + tcpm_set_state(port, ready_state(port), 0); break; case VCONN_SWAP_TURN_OFF_VCONN: tcpm_set_vconn(port, false); tcpm_set_state(port, ready_state(port), 0); break; + case VCONN_SWAP_SEND_SOFT_RESET: + tcpm_swap_complete(port, port->swap_status); + if (tcpm_can_communicate_sop_prime(port)) { + port->tx_sop_type = TCPC_TX_SOP_PRIME; + port->upcoming_state = SOFT_RESET_SEND; + tcpm_ams_start(port, SOFT_RESET_AMS); + } else { + tcpm_set_state(port, ready_state(port), 0); + } + break; case DR_SWAP_CANCEL: case PR_SWAP_CANCEL: @@ -4862,7 +5598,7 @@ static void run_state_machine(struct tcpm_port *port) } break; case GET_STATUS_SEND: - tcpm_pd_send_control(port, PD_CTRL_GET_STATUS); + tcpm_pd_send_control(port, PD_CTRL_GET_STATUS, TCPC_TX_SOP); tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4870,7 +5606,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, ready_state(port), 0); break; case GET_PPS_STATUS_SEND: - tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS); + tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS, TCPC_TX_SOP); tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT, PD_T_SENDER_RESPONSE); break; @@ -4878,7 +5614,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, ready_state(port), 0); break; case GET_SINK_CAP: - tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP); + tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP, TCPC_TX_SOP); tcpm_set_state(port, GET_SINK_CAP_TIMEOUT, PD_T_SENDER_RESPONSE); break; case GET_SINK_CAP_TIMEOUT: @@ -4922,9 +5658,18 @@ static void run_state_machine(struct tcpm_port *port) /* Chunk state */ case CHUNK_NOT_SUPP: - tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP, TCPC_TX_SOP); tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? SRC_READY : SNK_READY, 0); break; + + /* Cable states */ + case SRC_VDM_IDENTITY_REQUEST: + port->send_discover_prime = true; + port->tx_sop_type = TCPC_TX_SOP_PRIME; + mod_send_discover_delayed_work(port, 0); + port->upcoming_state = SRC_SEND_CAPABILITIES; + break; + default: WARN(1, "Unexpected port state %d\n", port->state); break; @@ -5616,7 +6361,8 @@ static void tcpm_enable_frs_work(struct kthread_work *work) goto unlock; /* Send when the state machine is idle */ - if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover) + if (port->state != SNK_READY || port->vdm_sm_running || port->send_discover || + port->send_discover_prime) goto resched; port->upcoming_state = GET_SINK_CAP; @@ -5639,21 +6385,23 @@ static void tcpm_send_discover_work(struct kthread_work *work) mutex_lock(&port->lock); /* No need to send DISCOVER_IDENTITY anymore */ - if (!port->send_discover) + if (!port->send_discover && !port->send_discover_prime) goto unlock; if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) { port->send_discover = false; + port->send_discover_prime = false; goto unlock; } /* Retry if the port is not idle */ - if ((port->state != SRC_READY && port->state != SNK_READY) || port->vdm_sm_running) { + if ((port->state != SRC_READY && port->state != SNK_READY && + port->state != SRC_VDM_IDENTITY_REQUEST) || port->vdm_sm_running) { mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS); goto unlock; } - tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0); + tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0, port->tx_sop_type); unlock: mutex_unlock(&port->lock); @@ -6880,6 +7628,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) typec_port_register_altmodes(port->typec_port, &tcpm_altmode_ops, port, port->port_altmode, ALTMODE_DISCOVERY_MAX); + typec_port_register_cable_ops(port->port_altmode, ARRAY_SIZE(port->port_altmode), + &tcpm_cable_ops); port->registered = true; mutex_lock(&port->lock); diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c index 87d4abde0e..cf719307b3 100644 --- a/drivers/usb/typec/tcpm/wcove.c +++ b/drivers/usb/typec/tcpm/wcove.c @@ -535,7 +535,7 @@ static irqreturn_t wcove_typec_irq(int irq, void *data) goto err; } - tcpm_pd_receive(wcove->tcpm, &msg); + tcpm_pd_receive(wcove->tcpm, &msg, TCPC_TX_SOP); ret = regmap_read(wcove->regmap, USBC_RXSTATUS, &status); diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index b501760012..bd6ae92aa3 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -403,6 +403,27 @@ static int ucsi_register_altmode(struct ucsi_connector *con, con->partner_altmode[i] = alt; break; + case UCSI_RECIPIENT_SOP_P: + i = ucsi_next_altmode(con->plug_altmode); + if (i < 0) { + ret = i; + goto err; + } + + ret = ucsi_altmode_next_mode(con->plug_altmode, desc->svid); + if (ret < 0) + return ret; + + desc->mode = ret; + + alt = typec_plug_register_altmode(con->plug, desc); + if (IS_ERR(alt)) { + ret = PTR_ERR(alt); + goto err; + } + + con->plug_altmode[i] = alt; + break; default: return -EINVAL; } @@ -570,6 +591,9 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient) case UCSI_RECIPIENT_SOP: adev = con->partner_altmode; break; + case UCSI_RECIPIENT_SOP_P: + adev = con->plug_altmode; + break; default: return; } @@ -650,6 +674,108 @@ static int ucsi_get_src_pdos(struct ucsi_connector *con) return ret; } +static int ucsi_read_identity(struct ucsi_connector *con, u8 recipient, + u8 offset, u8 bytes, void *resp) +{ + struct ucsi *ucsi = con->ucsi; + u64 command; + int ret; + + command = UCSI_COMMAND(UCSI_GET_PD_MESSAGE) | + UCSI_CONNECTOR_NUMBER(con->num); + command |= UCSI_GET_PD_MESSAGE_RECIPIENT(recipient); + command |= UCSI_GET_PD_MESSAGE_OFFSET(offset); + command |= UCSI_GET_PD_MESSAGE_BYTES(bytes); + command |= UCSI_GET_PD_MESSAGE_TYPE(UCSI_GET_PD_MESSAGE_TYPE_IDENTITY); + + ret = ucsi_send_command(ucsi, command, resp, bytes); + if (ret < 0) + dev_err(ucsi->dev, "UCSI_GET_PD_MESSAGE failed (%d)\n", ret); + + return ret; +} + +static int ucsi_get_identity(struct ucsi_connector *con, u8 recipient, + struct usb_pd_identity *id) +{ + struct ucsi *ucsi = con->ucsi; + struct ucsi_pd_message_disc_id resp = {}; + int ret; + + if (ucsi->version < UCSI_VERSION_2_0) { + /* + * Before UCSI v2.0, MESSAGE_IN is 16 bytes which cannot fit + * the 28 byte identity response including the VDM header. + * First request the VDM header, ID Header VDO, Cert Stat VDO + * and Product VDO. + */ + ret = ucsi_read_identity(con, recipient, 0, 0x10, &resp); + if (ret < 0) + return ret; + + + /* Then request Product Type VDO1 through Product Type VDO3. */ + ret = ucsi_read_identity(con, recipient, 0x10, 0xc, + &resp.vdo[0]); + if (ret < 0) + return ret; + + } else { + /* + * In UCSI v2.0 and after, MESSAGE_IN is large enough to request + * the large enough to request the full Discover Identity + * response at once. + */ + ret = ucsi_read_identity(con, recipient, 0x0, 0x1c, &resp); + if (ret < 0) + return ret; + } + + id->id_header = resp.id_header; + id->cert_stat = resp.cert_stat; + id->product = resp.product; + id->vdo[0] = resp.vdo[0]; + id->vdo[1] = resp.vdo[1]; + id->vdo[2] = resp.vdo[2]; + return 0; +} + +static int ucsi_get_partner_identity(struct ucsi_connector *con) +{ + int ret; + + ret = ucsi_get_identity(con, UCSI_RECIPIENT_SOP, + &con->partner_identity); + if (ret < 0) + return ret; + + ret = typec_partner_set_identity(con->partner); + if (ret < 0) { + dev_err(con->ucsi->dev, "Failed to set partner identity (%d)\n", + ret); + } + + return ret; +} + +static int ucsi_get_cable_identity(struct ucsi_connector *con) +{ + int ret; + + ret = ucsi_get_identity(con, UCSI_RECIPIENT_SOP_P, + &con->cable_identity); + if (ret < 0) + return ret; + + ret = typec_cable_set_identity(con->cable); + if (ret < 0) { + dev_err(con->ucsi->dev, "Failed to set cable identity (%d)\n", + ret); + } + + return ret; +} + static int ucsi_check_altmodes(struct ucsi_connector *con) { int ret, num_partner_am; @@ -738,6 +864,82 @@ static void ucsi_unregister_partner_pdos(struct ucsi_connector *con) con->partner_pd = NULL; } +static int ucsi_register_plug(struct ucsi_connector *con) +{ + struct typec_plug *plug; + struct typec_plug_desc desc = {.index = TYPEC_PLUG_SOP_P}; + + plug = typec_register_plug(con->cable, &desc); + if (IS_ERR(plug)) { + dev_err(con->ucsi->dev, + "con%d: failed to register plug (%ld)\n", con->num, + PTR_ERR(plug)); + return PTR_ERR(plug); + } + + con->plug = plug; + return 0; +} + +static void ucsi_unregister_plug(struct ucsi_connector *con) +{ + if (!con->plug) + return; + + ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP_P); + typec_unregister_plug(con->plug); + con->plug = NULL; +} + +static int ucsi_register_cable(struct ucsi_connector *con) +{ + struct typec_cable *cable; + struct typec_cable_desc desc = {}; + + switch (UCSI_CABLE_PROP_FLAG_PLUG_TYPE(con->cable_prop.flags)) { + case UCSI_CABLE_PROPERTY_PLUG_TYPE_A: + desc.type = USB_PLUG_TYPE_A; + break; + case UCSI_CABLE_PROPERTY_PLUG_TYPE_B: + desc.type = USB_PLUG_TYPE_B; + break; + case UCSI_CABLE_PROPERTY_PLUG_TYPE_C: + desc.type = USB_PLUG_TYPE_C; + break; + default: + desc.type = USB_PLUG_NONE; + break; + } + + desc.identity = &con->cable_identity; + desc.active = !!(UCSI_CABLE_PROP_FLAG_ACTIVE_CABLE & + con->cable_prop.flags); + desc.pd_revision = UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV_AS_BCD( + con->cable_prop.flags); + + cable = typec_register_cable(con->port, &desc); + if (IS_ERR(cable)) { + dev_err(con->ucsi->dev, + "con%d: failed to register cable (%ld)\n", con->num, + PTR_ERR(cable)); + return PTR_ERR(cable); + } + + con->cable = cable; + return 0; +} + +static void ucsi_unregister_cable(struct ucsi_connector *con) +{ + if (!con->cable) + return; + + ucsi_unregister_plug(con); + typec_unregister_cable(con->cable); + memset(&con->cable_identity, 0, sizeof(con->cable_identity)); + con->cable = NULL; +} + static void ucsi_pwr_opmode_change(struct ucsi_connector *con) { switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) { @@ -785,7 +987,9 @@ static int ucsi_register_partner(struct ucsi_connector *con) break; } + desc.identity = &con->partner_identity; desc.usb_pd = pwr_opmode == UCSI_CONSTAT_PWR_OPMODE_PD; + desc.pd_revision = UCSI_CONCAP_FLAG_PARTNER_PD_MAJOR_REV_AS_BCD(con->cap.flags); partner = typec_register_partner(con->port, &desc); if (IS_ERR(partner)) { @@ -810,7 +1014,9 @@ static void ucsi_unregister_partner(struct ucsi_connector *con) typec_partner_set_usb_power_delivery(con->partner, NULL); ucsi_unregister_partner_pdos(con); ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP); + ucsi_unregister_cable(con); typec_unregister_partner(con->partner); + memset(&con->partner_identity, 0, sizeof(con->partner_identity)); con->partner = NULL; } @@ -860,6 +1066,27 @@ static void ucsi_partner_change(struct ucsi_connector *con) con->num, u_role); } +static int ucsi_check_connector_capability(struct ucsi_connector *con) +{ + u64 command; + int ret; + + if (!con->partner || con->ucsi->version < UCSI_VERSION_2_0) + return 0; + + command = UCSI_GET_CONNECTOR_CAPABILITY | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(con->ucsi, command, &con->cap, sizeof(con->cap)); + if (ret < 0) { + dev_err(con->ucsi->dev, "GET_CONNECTOR_CAPABILITY failed (%d)\n", ret); + return ret; + } + + typec_partner_set_pd_revision(con->partner, + UCSI_CONCAP_FLAG_PARTNER_PD_MAJOR_REV_AS_BCD(con->cap.flags)); + + return ret; +} + static int ucsi_check_connection(struct ucsi_connector *con) { u8 prev_flags = con->status.flags; @@ -889,6 +1116,46 @@ static int ucsi_check_connection(struct ucsi_connector *con) return 0; } +static int ucsi_check_cable(struct ucsi_connector *con) +{ + u64 command; + int ret; + + if (con->cable) + return 0; + + command = UCSI_GET_CABLE_PROPERTY | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(con->ucsi, command, &con->cable_prop, + sizeof(con->cable_prop)); + if (ret < 0) { + dev_err(con->ucsi->dev, "GET_CABLE_PROPERTY failed (%d)\n", + ret); + return ret; + } + + ret = ucsi_register_cable(con); + if (ret < 0) + return ret; + + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) { + ret = ucsi_get_cable_identity(con); + if (ret < 0) + return ret; + } + + if (con->ucsi->cap.features & UCSI_CAP_ALT_MODE_DETAILS) { + ret = ucsi_register_plug(con); + if (ret < 0) + return ret; + + ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP_P); + if (ret < 0) + return ret; + } + + return 0; +} + static void ucsi_handle_connector_change(struct work_struct *work) { struct ucsi_connector *con = container_of(work, struct ucsi_connector, @@ -929,6 +1196,11 @@ static void ucsi_handle_connector_change(struct work_struct *work) if (con->status.flags & UCSI_CONSTAT_CONNECTED) { ucsi_register_partner(con); ucsi_partner_task(con, ucsi_check_connection, 1, HZ); + ucsi_partner_task(con, ucsi_check_connector_capability, 1, HZ); + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) + ucsi_partner_task(con, ucsi_get_partner_identity, 1, HZ); + if (con->ucsi->cap.features & UCSI_CAP_CABLE_DETAILS) + ucsi_partner_task(con, ucsi_check_cable, 1, HZ); if (UCSI_CONSTAT_PWR_OPMODE(con->status.flags) == UCSI_CONSTAT_PWR_OPMODE_PD) @@ -1361,6 +1633,10 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con) ucsi_register_partner(con); ucsi_pwr_opmode_change(con); ucsi_port_psy_changed(con); + if (con->ucsi->cap.features & UCSI_CAP_GET_PD_MESSAGE) + ucsi_get_partner_identity(con); + if (con->ucsi->cap.features & UCSI_CAP_CABLE_DETAILS) + ucsi_check_cable(con); } /* Only notify USB controller if partner supports USB data */ diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index ede9876488..0e7c92eb1b 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -10,6 +10,7 @@ #include <linux/usb/typec.h> #include <linux/usb/pd.h> #include <linux/usb/role.h> +#include <asm/unaligned.h> /* -------------------------------------------------------------------------- */ @@ -35,6 +36,12 @@ struct dentry; #define UCSI_BCD_GET_MINOR(_v_) (((_v_) >> 4) & 0x0F) #define UCSI_BCD_GET_SUBMINOR(_v_) ((_v_) & 0x0F) +/* + * Per USB PD 3.2, Section 6.2.1.1.5, the spec revision is represented by 2 bits + * 0b00 = 1.0, 0b01 = 2.0, 0b10 = 3.0, 0b11 = Reserved, Shall NOT be used. + */ +#define UCSI_SPEC_REVISION_TO_BCD(_v_) (((_v_) + 1) << 8) + /* Command Status and Connector Change Indication (CCI) bits */ #define UCSI_CCI_CONNECTOR(_c_) (((_c_) & GENMASK(7, 1)) >> 1) #define UCSI_CCI_LENGTH(_c_) (((_c_) & GENMASK(15, 8)) >> 8) @@ -99,6 +106,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_GET_CABLE_PROPERTY 0x11 #define UCSI_GET_CONNECTOR_STATUS 0x12 #define UCSI_GET_ERROR_STATUS 0x13 +#define UCSI_GET_PD_MESSAGE 0x15 #define UCSI_CONNECTOR_NUMBER(_num_) ((u64)(_num_) << 16) #define UCSI_COMMAND(_cmd_) ((_cmd_) & 0xff) @@ -152,6 +160,18 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num); #define UCSI_MAX_PDOS (4) #define UCSI_GET_PDOS_SRC_PDOS ((u64)1 << 34) +/* GET_PD_MESSAGE command bits */ +#define UCSI_GET_PD_MESSAGE_RECIPIENT(_r_) ((u64)(_r_) << 23) +#define UCSI_GET_PD_MESSAGE_OFFSET(_r_) ((u64)(_r_) << 26) +#define UCSI_GET_PD_MESSAGE_BYTES(_r_) ((u64)(_r_) << 34) +#define UCSI_GET_PD_MESSAGE_TYPE(_r_) ((u64)(_r_) << 42) +#define UCSI_GET_PD_MESSAGE_TYPE_SNK_CAP_EXT 0 +#define UCSI_GET_PD_MESSAGE_TYPE_SRC_CAP_EXT 1 +#define UCSI_GET_PD_MESSAGE_TYPE_BAT_CAP 2 +#define UCSI_GET_PD_MESSAGE_TYPE_BAT_STAT 3 +#define UCSI_GET_PD_MESSAGE_TYPE_IDENTITY 4 +#define UCSI_GET_PD_MESSAGE_TYPE_REVISION 5 + /* -------------------------------------------------------------------------- */ /* Error information returned by PPM in response to GET_ERROR_STATUS command. */ @@ -186,7 +206,7 @@ struct ucsi_capability { #define UCSI_CAP_ATTR_POWER_OTHER BIT(10) #define UCSI_CAP_ATTR_POWER_VBUS BIT(14) u8 num_connectors; - u8 features; + u16 features; #define UCSI_CAP_SET_UOM BIT(0) #define UCSI_CAP_SET_PDM BIT(1) #define UCSI_CAP_ALT_MODE_DETAILS BIT(2) @@ -195,7 +215,8 @@ struct ucsi_capability { #define UCSI_CAP_CABLE_DETAILS BIT(5) #define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6) #define UCSI_CAP_PD_RESET BIT(7) - u16 reserved_1; +#define UCSI_CAP_GET_PD_MESSAGE BIT(8) + u8 reserved_1; u8 num_alt_modes; u8 reserved_2; u16 bc_version; @@ -214,9 +235,29 @@ struct ucsi_connector_capability { #define UCSI_CONCAP_OPMODE_USB2 BIT(5) #define UCSI_CONCAP_OPMODE_USB3 BIT(6) #define UCSI_CONCAP_OPMODE_ALT_MODE BIT(7) - u8 flags; + u32 flags; #define UCSI_CONCAP_FLAG_PROVIDER BIT(0) #define UCSI_CONCAP_FLAG_CONSUMER BIT(1) +#define UCSI_CONCAP_FLAG_SWAP_TO_DFP BIT(2) +#define UCSI_CONCAP_FLAG_SWAP_TO_UFP BIT(3) +#define UCSI_CONCAP_FLAG_SWAP_TO_SRC BIT(4) +#define UCSI_CONCAP_FLAG_SWAP_TO_SINK BIT(5) +#define UCSI_CONCAP_FLAG_EX_OP_MODE(_f_) \ + (((_f_) & GENMASK(13, 6)) >> 6) +#define UCSI_CONCAP_EX_OP_MODE_USB4_GEN2 BIT(0) +#define UCSI_CONCAP_EX_OP_MODE_EPR_SRC BIT(1) +#define UCSI_CONCAP_EX_OP_MODE_EPR_SINK BIT(2) +#define UCSI_CONCAP_EX_OP_MODE_USB4_GEN3 BIT(3) +#define UCSI_CONCAP_EX_OP_MODE_USB4_GEN4 BIT(4) +#define UCSI_CONCAP_FLAG_MISC_CAPS(_f_) \ + (((_f_) & GENMASK(17, 14)) >> 14) +#define UCSI_CONCAP_MISC_CAP_FW_UPDATE BIT(0) +#define UCSI_CONCAP_MISC_CAP_SECURITY BIT(1) +#define UCSI_CONCAP_FLAG_REV_CURR_PROT_SUPPORT BIT(18) +#define UCSI_CONCAP_FLAG_PARTNER_PD_MAJOR_REV(_f_) \ + (((_f_) & GENMASK(20, 19)) >> 19) +#define UCSI_CONCAP_FLAG_PARTNER_PD_MAJOR_REV_AS_BCD(_f_) \ + UCSI_SPEC_REVISION_TO_BCD(UCSI_CONCAP_FLAG_PARTNER_PD_MAJOR_REV(_f_)) } __packed; struct ucsi_altmode { @@ -238,6 +279,9 @@ struct ucsi_cable_property { #define UCSI_CABLE_PROPERTY_PLUG_TYPE_C 2 #define UCSI_CABLE_PROPERTY_PLUG_OTHER 3 #define UCSI_CABLE_PROP_FLAG_MODE_SUPPORT BIT(5) +#define UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV(_f_) (((_f_) & GENMASK(7, 6)) >> 6) +#define UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV_AS_BCD(_f_) \ + UCSI_SPEC_REVISION_TO_BCD(UCSI_CABLE_PROP_FLAG_PD_MAJOR_REV(_f_)) u8 latency; } __packed; @@ -276,15 +320,48 @@ struct ucsi_connector_status { #define UCSI_CONSTAT_PARTNER_TYPE_DEBUG 5 #define UCSI_CONSTAT_PARTNER_TYPE_AUDIO 6 u32 request_data_obj; - u8 pwr_status; -#define UCSI_CONSTAT_BC_STATUS(_p_) ((_p_) & GENMASK(2, 0)) + + u8 pwr_status[3]; +#define UCSI_CONSTAT_BC_STATUS(_p_) ((_p_[0]) & GENMASK(1, 0)) #define UCSI_CONSTAT_BC_NOT_CHARGING 0 #define UCSI_CONSTAT_BC_NOMINAL_CHARGING 1 #define UCSI_CONSTAT_BC_SLOW_CHARGING 2 #define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3 -#define UCSI_CONSTAT_PROVIDER_CAP_LIMIT(_p_) (((_p_) & GENMASK(6, 3)) >> 3) +#define UCSI_CONSTAT_PROVIDER_CAP_LIMIT(_p_) (((_p_[0]) & GENMASK(5, 2)) >> 2) #define UCSI_CONSTAT_CAP_PWR_LOWERED 0 #define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT 1 +#define UCSI_CONSTAT_PROVIDER_PD_VERSION_OPER_MODE(_p_) \ + ((get_unaligned_le32(_p_) & GENMASK(21, 6)) >> 6) +#define UCSI_CONSTAT_ORIENTATION(_p_) (((_p_[2]) & GENMASK(6, 6)) >> 6) +#define UCSI_CONSTAT_ORIENTATION_DIRECT 0 +#define UCSI_CONSTAT_ORIENTATION_FLIPPED 1 +#define UCSI_CONSTAT_SINK_PATH_STATUS(_p_) (((_p_[2]) & GENMASK(7, 7)) >> 7) +#define UCSI_CONSTAT_SINK_PATH_DISABLED 0 +#define UCSI_CONSTAT_SINK_PATH_ENABLED 1 + u8 pwr_readings[9]; +#define UCSI_CONSTAT_REV_CURR_PROT_STATUS(_p_) ((_p_[0]) & 0x1) +#define UCSI_CONSTAT_PWR_READING_VALID(_p_) (((_p_[0]) & GENMASK(1, 1)) >> 1) +#define UCSI_CONSTAT_CURRENT_SCALE(_p_) (((_p_[0]) & GENMASK(4, 2)) >> 2) +#define UCSI_CONSTAT_PEAK_CURRENT(_p_) \ + ((get_unaligned_le32(_p_) & GENMASK(20, 5)) >> 5) +#define UCSI_CONSTAT_AVG_CURRENT(_p_) \ + ((get_unaligned_le32(&(_p_)[2]) & GENMASK(20, 5)) >> 5) +#define UCSI_CONSTAT_VOLTAGE_SCALE(_p_) \ + ((get_unaligned_le16(&(_p_)[4]) & GENMASK(8, 5)) >> 5) +#define UCSI_CONSTAT_VOLTAGE_READING(_p_) \ + ((get_unaligned_le32(&(_p_)[5]) & GENMASK(16, 1)) >> 1) +} __packed; + +/* + * Data structure filled by PPM in response to GET_PD_MESSAGE command with the + * Response Message Type set to Discover Identity Response. + */ +struct ucsi_pd_message_disc_id { + u32 vdm_header; + u32 id_header; + u32 cert_stat; + u32 product; + u32 vdo[3]; } __packed; /* -------------------------------------------------------------------------- */ @@ -352,14 +429,18 @@ struct ucsi_connector { struct typec_port *port; struct typec_partner *partner; + struct typec_cable *cable; + struct typec_plug *plug; struct typec_altmode *port_altmode[UCSI_MAX_ALTMODES]; struct typec_altmode *partner_altmode[UCSI_MAX_ALTMODES]; + struct typec_altmode *plug_altmode[UCSI_MAX_ALTMODES]; struct typec_capability typec_cap; struct ucsi_connector_status status; struct ucsi_connector_capability cap; + struct ucsi_cable_property cable_prop; struct power_supply *psy; struct power_supply_desc psy_desc; u32 rdo; @@ -375,6 +456,10 @@ struct ucsi_connector { struct usb_power_delivery_capabilities *partner_sink_caps; struct usb_role_switch *usb_role_sw; + + /* USB PD identity */ + struct usb_pd_identity partner_identity; + struct usb_pd_identity cable_identity; }; int ucsi_send_command(struct ucsi *ucsi, u64 command, diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index 449c125f6f..dda7c7c94e 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -192,6 +192,12 @@ struct ucsi_ccg_altmode { bool checked; } __packed; +#define CCGX_MESSAGE_IN_MAX 4 +struct op_region { + __le32 cci; + __le32 message_in[CCGX_MESSAGE_IN_MAX]; +}; + struct ucsi_ccg { struct device *dev; struct ucsi *ucsi; @@ -222,6 +228,13 @@ struct ucsi_ccg { bool has_multiple_dp; struct ucsi_ccg_altmode orig[UCSI_MAX_ALTMODES]; struct ucsi_ccg_altmode updated[UCSI_MAX_ALTMODES]; + + /* + * This spinlock protects op_data which includes CCI and MESSAGE_IN that + * will be updated in ISR + */ + spinlock_t op_lock; + struct op_region op_data; }; static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) @@ -305,12 +318,42 @@ static int ccg_write(struct ucsi_ccg *uc, u16 rab, const u8 *data, u32 len) return 0; } +static int ccg_op_region_update(struct ucsi_ccg *uc, u32 cci) +{ + u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_MESSAGE_IN); + struct op_region *data = &uc->op_data; + unsigned char *buf; + size_t size = sizeof(data->message_in); + + buf = kzalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; + if (UCSI_CCI_LENGTH(cci)) { + int ret = ccg_read(uc, reg, (void *)buf, size); + + if (ret) { + kfree(buf); + return ret; + } + } + + spin_lock(&uc->op_lock); + data->cci = cpu_to_le32(cci); + if (UCSI_CCI_LENGTH(cci)) + memcpy(&data->message_in, buf, size); + spin_unlock(&uc->op_lock); + kfree(buf); + return 0; +} + static int ucsi_ccg_init(struct ucsi_ccg *uc) { unsigned int count = 10; u8 data; int status; + spin_lock_init(&uc->op_lock); + data = CCGX_RAB_UCSI_CONTROL_STOP; status = ccg_write(uc, CCGX_RAB_UCSI_CONTROL, &data, sizeof(data)); if (status < 0) @@ -520,9 +563,20 @@ static int ucsi_ccg_read(struct ucsi *ucsi, unsigned int offset, u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset); struct ucsi_capability *cap; struct ucsi_altmode *alt; - int ret; + int ret = 0; + + if (offset == UCSI_CCI) { + spin_lock(&uc->op_lock); + memcpy(val, &(uc->op_data).cci, val_len); + spin_unlock(&uc->op_lock); + } else if (offset == UCSI_MESSAGE_IN) { + spin_lock(&uc->op_lock); + memcpy(val, &(uc->op_data).message_in, val_len); + spin_unlock(&uc->op_lock); + } else { + ret = ccg_read(uc, reg, val, val_len); + } - ret = ccg_read(uc, reg, val, val_len); if (ret) return ret; @@ -559,9 +613,18 @@ static int ucsi_ccg_read(struct ucsi *ucsi, unsigned int offset, static int ucsi_ccg_async_write(struct ucsi *ucsi, unsigned int offset, const void *val, size_t val_len) { + struct ucsi_ccg *uc = ucsi_get_drvdata(ucsi); u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(offset); - return ccg_write(ucsi_get_drvdata(ucsi), reg, val, val_len); + /* + * UCSI may read CCI instantly after async_write, + * clear CCI to avoid caller getting wrong data before we get CCI from ISR + */ + spin_lock(&uc->op_lock); + uc->op_data.cci = 0; + spin_unlock(&uc->op_lock); + + return ccg_write(uc, reg, val, val_len); } static int ucsi_ccg_sync_write(struct ucsi *ucsi, unsigned int offset, @@ -615,13 +678,18 @@ static irqreturn_t ccg_irq_handler(int irq, void *data) u16 reg = CCGX_RAB_UCSI_DATA_BLOCK(UCSI_CCI); struct ucsi_ccg *uc = data; u8 intr_reg; - u32 cci; - int ret; + u32 cci = 0; + int ret = 0; ret = ccg_read(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); if (ret) return ret; + if (!intr_reg) + return IRQ_HANDLED; + else if (!(intr_reg & UCSI_READ_INT)) + goto err_clear_irq; + ret = ccg_read(uc, reg, (void *)&cci, sizeof(cci)); if (ret) goto err_clear_irq; @@ -629,13 +697,21 @@ static irqreturn_t ccg_irq_handler(int irq, void *data) if (UCSI_CCI_CONNECTOR(cci)) ucsi_connector_change(uc->ucsi, UCSI_CCI_CONNECTOR(cci)); - if (test_bit(DEV_CMD_PENDING, &uc->flags) && - cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) - complete(&uc->complete); + /* + * As per CCGx UCSI interface guide, copy CCI and MESSAGE_IN + * to the OpRegion before clear the UCSI interrupt + */ + ret = ccg_op_region_update(uc, cci); + if (ret) + goto err_clear_irq; err_clear_irq: ccg_write(uc, CCGX_RAB_INTR_REG, &intr_reg, sizeof(intr_reg)); + if (!ret && test_bit(DEV_CMD_PENDING, &uc->flags) && + cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE)) + complete(&uc->complete); + return IRQ_HANDLED; } |