From 03eb4c33140da16e02c4f47e53c0421681bde8ce Mon Sep 17 00:00:00 2001 From: Souptick Joarder Date: Sat, 21 Apr 2018 01:24:24 +0530 Subject: [PATCH 01/45] usb: mon: Change return type to vm_fault_t Use new return type vm_fault_t for the fault handler in struct vm_operations_struct. For now, this is just documenting that the function returns a VM_FAULT value rather than an errno. Once all instances are converted, vm_fault_t will become a distinct type. Reference id -> 1c8f422059ae ("mm: change return type to vm_fault_t") Signed-off-by: Souptick Joarder Reviewed-by: Matthew Wilcox Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mon/mon_bin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index 2761fad66b95..34e866ad4a81 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -1227,7 +1227,7 @@ static void mon_bin_vma_close(struct vm_area_struct *vma) /* * Map ring pages to user space. */ -static int mon_bin_vma_fault(struct vm_fault *vmf) +static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf) { struct mon_reader_bin *rp = vmf->vma->vm_private_data; unsigned long offset, chunk_idx; From 548f4726a10568c208a7a9d676d66470e9e99aa3 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Thu, 29 Mar 2018 02:14:11 +0000 Subject: [PATCH 02/45] usb: roles: Fix potential NULL dereference in intel_xhci_usb_probe() platform_get_resource() may fail and return NULL, so we should better check it's return value to avoid a NULL pointer dereference a bit later in the code. This is detected by Coccinelle semantic patch. @@ expression pdev, res, n, t, e, e1, e2; @@ res = platform_get_resource(pdev, t, n); + if (!res) + return -EINVAL; ... when != res == NULL e = devm_ioremap_nocache(e1, res->start, e2); Signed-off-by: Wei Yongjun Signed-off-by: Greg Kroah-Hartman --- drivers/usb/roles/intel-xhci-usb-role-switch.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c index de72eedb762e..28102127b9d5 100644 --- a/drivers/usb/roles/intel-xhci-usb-role-switch.c +++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -144,6 +144,8 @@ static int intel_xhci_usb_probe(struct platform_device *pdev) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; data->base = devm_ioremap_nocache(dev, res->start, resource_size(res)); if (!data->base) return -ENOMEM; From a47060cf40c6c8e6e125692f5d73ceea173b12b7 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 6 Apr 2018 15:31:49 +0200 Subject: [PATCH 03/45] usb: audio-v2: Correct the comment for struct uac_clock_selector_descriptor The comment in UAC2 clock selector descriptor definition mentions the bAssocTerminal after baCSourceID[], but it doesn't exist in the actual definition. Let's correct it. Fixes: 5dd360ebd832 ("include/linux/usb/audio-v2.h: add more UAC2 details") Signed-off-by: Takashi Iwai Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/audio-v2.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index aaafecf073ff..49699255cfd3 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -94,7 +94,7 @@ struct uac_clock_selector_descriptor { __u8 bClockID; __u8 bNrInPins; __u8 baCSourceID[]; - /* bmControls, bAssocTerminal and iClockSource omitted */ + /* bmControls and iClockSource omitted */ } __attribute__((packed)); /* 4.7.2.3 Clock Multiplier Descriptor */ From 2a3dae10d51d262514498fe710c963da946ce6e2 Mon Sep 17 00:00:00 2001 From: Jia-Ju Bai Date: Tue, 10 Apr 2018 09:29:08 +0800 Subject: [PATCH 04/45] usb: storage: Replace mdelay with msleep in init_freecom init_freecom() is never called in atomic context. init_freecom() is set as ".initFunction" through UNUSUAL_DEV(). And ->initFunction() is only called by usb_stor_acquire_resources(), which is only called by usb_stor_probe2(). usb_stor_probe2() is called by *_probe() functions (like alauda_probe()) for each USB driver. *_probe() functions are set ".probe" in struct usb_driver. These functions are not called in atomic context. Despite never getting called from atomic context, init_freecom() calls mdelay() to busily wait. This is not necessary and can be replaced with msleep() to avoid busy waiting. This is found by a static analysis tool named DCNS written by myself. And I also manually check it. Signed-off-by: Jia-Ju Bai Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/freecom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/storage/freecom.c b/drivers/usb/storage/freecom.c index ec4d92c92762..4f542df37a44 100644 --- a/drivers/usb/storage/freecom.c +++ b/drivers/usb/storage/freecom.c @@ -464,7 +464,7 @@ static int init_freecom(struct us_data *us) usb_stor_dbg(us, "result from activate reset is %d\n", result); /* wait 250ms */ - mdelay(250); + msleep(250); /* clear reset */ result = usb_stor_control_msg(us, us->send_ctrl_pipe, @@ -472,7 +472,7 @@ static int init_freecom(struct us_data *us) usb_stor_dbg(us, "result from clear reset is %d\n", result); /* wait 3 seconds */ - mdelay(3 * 1000); + msleep(3 * 1000); return USB_STOR_TRANSPORT_GOOD; } From 0f0290089abd37ca54db764c294fe7e47624a313 Mon Sep 17 00:00:00 2001 From: Jia-Ju Bai Date: Tue, 10 Apr 2018 15:36:40 +0800 Subject: [PATCH 05/45] usb: isp1760: Replace mdelay with msleep in isp1760_init_core isp1760_init_core() is never called in atomic context. The call chains ending up at isp1760_init_core() are: [1] isp1760_init_core() <- isp1760_register() <- isp1760_plat_probe() [2] isp1760_init_core() <- isp1760_register() <- isp1761_pci_probe() isp1760_plat_probe() is set as ".probe" in struct platform_driver. isp1761_pci_probe() is set as ".probe" in struct pci_driver. These functions are not called in atomic context. Despite never getting called from atomic context, isp1761_pci_probe() calls mdelay() to busily wait. This is not necessary and can be replaced with msleep() to avoid busy waiting. This is found by a static analysis tool named DCNS written by myself. And I also manually check it. Signed-off-by: Jia-Ju Bai Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/isp1760/isp1760-core.c b/drivers/usb/isp1760/isp1760-core.c index 05d22589b5cc..55b94fd10331 100644 --- a/drivers/usb/isp1760/isp1760-core.c +++ b/drivers/usb/isp1760/isp1760-core.c @@ -31,7 +31,7 @@ static void isp1760_init_core(struct isp1760_device *isp) /* Low-level chip reset */ if (isp->rst_gpio) { gpiod_set_value_cansleep(isp->rst_gpio, 1); - mdelay(50); + msleep(50); gpiod_set_value_cansleep(isp->rst_gpio, 0); } From de0611b27d15ddc831d4c47d3b4be592c5587948 Mon Sep 17 00:00:00 2001 From: Jia-Ju Bai Date: Tue, 10 Apr 2018 15:37:06 +0800 Subject: [PATCH 06/45] usb: isp1760: Replace mdelay with msleep in isp1760_stop isp1760_stop() is never called in atomic context. The call chain ending up at isp1760_stop() is: [1] isp1760_stop() <- isp1760_shutdown() isp1760_shutdown() is set as ".shutdown" in struct hc_driver. isp1760_stop() is also set as ".stop" in hc_driver. These functions are not called in atomic context. Despite never getting called from atomic context, isp1760_stop() calls mdelay() to busily wait. This is not necessary and can be replaced with msleep() to avoid busy waiting. This is found by a static analysis tool named DCNS written by myself. And I also manually check it Signed-off-by: Jia-Ju Bai Signed-off-by: Greg Kroah-Hartman --- drivers/usb/isp1760/isp1760-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/isp1760/isp1760-hcd.c b/drivers/usb/isp1760/isp1760-hcd.c index 42672d6ec525..1045521be293 100644 --- a/drivers/usb/isp1760/isp1760-hcd.c +++ b/drivers/usb/isp1760/isp1760-hcd.c @@ -2093,7 +2093,7 @@ static void isp1760_stop(struct usb_hcd *hcd) isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1, NULL, 0); - mdelay(20); + msleep(20); spin_lock_irq(&priv->lock); ehci_reset(hcd); From cd91a0e9d38fa85ad8e95ca7849ce4adc00e3caa Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 17 Apr 2018 19:49:20 +0200 Subject: [PATCH 07/45] usb: Remove depends on HAS_DMA in case of platform dependency Remove dependencies on HAS_DMA where a Kconfig symbol depends on another symbol that implies HAS_DMA, and, optionally, on "|| COMPILE_TEST". In most cases this other symbol is an architecture or platform specific symbol, or PCI. Generic symbols and drivers without platform dependencies keep their dependencies on HAS_DMA, to prevent compiling subsystems or drivers that cannot work anyway. This simplifies the dependencies, and allows to improve compile-testing. Signed-off-by: Geert Uytterhoeven Reviewed-by: Mark Brown Acked-by: Robin Murphy Acked-by: Felipe Balbi [drivers/usb/gadget/] Acked-by: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/Kconfig | 4 ++-- drivers/usb/mtu3/Kconfig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 0875d38476ee..9c3b4f86965e 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -179,7 +179,7 @@ config USB_R8A66597 config USB_RENESAS_USBHS_UDC tristate 'Renesas USBHS controller' - depends on USB_RENESAS_USBHS && HAS_DMA + depends on USB_RENESAS_USBHS help Renesas USBHS is a discrete USB host and peripheral controller chip that supports both full and high speed USB 2.0 data transfers. @@ -192,7 +192,7 @@ config USB_RENESAS_USBHS_UDC config USB_RENESAS_USB3 tristate 'Renesas USB3.0 Peripheral controller' depends on ARCH_RENESAS || COMPILE_TEST - depends on EXTCON && HAS_DMA + depends on EXTCON help Renesas USB3.0 Peripheral controller is a USB peripheral controller that supports super, high, and full speed USB 3.0 data transfers. diff --git a/drivers/usb/mtu3/Kconfig b/drivers/usb/mtu3/Kconfig index 25cd61947bee..c0c0eb88e5ea 100644 --- a/drivers/usb/mtu3/Kconfig +++ b/drivers/usb/mtu3/Kconfig @@ -2,7 +2,7 @@ config USB_MTU3 tristate "MediaTek USB3 Dual Role controller" - depends on EXTCON && (USB || USB_GADGET) && HAS_DMA + depends on EXTCON && (USB || USB_GADGET) depends on ARCH_MEDIATEK || COMPILE_TEST select USB_XHCI_MTK if USB_SUPPORT && USB_XHCI_HCD help From c95c975898e34437dfe8df483835ec833a66d704 Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Wed, 11 Apr 2018 18:13:30 -0600 Subject: [PATCH 08/45] usbip: usbip_host: refine probe and disconnect debug msgs to be useful Refine probe and disconnect debug msgs to be useful and say what is in progress. Signed-off-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/stub_dev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c index dd8ef36ab10e..7813c1862941 100644 --- a/drivers/usb/usbip/stub_dev.c +++ b/drivers/usb/usbip/stub_dev.c @@ -302,7 +302,7 @@ static int stub_probe(struct usb_device *udev) struct bus_id_priv *busid_priv; int rc; - dev_dbg(&udev->dev, "Enter\n"); + dev_dbg(&udev->dev, "Enter probe\n"); /* check we should claim or not by busid_table */ busid_priv = get_busid_priv(udev_busid); @@ -404,7 +404,7 @@ static void stub_disconnect(struct usb_device *udev) struct bus_id_priv *busid_priv; int rc; - dev_dbg(&udev->dev, "Enter\n"); + dev_dbg(&udev->dev, "Enter disconnect\n"); busid_priv = get_busid_priv(udev_busid); if (!busid_priv) { From 60b9f942bc059913bcdac90d5b225f557438b5c5 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 18 Apr 2018 11:26:19 +0200 Subject: [PATCH 09/45] USB: phy: drop unused legacy controller-phy bind helper Drop the unused legacy usb_bind_phy() helper whose last user was removed in 2016 when OMAP moved to device-tree boot (9080b8dc761a ("ARM: OMAP2+: Remove legacy usb-host.c platform init code")). Note that this means that for the last couple of years the phy_bind_list has been empty (when using mainline kernels) and that consequently all phy lookups using the usb_get_phy_dev() interface have failed with -ENODEV. This helper along with its current users will be removed by follow-on patches. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy.c | 34 ---------------------------------- include/linux/usb/phy.h | 8 -------- 2 files changed, 42 deletions(-) diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index bceb2c9988dd..833547b00383 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -795,40 +795,6 @@ void usb_remove_phy(struct usb_phy *x) } EXPORT_SYMBOL_GPL(usb_remove_phy); -/** - * usb_bind_phy - bind the phy and the controller that uses the phy - * @dev_name: the device name of the device that will bind to the phy - * @index: index to specify the port number - * @phy_dev_name: the device name of the phy - * - * Fills the phy_bind structure with the dev_name and phy_dev_name. This will - * be used when the phy driver registers the phy and when the controller - * requests this phy. - * - * To be used by platform specific initialization code. - */ -int usb_bind_phy(const char *dev_name, u8 index, - const char *phy_dev_name) -{ - struct usb_phy_bind *phy_bind; - unsigned long flags; - - phy_bind = kzalloc(sizeof(*phy_bind), GFP_KERNEL); - if (!phy_bind) - return -ENOMEM; - - phy_bind->dev_name = dev_name; - phy_bind->phy_dev_name = phy_dev_name; - phy_bind->index = index; - - spin_lock_irqsave(&phy_lock, flags); - list_add_tail(&phy_bind->list, &phy_bind_list); - spin_unlock_irqrestore(&phy_lock, flags); - - return 0; -} -EXPORT_SYMBOL_GPL(usb_bind_phy); - /** * usb_phy_set_event - set event to phy event * @x: the phy returned by usb_get_phy(); diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index b7a2625947f5..ac5a079161e1 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -242,8 +242,6 @@ extern struct usb_phy *devm_usb_get_phy_by_node(struct device *dev, struct device_node *node, struct notifier_block *nb); extern void usb_put_phy(struct usb_phy *); extern void devm_usb_put_phy(struct device *dev, struct usb_phy *x); -extern int usb_bind_phy(const char *dev_name, u8 index, - const char *phy_dev_name); extern void usb_phy_set_event(struct usb_phy *x, unsigned long event); extern void usb_phy_set_charger_current(struct usb_phy *usb_phy, unsigned int mA); @@ -293,12 +291,6 @@ static inline void devm_usb_put_phy(struct device *dev, struct usb_phy *x) { } -static inline int usb_bind_phy(const char *dev_name, u8 index, - const char *phy_dev_name) -{ - return -EOPNOTSUPP; -} - static inline void usb_phy_set_event(struct usb_phy *x, unsigned long event) { } From bc40f53417410be18298c5b5dbf5bcae9588d84f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 18 Apr 2018 11:26:20 +0200 Subject: [PATCH 10/45] USB: core: hcd: drop support for legacy phys Drop support for looking up and initialising legacy phys in USB core, something which hasn't been used by a mainline kernel since commit 9080b8dc761a ("ARM: OMAP2+: Remove legacy usb-host.c platform init code"). Specifically, since that commit usb_get_phy_dev() have always returned -ENODEV and consequently this code has not been used. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 38 +++----------------------------------- include/linux/usb/hcd.h | 1 - 2 files changed, 3 insertions(+), 36 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 777036ae6367..6241d32c5ba7 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -33,7 +33,6 @@ #include #include #include -#include #include #include "usb.h" @@ -2739,30 +2738,10 @@ int usb_add_hcd(struct usb_hcd *hcd, int retval; struct usb_device *rhdev; - if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->skip_phy_initialization) { - struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0); - - if (IS_ERR(phy)) { - retval = PTR_ERR(phy); - if (retval == -EPROBE_DEFER) - return retval; - } else { - retval = usb_phy_init(phy); - if (retval) { - usb_put_phy(phy); - return retval; - } - hcd->usb_phy = phy; - hcd->remove_phy = 1; - } - } - if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) { hcd->phy_roothub = usb_phy_roothub_init(hcd->self.sysdev); - if (IS_ERR(hcd->phy_roothub)) { - retval = PTR_ERR(hcd->phy_roothub); - goto err_phy_roothub_init; - } + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); retval = usb_phy_roothub_power_on(hcd->phy_roothub); if (retval) @@ -2936,12 +2915,7 @@ err_create_buf: usb_phy_roothub_power_off(hcd->phy_roothub); err_usb_phy_roothub_power_on: usb_phy_roothub_exit(hcd->phy_roothub); -err_phy_roothub_init: - if (hcd->remove_phy && hcd->usb_phy) { - usb_phy_shutdown(hcd->usb_phy); - usb_put_phy(hcd->usb_phy); - hcd->usb_phy = NULL; - } + return retval; } EXPORT_SYMBOL_GPL(usb_add_hcd); @@ -3017,12 +2991,6 @@ void usb_remove_hcd(struct usb_hcd *hcd) usb_phy_roothub_power_off(hcd->phy_roothub); usb_phy_roothub_exit(hcd->phy_roothub); - if (hcd->remove_phy && hcd->usb_phy) { - usb_phy_shutdown(hcd->usb_phy); - usb_put_phy(hcd->usb_phy); - hcd->usb_phy = NULL; - } - usb_put_invalidate_rhdev(hcd); hcd->flags = 0; } diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index aef50cb2ed1b..e33009c77840 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -150,7 +150,6 @@ struct usb_hcd { unsigned rh_pollable:1; /* may we poll the root hub? */ unsigned msix_enabled:1; /* driver has MSI-X enabled? */ unsigned msi_enabled:1; /* driver has MSI enabled? */ - unsigned remove_phy:1; /* auto-remove USB phy */ /* * do not manage the PHY state in the HCD core, instead let the driver * handle this (for example if the PHY can only be turned on after a From e90308a65c34c3632883e0a826b5f70c57ae8baf Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 18 Apr 2018 11:26:21 +0200 Subject: [PATCH 11/45] USB: ehci-omap: drop unused legacy phy support Drop support for looking up legacy phys defined by board files, something which hasn't been used by a mainline kernel since commit 9080b8dc761a ("ARM: OMAP2+: Remove legacy usb-host.c platform init code"). Specifically, since that commit usb_get_phy_dev() have always returned -ENODEV and consequently this code has not been used. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-omap.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 8d8bafc70c1f..7e4c13346a1e 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -157,10 +157,7 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) struct usb_phy *phy; /* get the PHY device */ - if (dev->of_node) - phy = devm_usb_get_phy_by_phandle(dev, "phys", i); - else - phy = devm_usb_get_phy_dev(dev, i); + phy = devm_usb_get_phy_by_phandle(dev, "phys", i); if (IS_ERR(phy)) { /* Don't bail out if PHY is not absolutely necessary */ if (pdata->port_mode[i] != OMAP_EHCI_PORT_MODE_PHY) From 5ee1787e5efb9bbc11403740df4393587c47ab65 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 18 Apr 2018 11:26:22 +0200 Subject: [PATCH 12/45] USB: musb: omap2430: drop non-DT support Drop support for non-DT systems, which hasn't been used by a mainline kernel since commit 9080b8dc761a ("ARM: OMAP2+: Remove legacy usb-host.c platform init code"). Specifically, since that commit usb_get_phy_dev() have always returned -ENODEV when looking up a legacy phy, something which in turn would have led to the init callback returning -EPROBE_DEFER indefinitely. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/omap2430.c | 97 ++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index 5d705930ef47..3dd6e1c5e04f 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -239,21 +239,15 @@ static int omap2430_musb_init(struct musb *musb) * up through ULPI. TWL4030-family PMICs include one, * which needs a driver, drivers aren't always needed. */ - if (dev->parent->of_node) { - musb->phy = devm_phy_get(dev->parent, "usb2-phy"); + musb->phy = devm_phy_get(dev->parent, "usb2-phy"); - /* We can't totally remove musb->xceiv as of now because - * musb core uses xceiv.state and xceiv.otg. Once we have - * a separate state machine to handle otg, these can be moved - * out of xceiv and then we can start using the generic PHY - * framework - */ - musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, - "usb-phy", 0); - } else { - musb->xceiv = devm_usb_get_phy_dev(dev, 0); - musb->phy = devm_phy_get(dev, "usb"); - } + /* We can't totally remove musb->xceiv as of now because + * musb core uses xceiv.state and xceiv.otg. Once we have + * a separate state machine to handle otg, these can be moved + * out of xceiv and then we can start using the generic PHY + * framework + */ + musb->xceiv = devm_usb_get_phy_by_phandle(dev->parent, "usb-phy", 0); if (IS_ERR(musb->xceiv)) { status = PTR_ERR(musb->xceiv); @@ -391,8 +385,13 @@ static int omap2430_probe(struct platform_device *pdev) struct omap2430_glue *glue; struct device_node *np = pdev->dev.of_node; struct musb_hdrc_config *config; + struct device_node *control_node; + struct platform_device *control_pdev; int ret = -ENOMEM, val; + if (!np) + return -ENODEV; + glue = devm_kzalloc(&pdev->dev, sizeof(*glue), GFP_KERNEL); if (!glue) goto err0; @@ -412,47 +411,43 @@ static int omap2430_probe(struct platform_device *pdev) glue->status = MUSB_UNKNOWN; glue->control_otghs = ERR_PTR(-ENODEV); - if (np) { - struct device_node *control_node; - struct platform_device *control_pdev; + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + goto err2; - pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + goto err2; + + config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); + if (!config) + goto err2; + + of_property_read_u32(np, "mode", (u32 *)&pdata->mode); + of_property_read_u32(np, "interface-type", + (u32 *)&data->interface_type); + of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps); + of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits); + of_property_read_u32(np, "power", (u32 *)&pdata->power); + + ret = of_property_read_u32(np, "multipoint", &val); + if (!ret && val) + config->multipoint = true; + + pdata->board_data = data; + pdata->config = config; + + control_node = of_parse_phandle(np, "ctrl-module", 0); + if (control_node) { + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + ret = -EINVAL; goto err2; - - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) - goto err2; - - config = devm_kzalloc(&pdev->dev, sizeof(*config), GFP_KERNEL); - if (!config) - goto err2; - - of_property_read_u32(np, "mode", (u32 *)&pdata->mode); - of_property_read_u32(np, "interface-type", - (u32 *)&data->interface_type); - of_property_read_u32(np, "num-eps", (u32 *)&config->num_eps); - of_property_read_u32(np, "ram-bits", (u32 *)&config->ram_bits); - of_property_read_u32(np, "power", (u32 *)&pdata->power); - - ret = of_property_read_u32(np, "multipoint", &val); - if (!ret && val) - config->multipoint = true; - - pdata->board_data = data; - pdata->config = config; - - control_node = of_parse_phandle(np, "ctrl-module", 0); - if (control_node) { - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); - ret = -EINVAL; - goto err2; - } - glue->control_otghs = &control_pdev->dev; } + glue->control_otghs = &control_pdev->dev; } + pdata->platform_ops = &omap2430_ops; platform_set_drvdata(pdev, glue); From d8351953fbb6ed6d7834ac57d9d943d2177c2b20 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 18 Apr 2018 11:26:23 +0200 Subject: [PATCH 13/45] USB: renesas_usbhs: drop unused legacy-phy support Drop support for legacy phys for rcar2 which hasn't been used with a mainline kernel since commit 9080b8dc761a ("ARM: OMAP2+: Remove legacy usb-host.c platform init code"). Specifically, since that commit usb_get_phy_dev() have always returned -ENODEV and consequently this code has not been used. Note that the legacy-phy API is still being used in gadget mode to bind the peripheral controller. Acked-by: Yoshihiro Shimoda Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/common.h | 1 - drivers/usb/renesas_usbhs/rcar2.c | 29 ----------------------------- 2 files changed, 30 deletions(-) diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index f619afeae2b8..6137f7942c05 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -276,7 +276,6 @@ struct usbhs_priv { */ struct usbhs_fifo_info fifo_info; - struct usb_phy *usb_phy; struct phy *phy; }; diff --git a/drivers/usb/renesas_usbhs/rcar2.c b/drivers/usb/renesas_usbhs/rcar2.c index 85a0e0933917..0027092b1118 100644 --- a/drivers/usb/renesas_usbhs/rcar2.c +++ b/drivers/usb/renesas_usbhs/rcar2.c @@ -8,7 +8,6 @@ #include #include #include -#include #include "common.h" #include "rcar2.h" @@ -26,16 +25,6 @@ static int usbhs_rcar2_hardware_init(struct platform_device *pdev) return 0; } - if (IS_ENABLED(CONFIG_USB_PHY)) { - struct usb_phy *usb_phy = usb_get_phy_dev(&pdev->dev, 0); - - if (IS_ERR(usb_phy)) - return PTR_ERR(usb_phy); - - priv->usb_phy = usb_phy; - return 0; - } - return -ENXIO; } @@ -48,11 +37,6 @@ static int usbhs_rcar2_hardware_exit(struct platform_device *pdev) priv->phy = NULL; } - if (priv->usb_phy) { - usb_put_phy(priv->usb_phy); - priv->usb_phy = NULL; - } - return 0; } @@ -75,19 +59,6 @@ static int usbhs_rcar2_power_ctrl(struct platform_device *pdev, } } - if (priv->usb_phy) { - if (enable) { - retval = usb_phy_init(priv->usb_phy); - - if (!retval) - retval = usb_phy_set_suspend(priv->usb_phy, 0); - } else { - usb_phy_set_suspend(priv->usb_phy, 1); - usb_phy_shutdown(priv->usb_phy); - retval = 0; - } - } - return retval; } From c3c0ac70c77d34c03e9600170932b2c28478795f Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 18 Apr 2018 11:26:24 +0200 Subject: [PATCH 14/45] USB: phy: drop legacy board-file support The legacy interface for associating controllers with phys from board files and platform code has been unused since commit 9080b8dc761a ("ARM: OMAP2+: Remove legacy usb-host.c platform init code"). Since then, all calls to usb_get_phy_dev() and its devres version have been returning -ENODEV. Now that the final calls to these functions have been removed, we can drop this legacy lookup interface altogether. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy.c | 99 +---------------------------------------- include/linux/usb/phy.h | 28 ------------ 2 files changed, 2 insertions(+), 125 deletions(-) diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index 833547b00383..0277f62739a2 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -27,7 +27,6 @@ #define DEFAULT_ACA_CUR_MAX 5000 static LIST_HEAD(phy_list); -static LIST_HEAD(phy_bind_list); static DEFINE_SPINLOCK(phy_lock); struct phy_devm { @@ -50,24 +49,6 @@ static struct usb_phy *__usb_find_phy(struct list_head *list, return ERR_PTR(-ENODEV); } -static struct usb_phy *__usb_find_phy_dev(struct device *dev, - struct list_head *list, u8 index) -{ - struct usb_phy_bind *phy_bind = NULL; - - list_for_each_entry(phy_bind, list, list) { - if (!(strcmp(phy_bind->dev_name, dev_name(dev))) && - phy_bind->index == index) { - if (phy_bind->phy) - return phy_bind->phy; - else - return ERR_PTR(-EPROBE_DEFER); - } - } - - return ERR_PTR(-ENODEV); -} - static struct usb_phy *__of_usb_find_phy(struct device_node *node) { struct usb_phy *phy; @@ -584,72 +565,6 @@ struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, } EXPORT_SYMBOL_GPL(devm_usb_get_phy_by_phandle); -/** - * usb_get_phy_dev - find the USB PHY - * @dev - device that requests this phy - * @index - the index of the phy - * - * Returns the phy driver, after getting a refcount to it; or - * -ENODEV if there is no such phy. The caller is responsible for - * calling usb_put_phy() to release that count. - * - * For use by USB host and peripheral drivers. - */ -struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index) -{ - struct usb_phy *phy = NULL; - unsigned long flags; - - spin_lock_irqsave(&phy_lock, flags); - - phy = __usb_find_phy_dev(dev, &phy_bind_list, index); - if (IS_ERR(phy) || !try_module_get(phy->dev->driver->owner)) { - dev_dbg(dev, "unable to find transceiver\n"); - if (!IS_ERR(phy)) - phy = ERR_PTR(-ENODEV); - - goto err0; - } - - get_device(phy->dev); - -err0: - spin_unlock_irqrestore(&phy_lock, flags); - - return phy; -} -EXPORT_SYMBOL_GPL(usb_get_phy_dev); - -/** - * devm_usb_get_phy_dev - find the USB PHY using device ptr and index - * @dev - device that requests this phy - * @index - the index of the phy - * - * Gets the phy using usb_get_phy_dev(), and associates a device with it using - * devres. On driver detach, release function is invoked on the devres data, - * then, devres data is freed. - * - * For use by USB host and peripheral drivers. - */ -struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index) -{ - struct usb_phy **ptr, *phy; - - ptr = devres_alloc(devm_usb_phy_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return NULL; - - phy = usb_get_phy_dev(dev, index); - if (!IS_ERR(phy)) { - *ptr = phy; - devres_add(dev, ptr); - } else - devres_free(ptr); - - return phy; -} -EXPORT_SYMBOL_GPL(devm_usb_get_phy_dev); - /** * devm_usb_put_phy - release the USB PHY * @dev - device that wants to release this phy @@ -745,7 +660,6 @@ EXPORT_SYMBOL_GPL(usb_add_phy); */ int usb_add_phy_dev(struct usb_phy *x) { - struct usb_phy_bind *phy_bind; unsigned long flags; int ret; @@ -762,13 +676,9 @@ int usb_add_phy_dev(struct usb_phy *x) ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); spin_lock_irqsave(&phy_lock, flags); - list_for_each_entry(phy_bind, &phy_bind_list, list) - if (!(strcmp(phy_bind->phy_dev_name, dev_name(x->dev)))) - phy_bind->phy = x; - list_add_tail(&x->head, &phy_list); - spin_unlock_irqrestore(&phy_lock, flags); + return 0; } EXPORT_SYMBOL_GPL(usb_add_phy_dev); @@ -782,15 +692,10 @@ EXPORT_SYMBOL_GPL(usb_add_phy_dev); void usb_remove_phy(struct usb_phy *x) { unsigned long flags; - struct usb_phy_bind *phy_bind; spin_lock_irqsave(&phy_lock, flags); - if (x) { - list_for_each_entry(phy_bind, &phy_bind_list, list) - if (phy_bind->phy == x) - phy_bind->phy = NULL; + if (x) list_del(&x->head); - } spin_unlock_irqrestore(&phy_lock, flags); } EXPORT_SYMBOL_GPL(usb_remove_phy); diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index ac5a079161e1..e4de6bc1f69b 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -157,22 +157,6 @@ struct usb_phy { enum usb_charger_type (*charger_detect)(struct usb_phy *x); }; -/** - * struct usb_phy_bind - represent the binding for the phy - * @dev_name: the device name of the device that will bind to the phy - * @phy_dev_name: the device name of the phy - * @index: used if a single controller uses multiple phys - * @phy: reference to the phy - * @list: to maintain a linked list of the binding information - */ -struct usb_phy_bind { - const char *dev_name; - const char *phy_dev_name; - u8 index; - struct usb_phy *phy; - struct list_head list; -}; - /* for board-specific init logic */ extern int usb_add_phy(struct usb_phy *, enum usb_phy_type type); extern int usb_add_phy_dev(struct usb_phy *); @@ -234,8 +218,6 @@ usb_phy_vbus_off(struct usb_phy *x) extern struct usb_phy *usb_get_phy(enum usb_phy_type type); extern struct usb_phy *devm_usb_get_phy(struct device *dev, enum usb_phy_type type); -extern struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index); -extern struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index); extern struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, const char *phandle, u8 index); extern struct usb_phy *devm_usb_get_phy_by_node(struct device *dev, @@ -261,16 +243,6 @@ static inline struct usb_phy *devm_usb_get_phy(struct device *dev, return ERR_PTR(-ENXIO); } -static inline struct usb_phy *usb_get_phy_dev(struct device *dev, u8 index) -{ - return ERR_PTR(-ENXIO); -} - -static inline struct usb_phy *devm_usb_get_phy_dev(struct device *dev, u8 index) -{ - return ERR_PTR(-ENXIO); -} - static inline struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev, const char *phandle, u8 index) { From 8bffbdb6d829b9d69ffb21abb50e15e0c0938c77 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 19 Apr 2018 16:06:26 +0200 Subject: [PATCH 15/45] usb: phy: simplify getting .drvdata We should get drvdata from struct device directly. Going via platform_device is an unneeded step back and forth. Signed-off-by: Wolfram Sang Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-am335x.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/phy/phy-am335x.c b/drivers/usb/phy/phy-am335x.c index b36fa8b953d0..27bdb7222527 100644 --- a/drivers/usb/phy/phy-am335x.c +++ b/drivers/usb/phy/phy-am335x.c @@ -96,8 +96,7 @@ static int am335x_phy_remove(struct platform_device *pdev) #ifdef CONFIG_PM_SLEEP static int am335x_phy_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct am335x_phy *am_phy = platform_get_drvdata(pdev); + struct am335x_phy *am_phy = dev_get_drvdata(dev); /* * Enable phy wakeup only if dev->power.can_wakeup is true. @@ -117,8 +116,7 @@ static int am335x_phy_suspend(struct device *dev) static int am335x_phy_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct am335x_phy *am_phy = platform_get_drvdata(pdev); + struct am335x_phy *am_phy = dev_get_drvdata(dev); phy_ctrl_power(am_phy->phy_ctrl, am_phy->id, am_phy->dr_mode, true); From f86c6888ddee4dd3169011b984de850620fcdda1 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 19 Apr 2018 16:06:25 +0200 Subject: [PATCH 16/45] usb: mtu3: simplify getting .drvdata We should get drvdata from struct device directly. Going via platform_device is an unneeded step back and forth. Signed-off-by: Wolfram Sang Acked-by: Chunfeng Yun Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_plat.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 628d5ce356ca..46551f6d16fd 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -447,8 +447,7 @@ static int mtu3_remove(struct platform_device *pdev) */ static int __maybe_unused mtu3_suspend(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + struct ssusb_mtk *ssusb = dev_get_drvdata(dev); dev_dbg(dev, "%s\n", __func__); @@ -466,8 +465,7 @@ static int __maybe_unused mtu3_suspend(struct device *dev) static int __maybe_unused mtu3_resume(struct device *dev) { - struct platform_device *pdev = to_platform_device(dev); - struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + struct ssusb_mtk *ssusb = dev_get_drvdata(dev); int ret; dev_dbg(dev, "%s\n", __func__); From 5d111f5190848d6fb1c414dc57797efea3526a2f Mon Sep 17 00:00:00 2001 From: Dominik Bozek Date: Fri, 13 Apr 2018 10:42:31 -0700 Subject: [PATCH 17/45] usb: hub: Don't wait for connect state at resume for powered-off ports wait_for_connected() wait till a port change status to USB_PORT_STAT_CONNECTION, but this is not possible if the port is unpowered. The loop will only exit at timeout. Such case take place if an over-current incident happen while system is in S3. Then during resume wait_for_connected() will wait 2s, which may be noticeable by the user. Signed-off-by: Dominik Bozek Signed-off-by: Kuppuswamy Sathyanarayanan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index f6ea16e9f6bb..9e79bcf03cde 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3371,6 +3371,10 @@ static int wait_for_connected(struct usb_device *udev, while (delay_ms < 2000) { if (status || *portstatus & USB_PORT_STAT_CONNECTION) break; + if (!port_is_power_on(hub, *portstatus)) { + status = -ENODEV; + break; + } msleep(20); delay_ms += 20; status = hub_port_status(hub, *port1, portstatus, portchange); From 53fe0de9a35d7149e17dcdd03c55def617a937a7 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 16 Apr 2018 14:54:33 +0800 Subject: [PATCH 18/45] usb: typec: tcpm: pdo matching optimization This patch is a combination of commit 57e6f0d7b804 ("typec: tcpm: Only request matching pdos") and source pdo selection optimization based on it, instead of only compare between the same pdo type of sink and source, we should check source pdo voltage range is within the voltage range of one sink pdo. Reviewed-by: Hans de Goede Signed-off-by: Li Jun Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm.c | 109 +++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 40 deletions(-) diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 677d12138dbd..048b9532fd39 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -1772,39 +1772,63 @@ static int tcpm_pd_check_request(struct tcpm_port *port) return 0; } -static int tcpm_pd_select_pdo(struct tcpm_port *port) +#define min_power(x, y) min(pdo_max_power(x), pdo_max_power(y)) +#define min_current(x, y) min(pdo_max_current(x), pdo_max_current(y)) + +static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, + int *src_pdo) { - unsigned int i, max_mw = 0, max_mv = 0; + unsigned int i, j, max_src_mv = 0, min_src_mv = 0, max_mw = 0, + max_mv = 0, src_mw = 0, src_ma = 0, max_snk_mv = 0, + min_snk_mv = 0; int ret = -EINVAL; /* - * Select the source PDO providing the most power while staying within - * the board's voltage limits. Prefer PDO providing exp + * Select the source PDO providing the most power which has a + * matchig sink cap. */ for (i = 0; i < port->nr_source_caps; i++) { u32 pdo = port->source_caps[i]; enum pd_pdo_type type = pdo_type(pdo); - unsigned int mv, ma, mw; - if (type == PDO_TYPE_FIXED) - mv = pdo_fixed_voltage(pdo); - else - mv = pdo_min_voltage(pdo); - - if (type == PDO_TYPE_BATT) { - mw = pdo_max_power(pdo); + if (type == PDO_TYPE_FIXED) { + max_src_mv = pdo_fixed_voltage(pdo); + min_src_mv = max_src_mv; } else { - ma = min(pdo_max_current(pdo), - port->max_snk_ma); - mw = ma * mv / 1000; + max_src_mv = pdo_max_voltage(pdo); + min_src_mv = pdo_min_voltage(pdo); } - /* Perfer higher voltages if available */ - if ((mw > max_mw || (mw == max_mw && mv > max_mv)) && - mv <= port->max_snk_mv) { - ret = i; - max_mw = mw; - max_mv = mv; + if (type == PDO_TYPE_BATT) { + src_mw = pdo_max_power(pdo); + } else { + src_ma = pdo_max_current(pdo); + src_mw = src_ma * min_src_mv / 1000; + } + + for (j = 0; j < port->nr_snk_pdo; j++) { + pdo = port->snk_pdo[j]; + + if (pdo_type(pdo) == PDO_TYPE_FIXED) { + min_snk_mv = pdo_fixed_voltage(pdo); + max_snk_mv = pdo_fixed_voltage(pdo); + } else { + min_snk_mv = pdo_min_voltage(pdo); + max_snk_mv = pdo_max_voltage(pdo); + } + + if (max_src_mv <= max_snk_mv && + min_src_mv >= min_snk_mv) { + /* Prefer higher voltages if available */ + if ((src_mw == max_mw && min_src_mv > max_mv) || + src_mw > max_mw) { + *src_pdo = i; + *sink_pdo = j; + max_mw = src_mw; + max_mv = min_src_mv; + ret = 0; + } + } } } @@ -1816,13 +1840,16 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) unsigned int mv, ma, mw, flags; unsigned int max_ma, max_mw; enum pd_pdo_type type; - int index; - u32 pdo; + int src_pdo_index, snk_pdo_index; + u32 pdo, matching_snk_pdo; + int ret; - index = tcpm_pd_select_pdo(port); - if (index < 0) - return -EINVAL; - pdo = port->source_caps[index]; + ret = tcpm_pd_select_pdo(port, &snk_pdo_index, &src_pdo_index); + if (ret < 0) + return ret; + + pdo = port->source_caps[src_pdo_index]; + matching_snk_pdo = port->snk_pdo[snk_pdo_index]; type = pdo_type(pdo); if (type == PDO_TYPE_FIXED) @@ -1830,26 +1857,28 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) else mv = pdo_min_voltage(pdo); - /* Select maximum available current within the board's power limit */ + /* Select maximum available current within the sink pdo's limit */ if (type == PDO_TYPE_BATT) { - mw = pdo_max_power(pdo); - ma = 1000 * min(mw, port->max_snk_mw) / mv; + mw = min_power(pdo, matching_snk_pdo); + ma = 1000 * mw / mv; } else { - ma = min(pdo_max_current(pdo), - 1000 * port->max_snk_mw / mv); + ma = min_current(pdo, matching_snk_pdo); + mw = ma * mv / 1000; } - ma = min(ma, port->max_snk_ma); flags = RDO_USB_COMM | RDO_NO_SUSPEND; /* Set mismatch bit if offered power is less than operating power */ - mw = ma * mv / 1000; max_ma = ma; max_mw = mw; if (mw < port->operating_snk_mw) { flags |= RDO_CAP_MISMATCH; - max_mw = port->operating_snk_mw; - max_ma = max_mw * 1000 / mv; + if (type == PDO_TYPE_BATT && + (pdo_max_power(matching_snk_pdo) > pdo_max_power(pdo))) + max_mw = pdo_max_power(matching_snk_pdo); + else if (pdo_max_current(matching_snk_pdo) > + pdo_max_current(pdo)) + max_ma = pdo_max_current(matching_snk_pdo); } tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", @@ -1858,16 +1887,16 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) port->polarity); if (type == PDO_TYPE_BATT) { - *rdo = RDO_BATT(index + 1, mw, max_mw, flags); + *rdo = RDO_BATT(src_pdo_index + 1, mw, max_mw, flags); tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s", - index, mv, mw, + src_pdo_index, mv, mw, flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); } else { - *rdo = RDO_FIXED(index + 1, ma, max_ma, flags); + *rdo = RDO_FIXED(src_pdo_index + 1, ma, max_ma, flags); tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s", - index, mv, ma, + src_pdo_index, mv, ma, flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); } From b33d3180f427d2b76a754e4cfe8bfdc234fcad10 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 16 Apr 2018 14:54:34 +0800 Subject: [PATCH 19/45] usb: typec: fusb302: remove max_snk_* for sink config Since max_snk_* is to be deprecated, so remove max_snk_* by adding a variable PDO for sink config. Reviewed-by: Hans de Goede Signed-off-by: Li Jun Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/fusb302/fusb302.c | 42 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 703617129067..664463de7098 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -120,6 +120,7 @@ struct fusb302_chip { enum typec_cc_polarity cc_polarity; enum typec_cc_status cc1; enum typec_cc_status cc2; + u32 snk_pdo[PDO_MAX_OBJECTS]; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; @@ -1212,11 +1213,6 @@ static const u32 snk_pdo[] = { static const struct tcpc_config fusb302_tcpc_config = { .src_pdo = src_pdo, .nr_src_pdo = ARRAY_SIZE(src_pdo), - .snk_pdo = snk_pdo, - .nr_snk_pdo = ARRAY_SIZE(snk_pdo), - .max_snk_mv = 5000, - .max_snk_ma = 3000, - .max_snk_mw = 15000, .operating_snk_mw = 2500, .type = TYPEC_PORT_DRP, .data = TYPEC_PORT_DRD, @@ -1756,6 +1752,29 @@ static int init_gpio(struct fusb302_chip *chip) return 0; } +static int fusb302_composite_snk_pdo_array(struct fusb302_chip *chip) +{ + struct device *dev = chip->dev; + u32 max_uv, max_ua; + + chip->snk_pdo[0] = PDO_FIXED(5000, 400, PDO_FIXED_FLAGS); + + /* + * As max_snk_ma/mv/mw is not needed for tcpc_config, + * those settings should be passed in via sink PDO, so + * "fcs, max-sink-*" properties will be deprecated, to + * perserve compatibility with existing users of them, + * we read those properties to convert them to be a var + * PDO. + */ + if (device_property_read_u32(dev, "fcs,max-sink-microvolt", &max_uv) || + device_property_read_u32(dev, "fcs,max-sink-microamp", &max_ua)) + return 1; + + chip->snk_pdo[1] = PDO_VAR(5000, max_uv / 1000, max_ua / 1000); + return 2; +} + static int fusb302_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -1784,18 +1803,13 @@ static int fusb302_probe(struct i2c_client *client, chip->tcpc_dev.config = &chip->tcpc_config; mutex_init(&chip->lock); - if (!device_property_read_u32(dev, "fcs,max-sink-microvolt", &v)) - chip->tcpc_config.max_snk_mv = v / 1000; - - if (!device_property_read_u32(dev, "fcs,max-sink-microamp", &v)) - chip->tcpc_config.max_snk_ma = v / 1000; - - if (!device_property_read_u32(dev, "fcs,max-sink-microwatt", &v)) - chip->tcpc_config.max_snk_mw = v / 1000; - if (!device_property_read_u32(dev, "fcs,operating-sink-microwatt", &v)) chip->tcpc_config.operating_snk_mw = v / 1000; + /* Composite sink PDO */ + chip->tcpc_config.nr_snk_pdo = fusb302_composite_snk_pdo_array(chip); + chip->tcpc_config.snk_pdo = chip->snk_pdo; + /* * Devicetree platforms should get extcon via phandle (not yet * supported). On ACPI platforms, we get the name from a device prop. From 75b0220a63487bd7243626f23bb3bdad25887a9a Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 16 Apr 2018 14:54:35 +0800 Subject: [PATCH 20/45] dt-bindings: usb: fusb302: remove max-sink-* properties Remove max-sink-* properties since they are deprecated. Reviewed-by: Rob Herring Reviewed-by: Hans de Goede Signed-off-by: Li Jun Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/fcs,fusb302.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/fcs,fusb302.txt b/Documentation/devicetree/bindings/usb/fcs,fusb302.txt index 472facfa5a71..6087dc7f209e 100644 --- a/Documentation/devicetree/bindings/usb/fcs,fusb302.txt +++ b/Documentation/devicetree/bindings/usb/fcs,fusb302.txt @@ -6,12 +6,6 @@ Required properties : - interrupts : Interrupt specifier Optional properties : -- fcs,max-sink-microvolt : Maximum voltage to negotiate when configured as sink -- fcs,max-sink-microamp : Maximum current to negotiate when configured as sink -- fcs,max-sink-microwatt : Maximum power to negotiate when configured as sink - If this is less then max-sink-microvolt * - max-sink-microamp then the configured current will - be clamped. - fcs,operating-sink-microwatt : Minimum amount of power accepted from a sink when negotiating From c457a9c4fe78a443fcbd7701812e8f8382638689 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 16 Apr 2018 14:54:36 +0800 Subject: [PATCH 21/45] usb: typec: wcove: remove max_snk_* for sink config Since max_snk_* is to be deprecated, so remove max_snk_* by adding a variable PDO for sink config. Reviewed-by: Hans de Goede Signed-off-by: Li Jun Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/typec_wcove.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c index 19cca7f1b2c5..39cff11ec7a2 100644 --- a/drivers/usb/typec/typec_wcove.c +++ b/drivers/usb/typec/typec_wcove.c @@ -558,6 +558,7 @@ static const u32 src_pdo[] = { static const u32 snk_pdo[] = { PDO_FIXED(5000, 500, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM), + PDO_VAR(5000, 12000, 3000), }; static struct tcpc_config wcove_typec_config = { @@ -566,9 +567,6 @@ static struct tcpc_config wcove_typec_config = { .snk_pdo = snk_pdo, .nr_snk_pdo = ARRAY_SIZE(snk_pdo), - .max_snk_mv = 12000, - .max_snk_ma = 3000, - .max_snk_mw = 36000, .operating_snk_mw = 15000, .type = TYPEC_PORT_DRP, From e8374db15436648411d933b01d929acae0538775 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 16 Apr 2018 14:54:37 +0800 Subject: [PATCH 22/45] usb: typec: tcpm: remove max_snk_mv/ma/mw Since there is no user of max_snk_*, so we can remove them from tcpm. Reviewed-by: Hans de Goede Signed-off-by: Li Jun Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm.c | 12 ------------ include/linux/usb/tcpm.h | 9 --------- 2 files changed, 21 deletions(-) diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 048b9532fd39..27192083f8e6 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -257,9 +257,6 @@ struct tcpm_port { u32 snk_vdo[VDO_MAX_OBJECTS]; unsigned int nr_snk_vdo; - unsigned int max_snk_mv; - unsigned int max_snk_ma; - unsigned int max_snk_mw; unsigned int operating_snk_mw; /* Requested current / voltage */ @@ -3598,9 +3595,6 @@ EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities); int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo, - unsigned int max_snk_mv, - unsigned int max_snk_ma, - unsigned int max_snk_mw, unsigned int operating_snk_mw) { if (tcpm_validate_caps(port, pdo, nr_pdo)) @@ -3608,9 +3602,6 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, mutex_lock(&port->lock); port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo); - port->max_snk_mv = max_snk_mv; - port->max_snk_ma = max_snk_ma; - port->max_snk_mw = max_snk_mw; port->operating_snk_mw = operating_snk_mw; switch (port->state) { @@ -3676,9 +3667,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo, tcpc->config->nr_snk_vdo); - port->max_snk_mv = tcpc->config->max_snk_mv; - port->max_snk_ma = tcpc->config->max_snk_ma; - port->max_snk_mw = tcpc->config->max_snk_mw; port->operating_snk_mw = tcpc->config->operating_snk_mw; if (!tcpc->config->try_role_hw) port->try_role = tcpc->config->default_role; diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index f0d839daeaea..f5bda9a0f644 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -62,9 +62,6 @@ enum tcpm_transmit_type { * @snk_pdo: PDO parameters sent to partner as response to * PD_CTRL_GET_SINK_CAP message * @nr_snk_pdo: Number of entries in @snk_pdo - * @max_snk_mv: Maximum acceptable sink voltage in mV - * @max_snk_ma: Maximum sink current in mA - * @max_snk_mw: Maximum required sink power in mW * @operating_snk_mw: * Required operating sink power in mW * @type: Port type (TYPEC_PORT_DFP, TYPEC_PORT_UFP, or @@ -85,9 +82,6 @@ struct tcpc_config { const u32 *snk_vdo; unsigned int nr_snk_vdo; - unsigned int max_snk_mv; - unsigned int max_snk_ma; - unsigned int max_snk_mw; unsigned int operating_snk_mw; enum typec_port_type type; @@ -174,9 +168,6 @@ int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo); int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo, - unsigned int max_snk_mv, - unsigned int max_snk_ma, - unsigned int max_snk_mw, unsigned int operating_snk_mw); void tcpm_vbus_change(struct tcpm_port *port); From 3e29109f51a6b5662467358a8258ce67d2e915f7 Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Thu, 19 Apr 2018 15:30:52 +0200 Subject: [PATCH 23/45] usb: host: xhci-plat: Remove useless test before clk_disable_unprepare clk_disable_unprepare() already checks that the clock pointer is valid. No need to test it before calling it. Signed-off-by: Gregory CLEMENT Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-plat.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index df327dcc2bac..f0231fea524e 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -320,8 +320,7 @@ put_usb3_hcd: usb_put_hcd(xhci->shared_hcd); disable_clk: - if (!IS_ERR(clk)) - clk_disable_unprepare(clk); + clk_disable_unprepare(clk); put_hcd: usb_put_hcd(hcd); @@ -347,8 +346,7 @@ static int xhci_plat_remove(struct platform_device *dev) usb_remove_hcd(hcd); usb_put_hcd(xhci->shared_hcd); - if (!IS_ERR(clk)) - clk_disable_unprepare(clk); + clk_disable_unprepare(clk); usb_put_hcd(hcd); pm_runtime_set_suspended(&dev->dev); From 0fd2060ad42726de387e0e78b3d34a3b7e47a6e1 Mon Sep 17 00:00:00 2001 From: Gregory CLEMENT Date: Thu, 19 Apr 2018 15:30:53 +0200 Subject: [PATCH 24/45] usb: host: xhci-plat: Fix clock resource by adding a register clock On Armada 7K/8K we need to explicitly enable the register clock. This clock is optional because not all the SoCs using this IP need it but at least for Armada 7K/8K it is actually mandatory. The change was done at xhci-plat level and not at a xhci-mvebu.c because, it is expected that other SoC would have this kind of constraint. The binding documentation is updating accordingly. Signed-off-by: Gregory CLEMENT Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/usb-xhci.txt | 5 +++- drivers/usb/host/xhci-plat.c | 25 ++++++++++++++++--- drivers/usb/host/xhci.h | 3 ++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index c4c00dff4b56..bd1dd316fb23 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -28,7 +28,10 @@ Required properties: - interrupts: one XHCI interrupt should be described here. Optional properties: - - clocks: reference to a clock + - clocks: reference to the clocks + - clock-names: mandatory if there is a second clock, in this case + the name must be "core" for the first clock and "reg" for the + second one - usb2-lpm-disable: indicate if we don't want to enable USB2 HW LPM - usb3-lpm-capable: determines if platform is USB3 LPM capable - quirk-broken-port-ped: set if the controller has broken port disable mechanism diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index f0231fea524e..596e7a71b666 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -157,6 +157,7 @@ static int xhci_plat_probe(struct platform_device *pdev) struct resource *res; struct usb_hcd *hcd; struct clk *clk; + struct clk *reg_clk; int ret; int irq; @@ -226,17 +227,27 @@ static int xhci_plat_probe(struct platform_device *pdev) hcd->rsrc_len = resource_size(res); /* - * Not all platforms have a clk so it is not an error if the - * clock does not exists. + * Not all platforms have clks so it is not an error if the + * clock do not exist. */ + reg_clk = devm_clk_get(&pdev->dev, "reg"); + if (!IS_ERR(reg_clk)) { + ret = clk_prepare_enable(reg_clk); + if (ret) + goto put_hcd; + } else if (PTR_ERR(reg_clk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto put_hcd; + } + clk = devm_clk_get(&pdev->dev, NULL); if (!IS_ERR(clk)) { ret = clk_prepare_enable(clk); if (ret) - goto put_hcd; + goto disable_reg_clk; } else if (PTR_ERR(clk) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; - goto put_hcd; + goto disable_reg_clk; } xhci = hcd_to_xhci(hcd); @@ -252,6 +263,7 @@ static int xhci_plat_probe(struct platform_device *pdev) device_wakeup_enable(hcd->self.controller); xhci->clk = clk; + xhci->reg_clk = reg_clk; xhci->main_hcd = hcd; xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, dev_name(&pdev->dev), hcd); @@ -322,6 +334,9 @@ put_usb3_hcd: disable_clk: clk_disable_unprepare(clk); +disable_reg_clk: + clk_disable_unprepare(reg_clk); + put_hcd: usb_put_hcd(hcd); @@ -337,6 +352,7 @@ static int xhci_plat_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct clk *clk = xhci->clk; + struct clk *reg_clk = xhci->reg_clk; xhci->xhc_state |= XHCI_STATE_REMOVING; @@ -347,6 +363,7 @@ static int xhci_plat_remove(struct platform_device *dev) usb_put_hcd(xhci->shared_hcd); clk_disable_unprepare(clk); + clk_disable_unprepare(reg_clk); usb_put_hcd(hcd); pm_runtime_set_suspended(&dev->dev); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 05c909b04f14..6dfc4867dbcf 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1729,8 +1729,9 @@ struct xhci_hcd { int page_shift; /* msi-x vectors */ int msix_count; - /* optional clock */ + /* optional clocks */ struct clk *clk; + struct clk *reg_clk; /* data structures */ struct xhci_device_context_array *dcbaa; struct xhci_ring *cmd_ring; From ce08eaeb6388e78daf35a5ce2e72a19026def8a7 Mon Sep 17 00:00:00 2001 From: ShuFan Lee Date: Mon, 9 Apr 2018 10:11:34 +0800 Subject: [PATCH 25/45] staging: typec: rt1711h typec chip driver Richtek RT1711H Type-C chip driver that works with Type-C Port Controller Manager to provide USB PD and USB Type-C functionalities. Add definition of TCPC_CC_STATUS_TOGGLING. Signed-off-by: ShuFan Lee Reviewed-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/staging/typec/Kconfig | 8 + drivers/staging/typec/Makefile | 1 + drivers/staging/typec/tcpci.h | 1 + drivers/staging/typec/tcpci_rt1711h.c | 312 ++++++++++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 drivers/staging/typec/tcpci_rt1711h.c diff --git a/drivers/staging/typec/Kconfig b/drivers/staging/typec/Kconfig index 5359f556d203..3aa981fbc8f5 100644 --- a/drivers/staging/typec/Kconfig +++ b/drivers/staging/typec/Kconfig @@ -9,6 +9,14 @@ config TYPEC_TCPCI help Type-C Port Controller driver for TCPCI-compliant controller. +config TYPEC_RT1711H + tristate "Richtek RT1711H Type-C chip driver" + select TYPEC_TCPCI + help + Richtek RT1711H Type-C chip driver that works with + Type-C Port Controller Manager to provide USB PD and USB + Type-C functionalities. + endif endmenu diff --git a/drivers/staging/typec/Makefile b/drivers/staging/typec/Makefile index 53d649abcb53..7803d485e1b3 100644 --- a/drivers/staging/typec/Makefile +++ b/drivers/staging/typec/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o +obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o diff --git a/drivers/staging/typec/tcpci.h b/drivers/staging/typec/tcpci.h index 34c865f0dcf6..303ebde26546 100644 --- a/drivers/staging/typec/tcpci.h +++ b/drivers/staging/typec/tcpci.h @@ -59,6 +59,7 @@ #define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0) #define TCPC_CC_STATUS 0x1d +#define TCPC_CC_STATUS_TOGGLING BIT(5) #define TCPC_CC_STATUS_TERM BIT(4) #define TCPC_CC_STATUS_CC2_SHIFT 2 #define TCPC_CC_STATUS_CC2_MASK 0x3 diff --git a/drivers/staging/typec/tcpci_rt1711h.c b/drivers/staging/typec/tcpci_rt1711h.c new file mode 100644 index 000000000000..017389021b96 --- /dev/null +++ b/drivers/staging/typec/tcpci_rt1711h.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018, Richtek Technology Corporation + * + * Richtek RT1711H Type-C Chip Driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include "tcpci.h" + +#define RT1711H_VID 0x29CF +#define RT1711H_PID 0x1711 + +#define RT1711H_RTCTRL8 0x9B + +/* Autoidle timeout = (tout * 2 + 1) * 6.4ms */ +#define RT1711H_RTCTRL8_SET(ck300, ship_off, auto_idle, tout) \ + (((ck300) << 7) | ((ship_off) << 5) | \ + ((auto_idle) << 3) | ((tout) & 0x07)) + +#define RT1711H_RTCTRL11 0x9E + +/* I2C timeout = (tout + 1) * 12.5ms */ +#define RT1711H_RTCTRL11_SET(en, tout) \ + (((en) << 7) | ((tout) & 0x0F)) + +#define RT1711H_RTCTRL13 0xA0 +#define RT1711H_RTCTRL14 0xA1 +#define RT1711H_RTCTRL15 0xA2 +#define RT1711H_RTCTRL16 0xA3 + +struct rt1711h_chip { + struct tcpci_data data; + struct tcpci *tcpci; + struct device *dev; +}; + +static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); +} + +static int rt1711h_write16(struct rt1711h_chip *chip, unsigned int reg, u16 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); +} + +static int rt1711h_read8(struct rt1711h_chip *chip, unsigned int reg, u8 *val) +{ + return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); +} + +static int rt1711h_write8(struct rt1711h_chip *chip, unsigned int reg, u8 val) +{ + return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); +} + +static const struct regmap_config rt1711h_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xFF, /* 0x80 .. 0xFF are vendor defined */ +}; + +static struct rt1711h_chip *tdata_to_rt1711h(struct tcpci_data *tdata) +{ + return container_of(tdata, struct rt1711h_chip, data); +} + +static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata) +{ + int ret; + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + + /* CK 300K from 320K, shipping off, auto_idle enable, tout = 32ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL8, + RT1711H_RTCTRL8_SET(0, 1, 1, 2)); + if (ret < 0) + return ret; + + /* I2C reset : (val + 1) * 12.5ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL11, + RT1711H_RTCTRL11_SET(1, 0x0F)); + if (ret < 0) + return ret; + + /* tTCPCfilter : (26.7 * val) us */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL14, 0x0F); + if (ret < 0) + return ret; + + /* tDRP : (51.2 + 6.4 * val) ms */ + ret = rt1711h_write8(chip, RT1711H_RTCTRL15, 0x04); + if (ret < 0) + return ret; + + /* dcSRC.DRP : 33% */ + return rt1711h_write16(chip, RT1711H_RTCTRL16, 330); +} + +static int rt1711h_set_vconn(struct tcpci *tcpci, struct tcpci_data *tdata, + bool enable) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + + return rt1711h_write8(chip, RT1711H_RTCTRL8, + RT1711H_RTCTRL8_SET(0, 1, !enable, 2)); +} + +static int rt1711h_start_drp_toggling(struct tcpci *tcpci, + struct tcpci_data *tdata, + enum typec_cc_status cc) +{ + struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); + int ret; + unsigned int reg = 0; + + switch (cc) { + default: + case TYPEC_CC_RP_DEF: + reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_1_5: + reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_3_0: + reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + } + + if (cc == TYPEC_CC_RD) + reg |= (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); + else + reg |= (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT); + + ret = rt1711h_write8(chip, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + usleep_range(500, 1000); + + return 0; +} + +static irqreturn_t rt1711h_irq(int irq, void *dev_id) +{ + int ret; + u16 alert; + u8 status; + struct rt1711h_chip *chip = dev_id; + + if (!chip->tcpci) + return IRQ_HANDLED; + + ret = rt1711h_read16(chip, TCPC_ALERT, &alert); + if (ret < 0) + goto out; + + if (alert & TCPC_ALERT_CC_STATUS) { + ret = rt1711h_read8(chip, TCPC_CC_STATUS, &status); + if (ret < 0) + goto out; + /* Clear cc change event triggered by starting toggling */ + if (status & TCPC_CC_STATUS_TOGGLING) + rt1711h_write8(chip, TCPC_ALERT, TCPC_ALERT_CC_STATUS); + } + +out: + return tcpci_irq(chip->tcpci); +} + +static int rt1711h_init_alert(struct rt1711h_chip *chip, + struct i2c_client *client) +{ + int ret; + + /* Disable chip interrupts before requesting irq */ + ret = rt1711h_write16(chip, TCPC_ALERT_MASK, 0); + if (ret < 0) + return ret; + + ret = devm_request_threaded_irq(chip->dev, client->irq, NULL, + rt1711h_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + dev_name(chip->dev), chip); + if (ret < 0) + return ret; + enable_irq_wake(client->irq); + return 0; +} + +static int rt1711h_sw_reset(struct rt1711h_chip *chip) +{ + int ret; + + ret = rt1711h_write8(chip, RT1711H_RTCTRL13, 0x01); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + return 0; +} + +static int rt1711h_check_revision(struct i2c_client *i2c) +{ + int ret; + + ret = i2c_smbus_read_word_data(i2c, TCPC_VENDOR_ID); + if (ret < 0) + return ret; + if (ret != RT1711H_VID) { + dev_err(&i2c->dev, "vid is not correct, 0x%04x\n", ret); + return -ENODEV; + } + ret = i2c_smbus_read_word_data(i2c, TCPC_PRODUCT_ID); + if (ret < 0) + return ret; + if (ret != RT1711H_PID) { + dev_err(&i2c->dev, "pid is not correct, 0x%04x\n", ret); + return -ENODEV; + } + return 0; +} + +static int rt1711h_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + int ret; + struct rt1711h_chip *chip; + + ret = rt1711h_check_revision(client); + if (ret < 0) { + dev_err(&client->dev, "check vid/pid fail\n"); + return ret; + } + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->data.regmap = devm_regmap_init_i2c(client, + &rt1711h_regmap_config); + if (IS_ERR(chip->data.regmap)) + return PTR_ERR(chip->data.regmap); + + chip->dev = &client->dev; + i2c_set_clientdata(client, chip); + + ret = rt1711h_sw_reset(chip); + if (ret < 0) + return ret; + + ret = rt1711h_init_alert(chip, client); + if (ret < 0) + return ret; + + chip->data.init = rt1711h_init; + chip->data.set_vconn = rt1711h_set_vconn; + chip->data.start_drp_toggling = rt1711h_start_drp_toggling; + chip->tcpci = tcpci_register_port(chip->dev, &chip->data); + if (IS_ERR_OR_NULL(chip->tcpci)) + return PTR_ERR(chip->tcpci); + + return 0; +} + +static int rt1711h_remove(struct i2c_client *client) +{ + struct rt1711h_chip *chip = i2c_get_clientdata(client); + + tcpci_unregister_port(chip->tcpci); + return 0; +} + +static const struct i2c_device_id rt1711h_id[] = { + { "rt1711h", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, rt1711h_id); + +#ifdef CONFIG_OF +static const struct of_device_id rt1711h_of_match[] = { + { .compatible = "richtek,rt1711h", }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt1711h_of_match); +#endif + +static struct i2c_driver rt1711h_i2c_driver = { + .driver = { + .name = "rt1711h", + .of_match_table = of_match_ptr(rt1711h_of_match), + }, + .probe = rt1711h_probe, + .remove = rt1711h_remove, + .id_table = rt1711h_id, +}; +module_i2c_driver(rt1711h_i2c_driver); + +MODULE_AUTHOR("ShuFan Lee "); +MODULE_DESCRIPTION("RT1711H USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL"); From 32ce77d52eba678a272601a584b0f006d7df547c Mon Sep 17 00:00:00 2001 From: ShuFan Lee Date: Mon, 9 Apr 2018 10:11:35 +0800 Subject: [PATCH 26/45] dt-bindings: usb: rt1711h device tree binding document Add device tree binding document for Richtek RT1711H Type-C chip driver Signed-off-by: ShuFan Lee Reviewed-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/usb/richtek,rt1711h.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/richtek,rt1711h.txt diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt b/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt new file mode 100644 index 000000000000..09e847e92e5e --- /dev/null +++ b/Documentation/devicetree/bindings/usb/richtek,rt1711h.txt @@ -0,0 +1,17 @@ +Richtek RT1711H TypeC PD Controller. + +Required properties: + - compatible : Must be "richtek,rt1711h". + - reg : Must be 0x4e, it's slave address of RT1711H. + - interrupt-parent : the phandle for the interrupt controller that + provides interrupts for this device. + - interrupts : where a is the interrupt number and b represents an + encoding of the sense and level information for the interrupt. + +Example : +rt1711h@4e { + compatible = "richtek,rt1711h"; + reg = <0x4e>; + interrupt-parent = <&gpio26>; + interrupts = <0 IRQ_TYPE_LEVEL_LOW>; +}; From ffe95371d2a84f3ad8085656d4fcb2fc926ff7a1 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 19 Apr 2018 19:05:50 +0300 Subject: [PATCH 27/45] usb: define HCD_USB32 speed option for hosts that support USB 3.2 dual-lane Hosts that support USB 3.2 Enhaned SuperSpeed can set their hcd speed to HCD_USB32 to let usb core and host drivers know that the controller supports new USB 3.2 dual-lane features. make sure usb core handle HCD_USB32 hosts correctly, for now similar to HCD_USB32. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 3 +++ include/linux/usb/hcd.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 6241d32c5ba7..5a799f84dcc2 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -567,6 +567,7 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) switch (wValue & 0xff00) { case USB_DT_DEVICE << 8: switch (hcd->speed) { + case HCD_USB32: case HCD_USB31: bufp = usb31_rh_dev_descriptor; break; @@ -591,6 +592,7 @@ static int rh_call_control (struct usb_hcd *hcd, struct urb *urb) break; case USB_DT_CONFIG << 8: switch (hcd->speed) { + case HCD_USB32: case HCD_USB31: case HCD_USB3: bufp = ss_rh_config_descriptor; @@ -2804,6 +2806,7 @@ int usb_add_hcd(struct usb_hcd *hcd, case HCD_USB3: rhdev->speed = USB_SPEED_SUPER; break; + case HCD_USB32: case HCD_USB31: rhdev->speed = USB_SPEED_SUPER_PLUS; break; diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index e33009c77840..34a6ded6f319 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -260,6 +260,7 @@ struct hc_driver { #define HCD_USB25 0x0030 /* Wireless USB 1.0 (USB 2.5)*/ #define HCD_USB3 0x0040 /* USB 3.0 */ #define HCD_USB31 0x0050 /* USB 3.1 */ +#define HCD_USB32 0x0060 /* USB 3.2 */ #define HCD_MASK 0x0070 #define HCD_BH 0x0100 /* URB complete in BH context */ From 013eedb8c56e55549c45df19ca56a029cc804028 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 19 Apr 2018 19:05:51 +0300 Subject: [PATCH 28/45] USB: Add support to store lane count used by USB 3.2 USB 3.2 specification adds Dual-lane support, doubling the maximum SuperSpeedPlus data rate from 10Gbps to 20Gbps. Dual-lane takes into use a second set of rx and tx wires/pins in the Type-C cable and connector. Add "rx_lanes" and "tx_lanes" variables to struct usb_device to store the numer of lanes in use. Number of lanes can be read using the extended port status hub request that was introduced in USB 3.1. Extended port status rx and tx lane count are zero based, maximum lanes supported by non inter-chip (SSIC) USB 3.2 is 2 (dual lane) with rx and tx lane count symmetric. SSIC devices support asymmetric lanes up to 4 lanes per direction. If extended port status is not available then default to one lane. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 8 ++++++++ include/linux/usb.h | 4 ++++ include/uapi/linux/usb/ch11.h | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 9e79bcf03cde..e21cc3be18b7 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2746,6 +2746,14 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, if (!udev) return 0; + if (hub_is_superspeedplus(hub->hdev)) { + /* extended portstatus Rx and Tx lane count are zero based */ + udev->rx_lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1; + udev->tx_lanes = USB_EXT_PORT_TX_LANES(ext_portstatus) + 1; + } else { + udev->rx_lanes = 1; + udev->tx_lanes = 1; + } if (hub_is_wusb(hub)) udev->speed = USB_SPEED_WIRELESS; else if (hub_is_superspeedplus(hub->hdev) && diff --git a/include/linux/usb.h b/include/linux/usb.h index 0173597e59aa..beffceec4915 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -551,6 +551,8 @@ struct usb3_lpm_parameters { * @route: tree topology hex string for use with xHCI * @state: device state: configured, not attached, etc. * @speed: device speed: high/full/low (or error) + * @rx_lanes: number of rx lanes in use, USB 3.2 adds dual-lane support + * @tx_lanes: number of tx lanes in use, USB 3.2 adds dual-lane support * @tt: Transaction Translator info; used with low/full speed dev, highspeed hub * @ttport: device port on that tt hub * @toggle: one bit for each endpoint, with ([0] = IN, [1] = OUT) endpoints @@ -624,6 +626,8 @@ struct usb_device { u32 route; enum usb_device_state state; enum usb_device_speed speed; + unsigned int rx_lanes; + unsigned int tx_lanes; struct usb_tt *tt; int ttport; diff --git a/include/uapi/linux/usb/ch11.h b/include/uapi/linux/usb/ch11.h index 29c120c88747..fb0cd24c392c 100644 --- a/include/uapi/linux/usb/ch11.h +++ b/include/uapi/linux/usb/ch11.h @@ -197,6 +197,11 @@ struct usb_port_status { #define USB_EXT_PORT_STAT_RX_LANES 0x00000f00 #define USB_EXT_PORT_STAT_TX_LANES 0x0000f000 +#define USB_EXT_PORT_RX_LANES(p) \ + (((p) & USB_EXT_PORT_STAT_RX_LANES) >> 8) +#define USB_EXT_PORT_TX_LANES(p) \ + (((p) & USB_EXT_PORT_STAT_TX_LANES) >> 12) + /* * wHubCharacteristics (masks) * See USB 2.0 spec Table 11-13, offset 3 From a2d49572e11ef54f854f6b7db48846286b1676d5 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 19 Apr 2018 19:05:52 +0300 Subject: [PATCH 29/45] usb: set root hub lane counts Set the the rx_lane and tx_lane count to "2" for USB 3.2 hosts. For all other older hosts set the default lane counts to 1 Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 5a799f84dcc2..ac5bcf449d7d 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2793,6 +2793,9 @@ int usb_add_hcd(struct usb_hcd *hcd, hcd->self.root_hub = rhdev; mutex_unlock(&usb_port_peer_mutex); + rhdev->rx_lanes = 1; + rhdev->tx_lanes = 1; + switch (hcd->speed) { case HCD_USB11: rhdev->speed = USB_SPEED_FULL; @@ -2807,6 +2810,8 @@ int usb_add_hcd(struct usb_hcd *hcd, rhdev->speed = USB_SPEED_SUPER; break; case HCD_USB32: + rhdev->rx_lanes = 2; + rhdev->tx_lanes = 2; case HCD_USB31: rhdev->speed = USB_SPEED_SUPER_PLUS; break; From 45455e4d7a6b767497f282bbd0146e838862291f Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 19 Apr 2018 19:05:53 +0300 Subject: [PATCH 30/45] USB: show USB 3.2 Dual-lane devices as Gen Xx2 during device enumeration USB 3.2 specification adds a Gen XxY notion for USB3 devices where X is the signaling rate on the wire. Gen 1xY is 5Gbps Superspeed and Gen 2xY is 10Gbps SuperSpeedPlus. Y is the lane count. For normal, non inter-chip (SSIC) devies the rx and tx lane count is symmetric, and the maximum lane count for USB 3.2 devices is 2 (dual-lane). SSIC devices may have asymmetric lane counts, with up to four lanes per direction. The USB 3.2 specification doesn't point out how to use the Gen XxY notion for these devices, so we limit the Gen Xx2 notion to symmertic Dual lane devies. For other devices just show Gen1 or Gen2 Gen 1 5Gbps Gen 2 10Gbps Gen 1x2 10Gbps Dual-lane (USB 3.2) Gen 2x2 20Gbps Dual-lane (USB 3.2) Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index e21cc3be18b7..92378594a86e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4602,9 +4602,12 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, if (udev->speed >= USB_SPEED_SUPER) { devnum = udev->devnum; dev_info(&udev->dev, - "%s SuperSpeed%s USB device number %d using %s\n", + "%s SuperSpeed%s%s USB device number %d using %s\n", (udev->config) ? "reset" : "new", - (udev->speed == USB_SPEED_SUPER_PLUS) ? "Plus" : "", + (udev->speed == USB_SPEED_SUPER_PLUS) ? + "Plus Gen 2" : " Gen 1", + (udev->rx_lanes == 2 && udev->tx_lanes == 2) ? + "x2" : "", devnum, driver_name); } From 460fd21618bf95a16b30500eb4b5452dab04e023 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 19 Apr 2018 19:05:54 +0300 Subject: [PATCH 31/45] USB: USB 3.2 Add sysfs entries for a usb device rx_lanes and tx_lanes Add rx_lanes and tx_lanes lane count sysfs entries for a usb device struct usb_devuce rx_lanes and tx_lanes variables. Shows number of lanes used by the usb device Data rate of a device is the lane speed * lane count, for example USB 3.2 Gen 2x2 device uses 10Gbps signaling per lane, and has dual-lane support 10Gbps * 2 = 20Gbps Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/sysfs.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 27bb34043053..ea18284dfa9a 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -175,6 +175,26 @@ static ssize_t speed_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(speed); +static ssize_t rx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->rx_lanes); +} +static DEVICE_ATTR_RO(rx_lanes); + +static ssize_t tx_lanes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + return sprintf(buf, "%d\n", udev->tx_lanes); +} +static DEVICE_ATTR_RO(tx_lanes); + static ssize_t busnum_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -790,6 +810,8 @@ static struct attribute *dev_attrs[] = { &dev_attr_bNumConfigurations.attr, &dev_attr_bMaxPacketSize0.attr, &dev_attr_speed.attr, + &dev_attr_rx_lanes.attr, + &dev_attr_tx_lanes.attr, &dev_attr_busnum.attr, &dev_attr_devnum.attr, &dev_attr_devpath.attr, From b462e2e0d62a716f7a1b7a7ecea966edc3de45d7 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 19 Apr 2018 19:05:55 +0300 Subject: [PATCH 32/45] Documentation sysfs-bus-usb: Add rx_lanes and tx_lanes introduced in USB 3.2 rx_lanes and tx_lanes sysfs entries show the number of lanes in use by a device. USB 3.2 adds support for Dual-lane (symmetrical), using 2 rx lanes and 2 tx lanes for normal non Inter-Chip SSIC devices. USB 3.1 and older are all single lane. SSIC devices can have up to 4 lanes per direction in use, with different number of rx and tx lanes. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-usb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index c702c78f24d8..c6e9b30f05b1 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -236,3 +236,21 @@ Description: Supported values are 0 - 15. More information on how besl values map to microseconds can be found in USB 2.0 ECN Errata for Link Power Management, section 4.10) + +What: /sys/bus/usb/devices/.../rx_lanes +Date: March 2018 +Contact: Mathias Nyman +Description: + Number of rx lanes the device is using. + USB 3.2 adds Dual-lane support, 2 rx and 2 tx lanes over Type-C. + Inter-Chip SSIC devices support asymmetric lanes up to 4 lanes per + direction. Devices before USB 3.2 are single lane (rx_lanes = 1) + +What: /sys/bus/usb/devices/.../tx_lanes +Date: March 2018 +Contact: Mathias Nyman +Description: + Number of tx lanes the device is using. + USB 3.2 adds Dual-lane support, 2 rx and 2 tx -lanes over Type-C. + Inter-Chip SSIC devices support asymmetric lanes up to 4 lanes per + direction. Devices before USB 3.2 are single lane (tx_lanes = 1) From f59cd940051459f04eb5cbdf07cab27d628439cf Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 10 Apr 2018 01:02:57 +0300 Subject: [PATCH 33/45] usb: phy: tegra: Cleanup error messages Tegra's PHY driver has a mix of pr_err() and dev_err(), let's switch to dev_err() and use common errors message formatting across the driver for consistency. Signed-off-by: Dmitry Osipenko Signed-off-by: Greg Kroah-Hartman --- drivers/usb/phy/phy-tegra-usb.c | 69 ++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index 0e8d23e51732..e46219e7fa93 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -236,10 +236,14 @@ static void set_phcd(struct tegra_usb_phy *phy, bool enable) static int utmip_pad_open(struct tegra_usb_phy *phy) { + int err; + phy->pad_clk = devm_clk_get(phy->u_phy.dev, "utmi-pads"); if (IS_ERR(phy->pad_clk)) { - pr_err("%s: can't get utmip pad clock\n", __func__); - return PTR_ERR(phy->pad_clk); + err = PTR_ERR(phy->pad_clk); + dev_err(phy->u_phy.dev, + "Failed to get UTMIP pad clock: %d\n", err); + return err; } return 0; @@ -282,7 +286,7 @@ static int utmip_pad_power_off(struct tegra_usb_phy *phy) void __iomem *base = phy->pad_regs; if (!utmip_pad_count) { - pr_err("%s: utmip pad already powered off\n", __func__); + dev_err(phy->u_phy.dev, "UTMIP pad already powered off\n"); return -EINVAL; } @@ -338,7 +342,8 @@ static void utmi_phy_clk_disable(struct tegra_usb_phy *phy) set_phcd(phy, true); if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, 0) < 0) - pr_err("%s: timeout waiting for phy to stabilize\n", __func__); + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on disable\n"); } static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) @@ -370,7 +375,8 @@ static void utmi_phy_clk_enable(struct tegra_usb_phy *phy) if (utmi_wait_register(base + USB_SUSP_CTRL, USB_PHY_CLK_VALID, USB_PHY_CLK_VALID)) - pr_err("%s: timeout waiting for phy to stabilize\n", __func__); + dev_err(phy->u_phy.dev, + "Timeout waiting for PHY to stabilize on enable\n"); } static int utmi_phy_power_on(struct tegra_usb_phy *phy) @@ -617,15 +623,15 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) ret = gpio_direction_output(phy->reset_gpio, 0); if (ret < 0) { - dev_err(phy->u_phy.dev, "gpio %d not set to 0\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, "GPIO %d not set to 0: %d\n", + phy->reset_gpio, ret); return ret; } msleep(5); ret = gpio_direction_output(phy->reset_gpio, 1); if (ret < 0) { - dev_err(phy->u_phy.dev, "gpio %d not set to 1\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, "GPIO %d not set to 1: %d\n", + phy->reset_gpio, ret); return ret; } @@ -661,13 +667,13 @@ static int ulpi_phy_power_on(struct tegra_usb_phy *phy) /* Fix VbusInvalid due to floating VBUS */ ret = usb_phy_io_write(phy->ulpi, 0x40, 0x08); if (ret) { - pr_err("%s: ulpi write failed\n", __func__); + dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", ret); return ret; } ret = usb_phy_io_write(phy->ulpi, 0x80, 0x0B); if (ret) { - pr_err("%s: ulpi write failed\n", __func__); + dev_err(phy->u_phy.dev, "ULPI write failed: %d\n", ret); return ret; } @@ -728,28 +734,30 @@ static int ulpi_open(struct tegra_usb_phy *phy) phy->clk = devm_clk_get(phy->u_phy.dev, "ulpi-link"); if (IS_ERR(phy->clk)) { - pr_err("%s: can't get ulpi clock\n", __func__); - return PTR_ERR(phy->clk); + err = PTR_ERR(phy->clk); + dev_err(phy->u_phy.dev, "Failed to get ULPI clock: %d\n", err); + return err; } err = devm_gpio_request(phy->u_phy.dev, phy->reset_gpio, "ulpi_phy_reset_b"); if (err < 0) { - dev_err(phy->u_phy.dev, "request failed for gpio: %d\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, "Request failed for GPIO %d: %d\n", + phy->reset_gpio, err); return err; } err = gpio_direction_output(phy->reset_gpio, 0); if (err < 0) { - dev_err(phy->u_phy.dev, "gpio %d direction not set to output\n", - phy->reset_gpio); + dev_err(phy->u_phy.dev, + "GPIO %d direction not set to output: %d\n", + phy->reset_gpio, err); return err; } phy->ulpi = otg_ulpi_create(&ulpi_viewport_access_ops, 0); if (!phy->ulpi) { - dev_err(phy->u_phy.dev, "otg_ulpi_create returned NULL\n"); + dev_err(phy->u_phy.dev, "Failed to create ULPI OTG\n"); err = -ENOMEM; return err; } @@ -766,8 +774,10 @@ static int tegra_usb_phy_init(struct tegra_usb_phy *phy) phy->pll_u = devm_clk_get(phy->u_phy.dev, "pll_u"); if (IS_ERR(phy->pll_u)) { - pr_err("Can't get pll_u clock\n"); - return PTR_ERR(phy->pll_u); + err = PTR_ERR(phy->pll_u); + dev_err(phy->u_phy.dev, + "Failed to get pll_u clock: %d\n", err); + return err; } err = clk_prepare_enable(phy->pll_u); @@ -782,7 +792,8 @@ static int tegra_usb_phy_init(struct tegra_usb_phy *phy) } } if (!phy->freq) { - pr_err("invalid pll_u parent rate %ld\n", parent_rate); + dev_err(phy->u_phy.dev, "Invalid pll_u parent rate %ld\n", + parent_rate); err = -EINVAL; goto fail; } @@ -791,7 +802,7 @@ static int tegra_usb_phy_init(struct tegra_usb_phy *phy) err = regulator_enable(phy->vbus); if (err) { dev_err(phy->u_phy.dev, - "failed to enable usb vbus regulator: %d\n", + "Failed to enable USB VBUS regulator: %d\n", err); goto fail; } @@ -855,7 +866,8 @@ static int read_utmi_param(struct platform_device *pdev, const char *param, int err = of_property_read_u32(pdev->dev.of_node, param, &value); *dest = (u8)value; if (err < 0) - dev_err(&pdev->dev, "Failed to read USB UTMI parameter %s: %d\n", + dev_err(&pdev->dev, + "Failed to read USB UTMI parameter %s: %d\n", param, err); return err; } @@ -871,14 +883,14 @@ static int utmi_phy_probe(struct tegra_usb_phy *tegra_phy, res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!res) { - dev_err(&pdev->dev, "Failed to get UTMI Pad regs\n"); + dev_err(&pdev->dev, "Failed to get UTMI pad regs\n"); return -ENXIO; } tegra_phy->pad_regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!tegra_phy->pad_regs) { - dev_err(&pdev->dev, "Failed to remap UTMI Pad regs\n"); + dev_err(&pdev->dev, "Failed to remap UTMI pad regs\n"); return -ENOMEM; } @@ -1020,15 +1032,16 @@ static int tegra_usb_phy_probe(struct platform_device *pdev) tegra_phy->reset_gpio = of_get_named_gpio(np, "nvidia,phy-reset-gpio", 0); if (!gpio_is_valid(tegra_phy->reset_gpio)) { - dev_err(&pdev->dev, "invalid gpio: %d\n", - tegra_phy->reset_gpio); + dev_err(&pdev->dev, + "Invalid GPIO: %d\n", tegra_phy->reset_gpio); return tegra_phy->reset_gpio; } tegra_phy->config = NULL; break; default: - dev_err(&pdev->dev, "phy_type is invalid or unsupported\n"); + dev_err(&pdev->dev, "phy_type %u is invalid or unsupported\n", + phy_type); return -EINVAL; } From 143470368efd3f0fb4cbe81aa89f49de8048d8a9 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 10 Apr 2018 01:02:58 +0300 Subject: [PATCH 34/45] usb: tegra: Move utmi-pads reset from ehci-tegra to tegra-phy UTMI pads are shared by USB controllers and reset of UTMI pads is shared with the reset of USB1 controller. Currently reset of UTMI pads is done by the EHCI driver and ChipIdea UDC works because EHCI driver always happen to be probed first. Move reset controls from ehci-tegra to tegra-phy in order to resolve the problem. Signed-off-by: Dmitry Osipenko Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-tegra.c | 91 ++++++++++++++----------------- drivers/usb/phy/phy-tegra-usb.c | 79 +++++++++++++++++++++++++-- include/linux/usb/tegra_usb_phy.h | 2 + 3 files changed, 117 insertions(+), 55 deletions(-) diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index a6f4389f7e88..4d2cdec4cb78 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -36,7 +36,6 @@ #define DRV_NAME "tegra-ehci" static struct hc_driver __read_mostly tegra_ehci_hc_driver; -static bool usb1_reset_attempted; struct tegra_ehci_soc_config { bool has_hostpc; @@ -51,68 +50,55 @@ struct tegra_ehci_hcd { enum tegra_usb_phy_port_speed port_speed; }; -/* - * The 1st USB controller contains some UTMI pad registers that are global for - * all the controllers on the chip. Those registers are also cleared when - * reset is asserted to the 1st controller. This means that the 1st controller - * can only be reset when no other controlled has finished probing. So we'll - * reset the 1st controller before doing any other setup on any of the - * controllers, and then never again. - * - * Since this is a PHY issue, the Tegra PHY driver should probably be doing - * the resetting of the USB controllers. But to keep compatibility with old - * device trees that don't have reset phandles in the PHYs, do it here. - * Those old DTs will be vulnerable to total USB breakage if the 1st EHCI - * device isn't the first one to finish probing, so warn them. - */ static int tegra_reset_usb_controller(struct platform_device *pdev) { struct device_node *phy_np; struct usb_hcd *hcd = platform_get_drvdata(pdev); struct tegra_ehci_hcd *tegra = (struct tegra_ehci_hcd *)hcd_to_ehci(hcd)->priv; - bool has_utmi_pad_registers = false; + struct reset_control *rst; + int err; phy_np = of_parse_phandle(pdev->dev.of_node, "nvidia,phy", 0); if (!phy_np) return -ENOENT; - if (of_property_read_bool(phy_np, "nvidia,has-utmi-pad-registers")) - has_utmi_pad_registers = true; - - if (!usb1_reset_attempted) { - struct reset_control *usb1_reset; - - if (!has_utmi_pad_registers) - usb1_reset = of_reset_control_get(phy_np, "utmi-pads"); - else - usb1_reset = tegra->rst; - - if (IS_ERR(usb1_reset)) { - dev_warn(&pdev->dev, - "can't get utmi-pads reset from the PHY\n"); - dev_warn(&pdev->dev, - "continuing, but please update your DT\n"); - } else { - reset_control_assert(usb1_reset); - udelay(1); - reset_control_deassert(usb1_reset); - - if (!has_utmi_pad_registers) - reset_control_put(usb1_reset); - } - - usb1_reset_attempted = true; - } - - if (!has_utmi_pad_registers) { - reset_control_assert(tegra->rst); - udelay(1); - reset_control_deassert(tegra->rst); + /* + * The 1st USB controller contains some UTMI pad registers that are + * global for all the controllers on the chip. Those registers are + * also cleared when reset is asserted to the 1st controller. + */ + rst = of_reset_control_get_shared(phy_np, "utmi-pads"); + if (IS_ERR(rst)) { + dev_warn(&pdev->dev, + "can't get utmi-pads reset from the PHY\n"); + dev_warn(&pdev->dev, + "continuing, but please update your DT\n"); + } else { + /* + * PHY driver performs UTMI-pads reset in a case of + * non-legacy DT. + */ + reset_control_put(rst); } of_node_put(phy_np); + /* reset control is shared, hence initialize it first */ + err = reset_control_deassert(tegra->rst); + if (err) + return err; + + err = reset_control_assert(tegra->rst); + if (err) + return err; + + udelay(1); + + err = reset_control_deassert(tegra->rst); + if (err) + return err; + return 0; } @@ -440,7 +426,7 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto cleanup_hcd_create; } - tegra->rst = devm_reset_control_get(&pdev->dev, "usb"); + tegra->rst = devm_reset_control_get_shared(&pdev->dev, "usb"); if (IS_ERR(tegra->rst)) { dev_err(&pdev->dev, "Can't get ehci reset\n"); err = PTR_ERR(tegra->rst); @@ -452,8 +438,10 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto cleanup_hcd_create; err = tegra_reset_usb_controller(pdev); - if (err) + if (err) { + dev_err(&pdev->dev, "Failed to reset controller\n"); goto cleanup_clk_en; + } u_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "nvidia,phy", 0); if (IS_ERR(u_phy)) { @@ -538,6 +526,9 @@ static int tegra_ehci_remove(struct platform_device *pdev) usb_phy_shutdown(hcd->usb_phy); usb_remove_hcd(hcd); + reset_control_assert(tegra->rst); + udelay(1); + clk_disable_unprepare(tegra->clk); usb_put_hcd(hcd); diff --git a/drivers/usb/phy/phy-tegra-usb.c b/drivers/usb/phy/phy-tegra-usb.c index e46219e7fa93..ea7ef1dc0b42 100644 --- a/drivers/usb/phy/phy-tegra-usb.c +++ b/drivers/usb/phy/phy-tegra-usb.c @@ -236,17 +236,83 @@ static void set_phcd(struct tegra_usb_phy *phy, bool enable) static int utmip_pad_open(struct tegra_usb_phy *phy) { - int err; + int ret; phy->pad_clk = devm_clk_get(phy->u_phy.dev, "utmi-pads"); if (IS_ERR(phy->pad_clk)) { - err = PTR_ERR(phy->pad_clk); + ret = PTR_ERR(phy->pad_clk); dev_err(phy->u_phy.dev, - "Failed to get UTMIP pad clock: %d\n", err); - return err; + "Failed to get UTMIP pad clock: %d\n", ret); + return ret; } - return 0; + phy->pad_rst = devm_reset_control_get_optional_shared( + phy->u_phy.dev, "utmi-pads"); + if (IS_ERR(phy->pad_rst)) { + ret = PTR_ERR(phy->pad_rst); + dev_err(phy->u_phy.dev, + "Failed to get UTMI-pads reset: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(phy->pad_clk); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to enable UTMI-pads clock: %d\n", ret); + return ret; + } + + spin_lock(&utmip_pad_lock); + + ret = reset_control_deassert(phy->pad_rst); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to initialize UTMI-pads reset: %d\n", ret); + goto unlock; + } + + ret = reset_control_assert(phy->pad_rst); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to assert UTMI-pads reset: %d\n", ret); + goto unlock; + } + + udelay(1); + + ret = reset_control_deassert(phy->pad_rst); + if (ret) + dev_err(phy->u_phy.dev, + "Failed to deassert UTMI-pads reset: %d\n", ret); +unlock: + spin_unlock(&utmip_pad_lock); + + clk_disable_unprepare(phy->pad_clk); + + return ret; +} + +static int utmip_pad_close(struct tegra_usb_phy *phy) +{ + int ret; + + ret = clk_prepare_enable(phy->pad_clk); + if (ret) { + dev_err(phy->u_phy.dev, + "Failed to enable UTMI-pads clock: %d\n", ret); + return ret; + } + + ret = reset_control_assert(phy->pad_rst); + if (ret) + dev_err(phy->u_phy.dev, + "Failed to assert UTMI-pads reset: %d\n", ret); + + udelay(1); + + clk_disable_unprepare(phy->pad_clk); + + return ret; } static void utmip_pad_power_on(struct tegra_usb_phy *phy) @@ -700,6 +766,9 @@ static void tegra_usb_phy_close(struct tegra_usb_phy *phy) if (!IS_ERR(phy->vbus)) regulator_disable(phy->vbus); + if (!phy->is_ulpi_phy) + utmip_pad_close(phy); + clk_disable_unprepare(phy->pll_u); } diff --git a/include/linux/usb/tegra_usb_phy.h b/include/linux/usb/tegra_usb_phy.h index d641ea1660b7..0c5c3ea8b2d7 100644 --- a/include/linux/usb/tegra_usb_phy.h +++ b/include/linux/usb/tegra_usb_phy.h @@ -17,6 +17,7 @@ #define __TEGRA_USB_PHY_H #include +#include #include /* @@ -76,6 +77,7 @@ struct tegra_usb_phy { bool is_legacy_phy; bool is_ulpi_phy; int reset_gpio; + struct reset_control *pad_rst; }; void tegra_usb_phy_preresume(struct usb_phy *phy); From ee30ec0cba81cf0a9a3a823a38573ca491efdf57 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Tue, 10 Apr 2018 01:02:59 +0300 Subject: [PATCH 35/45] usb: phy: Add Kconfig entry for Tegra PHY driver Tegra's EHCI driver has a build dependency on Tegra's PHY driver and currently Tegra's PHY driver is built only when Tegra's EHCI driver is built. Add own Kconfig entry for the Tegra's PHY driver so that drivers other than ehci-tegra (like ChipIdea UDC) could work with ehci-tegra driver being disabled in kernels config by allowing user to manually select the PHY driver. Signed-off-by: Dmitry Osipenko Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 4 +--- drivers/usb/phy/Kconfig | 9 +++++++++ drivers/usb/phy/Makefile | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 5d958da8e1bc..9f0aeb068acb 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -234,9 +234,7 @@ config USB_EHCI_TEGRA tristate "NVIDIA Tegra HCD support" depends on ARCH_TEGRA select USB_EHCI_ROOT_HUB_TT - select USB_PHY - select USB_ULPI - select USB_ULPI_VIEWPORT + select USB_TEGRA_PHY help This driver enables support for the internal USB Host Controllers found in NVIDIA Tegra SoCs. The controllers are EHCI compliant. diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 0f8ab981d572..b9b0a44be679 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -159,6 +159,15 @@ config USB_MXS_PHY MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x. +config USB_TEGRA_PHY + tristate "NVIDIA Tegra USB PHY Driver" + depends on ARCH_TEGRA + select USB_PHY + select USB_ULPI + help + This driver provides PHY support for the USB controllers found + on NVIDIA Tegra SoC's. + config USB_ULPI bool "Generic ULPI Transceiver Driver" depends on ARM || ARM64 diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index 25e579fb92b8..df1d99010079 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -16,7 +16,7 @@ obj-$(CONFIG_AM335X_CONTROL_USB) += phy-am335x-control.o obj-$(CONFIG_AM335X_PHY_USB) += phy-am335x.o obj-$(CONFIG_OMAP_OTG) += phy-omap-otg.o obj-$(CONFIG_TWL6030_USB) += phy-twl6030-usb.o -obj-$(CONFIG_USB_EHCI_TEGRA) += phy-tegra-usb.o +obj-$(CONFIG_USB_TEGRA_PHY) += phy-tegra-usb.o obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio-vbus-usb.o obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o From 3f0989e4d366b5affc222f01d2b6273258171946 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Mon, 23 Apr 2018 07:14:53 -0500 Subject: [PATCH 36/45] usb: core: hcd: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Addresses-Coverity-ID: 1468266 ("Missing break in switch") Signed-off-by: Gustavo A. R. Silva Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index ac5bcf449d7d..f65c1f287ab3 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2812,6 +2812,7 @@ int usb_add_hcd(struct usb_hcd *hcd, case HCD_USB32: rhdev->rx_lanes = 2; rhdev->tx_lanes = 2; + /* fall through */ case HCD_USB31: rhdev->speed = USB_SPEED_SUPER_PLUS; break; From 6e01827ed93947895680fbdad68c072a0f4e2450 Mon Sep 17 00:00:00 2001 From: Maxim Moseychuk Date: Thu, 4 Jan 2018 21:43:03 +0300 Subject: [PATCH 37/45] usb: do not reset if a low-speed or full-speed device timed out Some low-speed and full-speed devices (for example, bluetooth) do not have time to initialize. For them, ETIMEDOUT is a valid error. We need to give them another try. Otherwise, they will never be initialized correctly and in dmesg will be messages "Bluetooth: hci0 command 0x1002 tx timeout" or similars. Fixes: 264904ccc33c ("usb: retry reset if a device times out") Cc: stable Signed-off-by: Maxim Moseychuk Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 92378594a86e..a86591772352 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4555,7 +4555,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, * reset. But only on the first attempt, * lest we get into a time out/reset loop */ - if (r == 0 || (r == -ETIMEDOUT && retries == 0)) + if (r == 0 || (r == -ETIMEDOUT && + retries == 0 && + udev->speed > USB_SPEED_FULL)) break; } udev->descriptor.bMaxPacketSize0 = From 7eced58dbf1056e6a25153ab2d5c068442e33446 Mon Sep 17 00:00:00 2001 From: Yossi Mansharoff Date: Tue, 17 Apr 2018 16:53:25 +0300 Subject: [PATCH 38/45] doc: usb: ci-hdrc-usb2: Add property "mux-controls" The chipidea usb controller may be connected, in some platforms, to an external mux to toggle between different usb ports for different roles (host and device). The mux-controller property, if set, binds the chipidea usb controller with a mux for this use. Signed-off-by: Yossi Mansharoff Reviewed-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt index 0e03344e2e8b..2e9318151df7 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt @@ -76,6 +76,10 @@ Optional properties: needs to make sure it does not send more than 90% maximum_periodic_data_per_frame. The use case is multiple transactions, but less frame rate. +- mux-controls: The mux control for toggling host/device output of this + controller. It's expected that a mux state of 0 indicates device mode and a + mux state of 1 indicates host mode. +- mux-control-names: Shall be "usb_switch" if mux-controls is specified. i.mx specific properties - fsl,usbmisc: phandler of non-core register device, with one @@ -102,4 +106,6 @@ Example: rx-burst-size-dword = <0x10>; extcon = <0>, <&usb_id>; phy-clkgate-delay-us = <400>; + mux-controls = <&usb_switch>; + mux-control-names = "usb_switch"; }; From 2eadc33f40d4c59dd0649f8b6958872d85ad05d7 Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Mon, 23 Apr 2018 15:10:56 +0100 Subject: [PATCH 39/45] typec: tcpm: Add core support for sink side PPS This commit adds code to handle requesting of PPS APDOs. Switching between standard PDOs and APDOs, and re-requesting an APDO to modify operating voltage/current will be triggered by an external call into TCPM. Signed-off-by: Adam Thomson Acked-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm.c | 569 ++++++++++++++++++++++++++++++++++++++- include/linux/usb/pd.h | 4 +- include/linux/usb/tcpm.h | 1 + 3 files changed, 558 insertions(+), 16 deletions(-) diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 27192083f8e6..b160da35605f 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -48,6 +48,7 @@ S(SNK_DISCOVERY_DEBOUNCE_DONE), \ S(SNK_WAIT_CAPABILITIES), \ S(SNK_NEGOTIATE_CAPABILITIES), \ + S(SNK_NEGOTIATE_PPS_CAPABILITIES), \ S(SNK_TRANSITION_SINK), \ S(SNK_TRANSITION_SINK_VBUS), \ S(SNK_READY), \ @@ -167,6 +168,16 @@ struct pd_mode_data { struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX]; }; +struct pd_pps_data { + u32 min_volt; + u32 max_volt; + u32 max_curr; + u32 out_volt; + u32 op_curr; + bool supported; + bool active; +}; + struct tcpm_port { struct device *dev; @@ -235,6 +246,7 @@ struct tcpm_port { struct completion swap_complete; int swap_status; + unsigned int negotiated_rev; unsigned int message_id; unsigned int caps_count; unsigned int hard_reset_count; @@ -258,6 +270,7 @@ struct tcpm_port { unsigned int nr_snk_vdo; unsigned int operating_snk_mw; + bool update_sink_caps; /* Requested current / voltage */ u32 current_limit; @@ -274,8 +287,13 @@ struct tcpm_port { /* VDO to retry if UFP responder replied busy */ u32 vdo_retry; - /* Alternate mode data */ + /* PPS */ + struct pd_pps_data pps_data; + struct completion pps_complete; + bool pps_pending; + int pps_status; + /* Alternate mode data */ struct pd_mode_data mode_data; struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX]; struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX]; @@ -493,6 +511,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port) pdo_max_voltage(pdo), pdo_max_power(pdo)); break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mA", + pdo_pps_apdo_min_voltage(pdo), + pdo_pps_apdo_max_voltage(pdo), + pdo_pps_apdo_max_current(pdo)); + else + strcpy(msg, "undefined APDO"); + break; default: strcpy(msg, "undefined"); break; @@ -790,11 +818,13 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port) msg.header = PD_HEADER_LE(PD_CTRL_REJECT, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, 0); } else { msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, port->nr_src_pdo); } @@ -815,11 +845,13 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) msg.header = PD_HEADER_LE(PD_CTRL_REJECT, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, 0); } else { msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, port->nr_snk_pdo); } @@ -1186,6 +1218,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) 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]); @@ -1257,6 +1290,8 @@ enum pdo_err { PDO_ERR_FIXED_NOT_SORTED, PDO_ERR_VARIABLE_BATT_NOT_SORTED, PDO_ERR_DUPE_PDO, + PDO_ERR_PPS_APDO_NOT_SORTED, + PDO_ERR_DUPE_PPS_APDO, }; static const char * const pdo_err_msg[] = { @@ -1272,6 +1307,10 @@ static const char * const pdo_err_msg[] = { " err: Variable/Battery supply pdos should be in increasing order of their minimum voltage", [PDO_ERR_DUPE_PDO] = " err: Variable/Batt supply pdos cannot have same min/max voltage", + [PDO_ERR_PPS_APDO_NOT_SORTED] = + " err: Programmable power supply apdos should be in increasing order of their maximum voltage", + [PDO_ERR_DUPE_PPS_APDO] = + " err: Programmable power supply apdos cannot have same min/max voltage and max current", }; static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, @@ -1321,6 +1360,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, pdo_min_voltage(pdo[i - 1]))) return PDO_ERR_DUPE_PDO; break; + /* + * The Programmable Power Supply APDOs, if present, + * shall be sent in Maximum Voltage order; + * lowest to highest. + */ + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS) + break; + + if (pdo_pps_apdo_max_current(pdo[i]) < + pdo_pps_apdo_max_current(pdo[i - 1])) + return PDO_ERR_PPS_APDO_NOT_SORTED; + else if (pdo_pps_apdo_min_voltage(pdo[i]) == + pdo_pps_apdo_min_voltage(pdo[i - 1]) && + pdo_pps_apdo_max_voltage(pdo[i]) == + pdo_pps_apdo_max_voltage(pdo[i - 1]) && + pdo_pps_apdo_max_current(pdo[i]) == + pdo_pps_apdo_max_current(pdo[i - 1])) + return PDO_ERR_DUPE_PPS_APDO; + break; default: tcpm_log_force(port, " Unknown pdo type"); } @@ -1346,11 +1405,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, /* * PD (data, control) command handling functions */ + +static int tcpm_pd_send_control(struct tcpm_port *port, + enum pd_ctrl_msg_type type); + static void tcpm_pd_data_request(struct tcpm_port *port, const struct pd_message *msg) { enum pd_data_msg_type type = pd_header_type_le(msg->header); unsigned int cnt = pd_header_cnt_le(msg->header); + unsigned int rev = pd_header_rev_le(msg->header); unsigned int i; switch (type) { @@ -1368,6 +1432,17 @@ static void tcpm_pd_data_request(struct tcpm_port *port, tcpm_validate_caps(port, port->source_caps, port->nr_source_caps); + /* + * Adjust revision in subsequent message headers, as required, + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't + * support Rev 1.0 so just do nothing in that scenario. + */ + if (rev == PD_REV10) + break; + + if (rev < PD_MAX_REV) + port->negotiated_rev = rev; + /* * This message may be received even if VBUS is not * present. This is quite unexpected; see USB PD @@ -1389,6 +1464,20 @@ static void tcpm_pd_data_request(struct tcpm_port *port, tcpm_queue_message(port, PD_MSG_CTRL_REJECT); break; } + + /* + * Adjust revision in subsequent message headers, as required, + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't + * support Rev 1.0 so just reject in that scenario. + */ + if (rev == PD_REV10) { + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + + if (rev < PD_MAX_REV) + port->negotiated_rev = rev; + port->sink_request = le32_to_cpu(msg->payload[0]); tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); break; @@ -1413,6 +1502,15 @@ static void tcpm_pd_data_request(struct tcpm_port *port, } } +static void tcpm_pps_complete(struct tcpm_port *port, int result) +{ + if (port->pps_pending) { + port->pps_status = result; + port->pps_pending = false; + complete(&port->pps_complete); + } +} + static void tcpm_pd_ctrl_request(struct tcpm_port *port, const struct pd_message *msg) { @@ -1489,6 +1587,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, next_state = SNK_WAIT_CAPABILITIES; tcpm_set_state(port, next_state, 0); break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + /* Revert data back from any requested PPS updates */ + port->pps_data.out_volt = port->supply_voltage; + port->pps_data.op_curr = port->current_limit; + port->pps_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, SNK_READY, 0); + break; case DR_SWAP_SEND: port->swap_status = (type == PD_CTRL_WAIT ? -EAGAIN : -EOPNOTSUPP); @@ -1511,6 +1617,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, case PD_CTRL_ACCEPT: switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: + port->pps_data.active = false; + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); + break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + port->pps_data.active = true; + port->supply_voltage = port->pps_data.out_volt; + port->current_limit = port->pps_data.op_curr; tcpm_set_state(port, SNK_TRANSITION_SINK, 0); break; case SOFT_RESET_SEND: @@ -1665,6 +1778,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port, memset(&msg, 0, sizeof(msg)); msg.header = PD_HEADER_LE(type, port->pwr_role, port->data_role, + port->negotiated_rev, port->message_id, 0); return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); @@ -1780,6 +1894,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, min_snk_mv = 0; int ret = -EINVAL; + port->pps_data.supported = false; + /* * Select the source PDO providing the most power which has a * matchig sink cap. @@ -1788,30 +1904,59 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, u32 pdo = port->source_caps[i]; enum pd_pdo_type type = pdo_type(pdo); - if (type == PDO_TYPE_FIXED) { + switch (type) { + case PDO_TYPE_FIXED: max_src_mv = pdo_fixed_voltage(pdo); min_src_mv = max_src_mv; - } else { + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: max_src_mv = pdo_max_voltage(pdo); min_src_mv = pdo_min_voltage(pdo); + break; + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) + port->pps_data.supported = true; + continue; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; } - if (type == PDO_TYPE_BATT) { - src_mw = pdo_max_power(pdo); - } else { + switch (type) { + case PDO_TYPE_FIXED: + case PDO_TYPE_VAR: src_ma = pdo_max_current(pdo); src_mw = src_ma * min_src_mv / 1000; + break; + case PDO_TYPE_BATT: + src_mw = pdo_max_power(pdo); + break; + case PDO_TYPE_APDO: + continue; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; } for (j = 0; j < port->nr_snk_pdo; j++) { pdo = port->snk_pdo[j]; - if (pdo_type(pdo) == PDO_TYPE_FIXED) { - min_snk_mv = pdo_fixed_voltage(pdo); + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: max_snk_mv = pdo_fixed_voltage(pdo); - } else { - min_snk_mv = pdo_min_voltage(pdo); + min_snk_mv = max_snk_mv; + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: max_snk_mv = pdo_max_voltage(pdo); + min_snk_mv = pdo_min_voltage(pdo); + break; + case PDO_TYPE_APDO: + continue; + default: + tcpm_log(port, "Invalid sink PDO type, ignoring"); + continue; } if (max_src_mv <= max_snk_mv && @@ -1832,6 +1977,103 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, return ret; } +#define min_pps_apdo_current(x, y) \ + min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y)) + +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port) +{ + unsigned int i, j, max_mw = 0, max_mv = 0; + unsigned int min_src_mv, max_src_mv, src_ma, src_mw; + unsigned int min_snk_mv, max_snk_mv, snk_ma; + u32 pdo; + unsigned int src_pdo = 0, snk_pdo = 0; + + /* + * Select the source PPS APDO providing the most power while staying + * within the board's limits. We skip the first PDO as this is always + * 5V 3A. + */ + for (i = 1; i < port->nr_source_caps; ++i) { + pdo = port->source_caps[i]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { + tcpm_log(port, "Not PPS APDO (source), ignoring"); + continue; + } + + min_src_mv = pdo_pps_apdo_min_voltage(pdo); + max_src_mv = pdo_pps_apdo_max_voltage(pdo); + src_ma = pdo_pps_apdo_max_current(pdo); + src_mw = (src_ma * max_src_mv) / 1000; + + /* + * Now search through the sink PDOs to find a matching + * PPS APDO. Again skip the first sink PDO as this will + * always be 5V 3A. + */ + for (j = i; j < port->nr_snk_pdo; j++) { + pdo = port->snk_pdo[j]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { + tcpm_log(port, + "Not PPS APDO (sink), ignoring"); + continue; + } + + min_snk_mv = + pdo_pps_apdo_min_voltage(pdo); + max_snk_mv = + pdo_pps_apdo_max_voltage(pdo); + snk_ma = + pdo_pps_apdo_max_current(pdo); + break; + default: + tcpm_log(port, + "Not APDO type (sink), ignoring"); + continue; + } + + if (max_src_mv <= max_snk_mv && + min_src_mv >= min_snk_mv) { + /* Prefer higher voltages if available */ + if ((src_mw == max_mw && + min_src_mv > max_mv) || + src_mw > max_mw) { + src_pdo = i; + snk_pdo = j; + max_mw = src_mw; + max_mv = max_src_mv; + } + } + } + + break; + default: + tcpm_log(port, "Not APDO type (source), ignoring"); + continue; + } + } + + if (src_pdo) { + pdo = port->source_caps[src_pdo]; + + port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo); + port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo); + port->pps_data.max_curr = + min_pps_apdo_current(pdo, port->snk_pdo[snk_pdo]); + port->pps_data.out_volt = + min(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt); + port->pps_data.op_curr = + min(port->pps_data.max_curr, port->pps_data.op_curr); + } + + return src_pdo; +} + static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) { unsigned int mv, ma, mw, flags; @@ -1849,10 +2091,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) matching_snk_pdo = port->snk_pdo[snk_pdo_index]; type = pdo_type(pdo); - if (type == PDO_TYPE_FIXED) + switch (type) { + case PDO_TYPE_FIXED: mv = pdo_fixed_voltage(pdo); - else + break; + case PDO_TYPE_BATT: + case PDO_TYPE_VAR: mv = pdo_min_voltage(pdo); + break; + default: + tcpm_log(port, "Invalid PDO selected!"); + return -EINVAL; + } /* Select maximum available current within the sink pdo's limit */ if (type == PDO_TYPE_BATT) { @@ -1917,6 +2167,105 @@ static int tcpm_pd_send_request(struct tcpm_port *port) msg.header = PD_HEADER_LE(PD_DATA_REQUEST, port->pwr_role, port->data_role, + port->negotiated_rev, + port->message_id, 1); + msg.payload[0] = cpu_to_le32(rdo); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo) +{ + unsigned int out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags; + enum pd_pdo_type type; + unsigned int src_pdo_index; + u32 pdo; + + src_pdo_index = tcpm_pd_select_pps_apdo(port); + if (!src_pdo_index) + return -EOPNOTSUPP; + + pdo = port->source_caps[src_pdo_index]; + type = pdo_type(pdo); + + switch (type) { + case PDO_TYPE_APDO: + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { + tcpm_log(port, "Invalid APDO selected!"); + return -EINVAL; + } + min_mv = port->pps_data.min_volt; + max_mv = port->pps_data.max_volt; + max_ma = port->pps_data.max_curr; + out_mv = port->pps_data.out_volt; + op_ma = port->pps_data.op_curr; + break; + default: + tcpm_log(port, "Invalid PDO selected!"); + return -EINVAL; + } + + flags = RDO_USB_COMM | RDO_NO_SUSPEND; + + op_mw = (op_ma * out_mv) / 1000; + if (op_mw < port->operating_snk_mw) { + /* + * Try raising current to meet power needs. If that's not enough + * then try upping the voltage. If that's still not enough + * then we've obviously chosen a PPS APDO which really isn't + * suitable so abandon ship. + */ + op_ma = (port->operating_snk_mw * 1000) / out_mv; + if ((port->operating_snk_mw * 1000) % out_mv) + ++op_ma; + op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP); + + if (op_ma > max_ma) { + op_ma = max_ma; + out_mv = (port->operating_snk_mw * 1000) / op_ma; + if ((port->operating_snk_mw * 1000) % op_ma) + ++out_mv; + out_mv += RDO_PROG_VOLT_MV_STEP - + (out_mv % RDO_PROG_VOLT_MV_STEP); + + if (out_mv > max_mv) { + tcpm_log(port, "Invalid PPS APDO selected!"); + return -EINVAL; + } + } + } + + tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", + port->cc_req, port->cc1, port->cc2, port->vbus_source, + port->vconn_role == TYPEC_SOURCE ? "source" : "sink", + port->polarity); + + *rdo = RDO_PROG(src_pdo_index + 1, out_mv, op_ma, flags); + + tcpm_log(port, "Requesting APDO %d: %u mV, %u mA", + src_pdo_index, out_mv, op_ma); + + port->pps_data.op_curr = op_ma; + port->pps_data.out_volt = out_mv; + + return 0; +} + +static int tcpm_pd_send_pps_request(struct tcpm_port *port) +{ + struct pd_message msg; + int ret; + u32 rdo; + + ret = tcpm_pd_build_pps_request(port, &rdo); + if (ret < 0) + return ret; + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_REQUEST, + port->pwr_role, + port->data_role, + port->negotiated_rev, port->message_id, 1); msg.payload[0] = cpu_to_le32(rdo); @@ -2103,6 +2452,7 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_typec_disconnect(port); port->attached = false; port->pd_capable = false; + port->pps_data.supported = false; /* * First Rx ID should be 0; set this to a sentinel of -1 so that @@ -2120,6 +2470,8 @@ static void tcpm_reset_port(struct tcpm_port *port) tcpm_set_attached_state(port, false); port->try_src_count = 0; port->try_snk_count = 0; + port->supply_voltage = 0; + port->current_limit = 0; } static void tcpm_detach(struct tcpm_port *port) @@ -2364,6 +2716,7 @@ 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->caps_count = 0; + port->negotiated_rev = PD_MAX_REV; port->message_id = 0; port->rx_msgid = -1; port->explicit_contract = false; @@ -2424,6 +2777,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, 0); tcpm_typec_connect(port); + tcpm_check_send_discover(port); /* * 6.3.5 @@ -2447,6 +2801,7 @@ static void run_state_machine(struct tcpm_port *port) case SNK_UNATTACHED: if (!port->non_pd_role_swap) tcpm_swap_complete(port, -ENOTCONN); + tcpm_pps_complete(port, -ENOTCONN); tcpm_snk_detach(port); if (tcpm_start_drp_toggling(port)) { tcpm_set_state(port, DRP_TOGGLING, 0); @@ -2536,6 +2891,7 @@ static void run_state_machine(struct tcpm_port *port) port->cc2 : port->cc1); typec_set_pwr_opmode(port->typec_port, opmode); port->pwr_opmode = TYPEC_PWR_MODE_USB; + port->negotiated_rev = PD_MAX_REV; port->message_id = 0; port->rx_msgid = -1; port->explicit_contract = false; @@ -2606,6 +2962,24 @@ static void run_state_machine(struct tcpm_port *port) PD_T_SENDER_RESPONSE); } break; + case SNK_NEGOTIATE_PPS_CAPABILITIES: + ret = tcpm_pd_send_pps_request(port); + if (ret < 0) { + port->pps_status = ret; + /* + * If this was called due to updates to sink + * capabilities, and pps is no longer valid, we should + * safely fall back to a standard PDO. + */ + if (port->update_sink_caps) + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + else + tcpm_set_state(port, SNK_READY, 0); + } else { + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + } + break; case SNK_TRANSITION_SINK: case SNK_TRANSITION_SINK_VBUS: tcpm_set_state(port, hard_reset_state(port), @@ -2613,6 +2987,7 @@ static void run_state_machine(struct tcpm_port *port) break; case SNK_READY: port->try_snk_count = 0; + port->update_sink_caps = false; if (port->explicit_contract) { typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); @@ -2622,6 +2997,8 @@ static void run_state_machine(struct tcpm_port *port) tcpm_swap_complete(port, 0); tcpm_typec_connect(port); tcpm_check_send_discover(port); + tcpm_pps_complete(port, port->pps_status); + break; /* Accessory states */ @@ -2668,6 +3045,7 @@ static void run_state_machine(struct tcpm_port *port) tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); break; case SNK_HARD_RESET_SINK_OFF: + memset(&port->pps_data, 0, sizeof(port->pps_data)); tcpm_set_vconn(port, false); tcpm_set_charge(port, false); tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE); @@ -2888,6 +3266,7 @@ static void run_state_machine(struct tcpm_port *port) break; case ERROR_RECOVERY: tcpm_swap_complete(port, -EPROTO); + tcpm_pps_complete(port, -EPROTO); tcpm_set_state(port, PORT_RESET, 0); break; case PORT_RESET: @@ -3470,6 +3849,162 @@ static int tcpm_try_role(const struct typec_capability *cap, int role) return ret; } +static int __maybe_unused tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) +{ + unsigned int target_mw; + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (!port->pps_data.active) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (op_curr > port->pps_data.max_curr) { + ret = -EINVAL; + goto port_unlock; + } + + target_mw = (op_curr * port->pps_data.out_volt) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + + reinit_completion(&port->pps_complete); + port->pps_data.op_curr = op_curr; + port->pps_status = 0; + port->pps_pending = true; + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->pps_complete, + msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->pps_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int __maybe_unused tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) +{ + unsigned int target_mw; + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (!port->pps_data.active) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (out_volt < port->pps_data.min_volt || + out_volt > port->pps_data.max_volt) { + ret = -EINVAL; + goto port_unlock; + } + + target_mw = (port->pps_data.op_curr * out_volt) / 1000; + if (target_mw < port->operating_snk_mw) { + ret = -EINVAL; + goto port_unlock; + } + + reinit_completion(&port->pps_complete); + port->pps_data.out_volt = out_volt; + port->pps_status = 0; + port->pps_pending = true; + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->pps_complete, + msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->pps_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + +static int __maybe_unused tcpm_pps_activate(struct tcpm_port *port, bool activate) +{ + int ret = 0; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (!port->pps_data.supported) { + ret = -EOPNOTSUPP; + goto port_unlock; + } + + /* Trying to deactivate PPS when already deactivated so just bail */ + if (!port->pps_data.active && !activate) + goto port_unlock; + + if (port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + reinit_completion(&port->pps_complete); + port->pps_status = 0; + port->pps_pending = true; + + /* Trigger PPS request or move back to standard PDO contract */ + if (activate) { + port->pps_data.out_volt = port->supply_voltage; + port->pps_data.op_curr = port->current_limit; + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + } else { + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + } + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->pps_complete, + msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->pps_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + + return ret; +} + static void tcpm_init(struct tcpm_port *port) { enum typec_cc_status cc1, cc2; @@ -3603,13 +4138,18 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, mutex_lock(&port->lock); port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo); port->operating_snk_mw = operating_snk_mw; + port->update_sink_caps = true; switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: + case SNK_NEGOTIATE_PPS_CAPABILITIES: case SNK_READY: case SNK_TRANSITION_SINK: case SNK_TRANSITION_SINK_VBUS: - tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + if (port->pps_data.active) + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); + else + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); break; default: break; @@ -3651,6 +4191,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) init_completion(&port->tx_complete); init_completion(&port->swap_complete); + init_completion(&port->pps_complete); tcpm_debugfs_init(port); if (tcpm_validate_caps(port, tcpc->config->src_pdo, @@ -3677,7 +4218,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) port->typec_caps.type = tcpc->config->type; port->typec_caps.data = tcpc->config->data; port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ - port->typec_caps.pd_revision = 0x0200; /* USB-PD spec release 2.0 */ + port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ port->typec_caps.dr_set = tcpm_dr_set; port->typec_caps.pr_set = tcpm_pr_set; port->typec_caps.vconn_set = tcpm_vconn_set; diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h index ff359bdfdc7b..09b570feb297 100644 --- a/include/linux/usb/pd.h +++ b/include/linux/usb/pd.h @@ -103,8 +103,8 @@ enum pd_ext_msg_type { (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) | \ ((ext_hdr) ? PD_HEADER_EXT_HDR : 0)) -#define PD_HEADER_LE(type, pwr, data, id, cnt) \ - cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (id), (cnt), (0))) +#define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) static inline unsigned int pd_header_cnt(u16 header) { diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h index f5bda9a0f644..b231b9314240 100644 --- a/include/linux/usb/tcpm.h +++ b/include/linux/usb/tcpm.h @@ -36,6 +36,7 @@ enum typec_cc_polarity { /* Time to wait for TCPC to complete transmit */ #define PD_T_TCPC_TX_TIMEOUT 100 /* in ms */ #define PD_ROLE_SWAP_TIMEOUT (MSEC_PER_SEC * 10) +#define PD_PPS_CTRL_TIMEOUT (MSEC_PER_SEC * 10) enum tcpm_transmit_status { TCPC_TX_SUCCESS = 0, From 91dabc54073324006d5eaba483679c47b6eb93a8 Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Mon, 23 Apr 2018 15:10:57 +0100 Subject: [PATCH 40/45] Documentation: power: Initial effort to document power_supply ABI This commit adds generic ABI information regarding power_supply properties. This is an initial attempt to try and align the usage of these properties between drivers. As part of this commit, common Battery and USB related properties have been listed. Signed-off-by: Adam Thomson Reviewed-by: Heikki Krogerus Reviewed-by: Sebastian Reichel Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-power | 443 ++++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 444 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index f85ce9e327b9..e046566e38cb 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -1,3 +1,446 @@ +===== General Properties ===== + +What: /sys/class/power_supply//manufacturer +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the name of the device manufacturer. + + Access: Read + Valid values: Represented as string + +What: /sys/class/power_supply//model_name +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the name of the device model. + + Access: Read + Valid values: Represented as string + +What: /sys/class/power_supply//serial_number +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the serial number of the device. + + Access: Read + Valid values: Represented as string + +What: /sys/class/power_supply//type +Date: May 2010 +Contact: linux-pm@vger.kernel.org +Description: + Describes the main type of the supply. + + Access: Read + Valid values: "Battery", "UPS", "Mains", "USB" + +===== Battery Properties ===== + +What: /sys/class/power_supply//capacity +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Fine grain representation of battery capacity. + Access: Read + Valid values: 0 - 100 (percent) + +What: /sys/class/power_supply//capacity_alert_max +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Maximum battery capacity trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery discharging scenario where user-space needs to know the + battery has dropped to an upper level so it can take + appropriate action (e.g. warning user that battery level is + low). + + Access: Read, Write + Valid values: 0 - 100 (percent) + +What: /sys/class/power_supply//capacity_alert_min +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Minimum battery capacity trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery discharging scenario where user-space needs to know the + battery has dropped to a lower level so it can take + appropriate action (e.g. warning user that battery level is + critically low). + + Access: Read, Write + Valid values: 0 - 100 (percent) + +What: /sys/class/power_supply//capacity_level +Date: June 2009 +Contact: linux-pm@vger.kernel.org +Description: + Coarse representation of battery capacity. + + Access: Read + Valid values: "Unknown", "Critical", "Low", "Normal", "High", + "Full" + +What: /sys/class/power_supply//current_avg +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an average IBAT current reading for the battery, over a + fixed period. Normally devices will provide a fixed interval in + which they average readings to smooth out the reported value. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply//current_max +Date: October 2010 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum IBAT current allowed into the battery. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply//current_now +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an instant, single IBAT current reading for the battery. + This value is not averaged/smoothed. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply//charge_type +Date: July 2009 +Contact: linux-pm@vger.kernel.org +Description: + Represents the type of charging currently being applied to the + battery. + + Access: Read + Valid values: "Unknown", "N/A", "Trickle", "Fast" + +What: /sys/class/power_supply//charge_term_current +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the charging current value which is used to determine + when the battery is considered full and charging should end. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply//health +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the health of the battery or battery side of charger + functionality. + + Access: Read + Valid values: "Unknown", "Good", "Overheat", "Dead", + "Over voltage", "Unspecified failure", "Cold", + "Watchdog timer expire", "Safety timer expire" + +What: /sys/class/power_supply//precharge_current +Date: June 2017 +Contact: linux-pm@vger.kernel.org +Description: + Reports the charging current applied during pre-charging phase + for a battery charge cycle. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply//present +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports whether a battery is present or not in the system. + + Access: Read + Valid values: + 0: Absent + 1: Present + +What: /sys/class/power_supply//status +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Represents the charging status of the battery. Normally this + is read-only reporting although for some supplies this can be + used to enable/disable charging to the battery. + + Access: Read, Write + Valid values: "Unknown", "Charging", "Discharging", + "Not charging", "Full" + +What: /sys/class/power_supply//technology +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Describes the battery technology supported by the supply. + + Access: Read + Valid values: "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", + "NiCd", "LiMn" + +What: /sys/class/power_supply//temp +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the current TBAT battery temperature reading. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_alert_max +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Maximum TBAT temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery charging scenario where user-space needs to know the + battery temperature has crossed an upper threshold so it can + take appropriate action (e.g. warning user that battery level is + critically high, and charging has stopped). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_alert_min +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Minimum TBAT temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + battery charging scenario where user-space needs to know the + battery temperature has crossed a lower threshold so it can take + appropriate action (e.g. warning user that battery level is + high, and charging current has been reduced accordingly to + remedy the situation). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_max +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum allowed TBAT battery temperature for + charging. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_min +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the minimum allowed TBAT battery temperature for + charging. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//voltage_avg, +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an average VBAT voltage reading for the battery, over a + fixed period. Normally devices will provide a fixed interval in + which they average readings to smooth out the reported value. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply//voltage_max, +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum safe VBAT voltage permitted for the battery, + during charging. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply//voltage_min, +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the minimum safe VBAT voltage permitted for the battery, + during discharging. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply//voltage_now, +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an instant, single VBAT voltage reading for the battery. + This value is not averaged/smoothed. + + Access: Read + Valid values: Represented in microvolts + +===== USB Properties ===== + +What: /sys/class/power_supply//current_avg +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports an average IBUS current reading over a fixed period. + Normally devices will provide a fixed interval in which they + average readings to smooth out the reported value. + + Access: Read + Valid values: Represented in microamps + + +What: /sys/class/power_supply//current_max +Date: October 2010 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum IBUS current the supply can support. + + Access: Read + Valid values: Represented in microamps + +What: /sys/class/power_supply//current_now +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the IBUS current supplied now. This value is generally + read-only reporting, unless the 'online' state of the supply + is set to be programmable, in which case this value can be set + within the reported min/max range. + + Access: Read, Write + Valid values: Represented in microamps + +What: /sys/class/power_supply//input_current_limit +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Details the incoming IBUS current limit currently set in the + supply. Normally this is configured based on the type of + connection made (e.g. A configured SDP should output a maximum + of 500mA so the input current limit is set to the same value). + + Access: Read, Write + Valid values: Represented in microamps + +What: /sys/class/power_supply//online, +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Indicates if VBUS is present for the supply. When the supply is + online, and the supply allows it, then it's possible to switch + between online states (e.g. Fixed -> Programmable for a PD_PPS + USB supply so voltage and current can be controlled). + + Access: Read, Write + Valid values: + 0: Offline + 1: Online Fixed - Fixed Voltage Supply + 2: Online Programmable - Programmable Voltage Supply + +What: /sys/class/power_supply//temp +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the current supply temperature reading. This would + normally be the internal temperature of the device itself (e.g + TJUNC temperature of an IC) + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_alert_max +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Maximum supply temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + charging scenario where user-space needs to know the supply + temperature has crossed an upper threshold so it can take + appropriate action (e.g. warning user that the supply + temperature is critically high, and charging has stopped to + remedy the situation). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_alert_min +Date: July 2012 +Contact: linux-pm@vger.kernel.org +Description: + Minimum supply temperature trip-wire value where the supply will + notify user-space of the event. This is normally used for the + charging scenario where user-space needs to know the supply + temperature has crossed a lower threshold so it can take + appropriate action (e.g. warning user that the supply + temperature is high, and charging current has been reduced + accordingly to remedy the situation). + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_max +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum allowed supply temperature for operation. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//temp_min +Date: July 2014 +Contact: linux-pm@vger.kernel.org +Description: + Reports the mainimum allowed supply temperature for operation. + + Access: Read + Valid values: Represented in 1/10 Degrees Celsius + +What: /sys/class/power_supply//voltage_max +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the maximum VBUS voltage the supply can support. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply//voltage_min +Date: January 2008 +Contact: linux-pm@vger.kernel.org +Description: + Reports the minimum VBUS voltage the supply can support. + + Access: Read + Valid values: Represented in microvolts + +What: /sys/class/power_supply//voltage_now +Date: May 2007 +Contact: linux-pm@vger.kernel.org +Description: + Reports the VBUS voltage supplied now. This value is generally + read-only reporting, unless the 'online' state of the supply + is set to be programmable, in which case this value can be set + within the reported min/max range. + + Access: Read, Write + Valid values: Represented in microvolts + +===== Device Specific Properties ===== + What: /sys/class/power/ds2760-battery.*/charge_now Date: May 2010 KernelVersion: 2.6.35 diff --git a/MAINTAINERS b/MAINTAINERS index 0a1410d5a621..700bfb7af2ee 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11242,6 +11242,7 @@ M: Sebastian Reichel L: linux-pm@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git S: Maintained +F: Documentation/ABI/testing/sysfs-class-power F: Documentation/devicetree/bindings/power/supply/ F: include/linux/power_supply.h F: drivers/power/supply/ From 1ac3eef74a61f40639d0fa835fd968f825b09135 Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Mon, 23 Apr 2018 15:10:58 +0100 Subject: [PATCH 41/45] power: supply: Add error checking of psy desc during registration Currently there's no error checking of this parameter in the registration function and it's blindly added to psy class and subsequently used as is. For example if this is NULL the call to psy_register_thermal() will try to dereference the pointer thus causing a kernel dump. This commit updates the registration code to add some basic checks on the desc pointer validity, name, and presence of properties. Signed-off-by: Adam Thomson Reviewed-by: Heikki Krogerus Reviewed-by: Sebastian Reichel Signed-off-by: Greg Kroah-Hartman --- drivers/power/supply/power_supply_core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index feac7b066e6c..a7984af1dc66 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -849,6 +849,9 @@ __power_supply_register(struct device *parent, pr_warn("%s: Expected proper parent device for '%s'\n", __func__, desc->name); + if (!desc || !desc->name || !desc->properties || !desc->num_properties) + return ERR_PTR(-EINVAL); + psy = kzalloc(sizeof(*psy), GFP_KERNEL); if (!psy) return ERR_PTR(-ENOMEM); From cf45004195efea6b479a1d710d6fc21c2b19353e Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Mon, 23 Apr 2018 15:10:59 +0100 Subject: [PATCH 42/45] power: supply: Add 'usb_type' property and supporting code This commit adds the 'usb_type' property to represent USB supplies which can report a number of different types based on a connection event. Examples of this already exist in drivers whereby the existing 'type' property is updated, based on an event, to represent what was connected (e.g. USB, USB_DCP, USB_ACA, ...). Current implementations however don't show all supported connectable types, so this knowledge has to be exlicitly known for each driver that supports this. The 'usb_type' property is intended to fill this void and show users all possible USB types supported by a driver. The property, when read, shows all available types for the driver, and the one currently chosen is highlighted/bracketed. It is expected that the 'type' property would then just show the top-level type 'USB', and this would be static. Currently the 'usb_type' enum contains all of the USB variant types that exist for the 'type' enum at this time, and in addition has SDP and PPS types. The mirroring is intentional so as to not impact existing usage of the 'type' property. Signed-off-by: Adam Thomson Reviewed-by: Heikki Krogerus Reviewed-by: Sebastian Reichel Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-power | 12 ++++++ drivers/power/supply/power_supply_core.c | 8 +++- drivers/power/supply/power_supply_sysfs.c | 45 +++++++++++++++++++++ include/linux/power_supply.h | 16 ++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index e046566e38cb..5e23e22dce1b 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -409,6 +409,18 @@ Description: Access: Read Valid values: Represented in 1/10 Degrees Celsius +What: /sys/class/power_supply//usb_type +Date: March 2018 +Contact: linux-pm@vger.kernel.org +Description: + Reports what type of USB connection is currently active for + the supply, for example it can show if USB-PD capable source + is attached. + + Access: Read-Only + Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD", + "PD_DRP", "PD_PPS", "BrickID" + What: /sys/class/power_supply//voltage_max Date: January 2008 Contact: linux-pm@vger.kernel.org diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index a7984af1dc66..ecd68c2053c5 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -843,7 +843,7 @@ __power_supply_register(struct device *parent, { struct device *dev; struct power_supply *psy; - int rc; + int i, rc; if (!parent) pr_warn("%s: Expected proper parent device for '%s'\n", @@ -852,6 +852,12 @@ __power_supply_register(struct device *parent, if (!desc || !desc->name || !desc->properties || !desc->num_properties) return ERR_PTR(-EINVAL); + for (i = 0; i < desc->num_properties; ++i) { + if ((desc->properties[i] == POWER_SUPPLY_PROP_USB_TYPE) && + (!desc->usb_types || !desc->num_usb_types)) + return ERR_PTR(-EINVAL); + } + psy = kzalloc(sizeof(*psy), GFP_KERNEL); if (!psy) return ERR_PTR(-ENOMEM); diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 5204f115970f..1350068c401a 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -46,6 +46,11 @@ static const char * const power_supply_type_text[] = { "USB_PD", "USB_PD_DRP", "BrickID" }; +static const char * const power_supply_usb_type_text[] = { + "Unknown", "SDP", "DCP", "CDP", "ACA", "C", + "PD", "PD_DRP", "PD_PPS", "BrickID" +}; + static const char * const power_supply_status_text[] = { "Unknown", "Charging", "Discharging", "Not charging", "Full" }; @@ -73,6 +78,41 @@ static const char * const power_supply_scope_text[] = { "Unknown", "System", "Device" }; +static ssize_t power_supply_show_usb_type(struct device *dev, + enum power_supply_usb_type *usb_types, + ssize_t num_usb_types, + union power_supply_propval *value, + char *buf) +{ + enum power_supply_usb_type usb_type; + ssize_t count = 0; + bool match = false; + int i; + + for (i = 0; i < num_usb_types; ++i) { + usb_type = usb_types[i]; + + if (value->intval == usb_type) { + count += sprintf(buf + count, "[%s] ", + power_supply_usb_type_text[usb_type]); + match = true; + } else { + count += sprintf(buf + count, "%s ", + power_supply_usb_type_text[usb_type]); + } + } + + if (!match) { + dev_warn(dev, "driver reporting unsupported connected type\n"); + return -EINVAL; + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} + static ssize_t power_supply_show_property(struct device *dev, struct device_attribute *attr, char *buf) { @@ -115,6 +155,10 @@ static ssize_t power_supply_show_property(struct device *dev, else if (off == POWER_SUPPLY_PROP_TYPE) return sprintf(buf, "%s\n", power_supply_type_text[value.intval]); + else if (off == POWER_SUPPLY_PROP_USB_TYPE) + return power_supply_show_usb_type(dev, psy->desc->usb_types, + psy->desc->num_usb_types, + &value, buf); else if (off == POWER_SUPPLY_PROP_SCOPE) return sprintf(buf, "%s\n", power_supply_scope_text[value.intval]); @@ -241,6 +285,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(time_to_full_now), POWER_SUPPLY_ATTR(time_to_full_avg), POWER_SUPPLY_ATTR(type), + POWER_SUPPLY_ATTR(usb_type), POWER_SUPPLY_ATTR(scope), POWER_SUPPLY_ATTR(precharge_current), POWER_SUPPLY_ATTR(charge_term_current), diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index f0139b460a72..0c9a572a1eb8 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -145,6 +145,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */ + POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, @@ -170,6 +171,19 @@ enum power_supply_type { POWER_SUPPLY_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */ }; +enum power_supply_usb_type { + POWER_SUPPLY_USB_TYPE_UNKNOWN = 0, + POWER_SUPPLY_USB_TYPE_SDP, /* Standard Downstream Port */ + POWER_SUPPLY_USB_TYPE_DCP, /* Dedicated Charging Port */ + POWER_SUPPLY_USB_TYPE_CDP, /* Charging Downstream Port */ + POWER_SUPPLY_USB_TYPE_ACA, /* Accessory Charger Adapters */ + POWER_SUPPLY_USB_TYPE_C, /* Type C Port */ + POWER_SUPPLY_USB_TYPE_PD, /* Power Delivery Port */ + POWER_SUPPLY_USB_TYPE_PD_DRP, /* PD Dual Role Port */ + POWER_SUPPLY_USB_TYPE_PD_PPS, /* PD Programmable Power Supply */ + POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */ +}; + enum power_supply_notifier_events { PSY_EVENT_PROP_CHANGED, }; @@ -196,6 +210,8 @@ struct power_supply_config { struct power_supply_desc { const char *name; enum power_supply_type type; + enum power_supply_usb_type *usb_types; + size_t num_usb_types; enum power_supply_property *properties; size_t num_properties; From f2a8aa053c1761232ce561e4fa725f02b8bd13fd Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Mon, 23 Apr 2018 15:11:00 +0100 Subject: [PATCH 43/45] typec: tcpm: Represent source supply through power_supply This commit adds a power_supply class instance to represent a PD source's voltage and current properties. This provides an interface for reading these properties from user-space or other drivers. For PPS enabled Sources, this also provides write access to set the current and voltage and allows for swapping between standard PDO and PPS APDO. As this represents a superset of the information provided in the fusb302 driver, the power_supply instance in that code is removed as part of this change, so reverting the commit titled 'typec: tcpm: Represent source supply through power_supply class' Signed-off-by: Adam Thomson Reviewed-by: Guenter Roeck Reviewed-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/Kconfig | 1 + drivers/usb/typec/fusb302/Kconfig | 2 +- drivers/usb/typec/fusb302/fusb302.c | 63 +------ drivers/usb/typec/tcpm.c | 251 +++++++++++++++++++++++++++- 4 files changed, 251 insertions(+), 66 deletions(-) diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 030f88cb0c3f..2c8eab11a493 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -49,6 +49,7 @@ config TYPEC_TCPM tristate "USB Type-C Port Controller Manager" depends on USB select USB_ROLE_SWITCH + select POWER_SUPPLY help The Type-C Port Controller Manager provides a USB PD and USB Type-C state machine for use with Type-C Port Controllers. diff --git a/drivers/usb/typec/fusb302/Kconfig b/drivers/usb/typec/fusb302/Kconfig index 48a4f2fcee03..fce099ff39fe 100644 --- a/drivers/usb/typec/fusb302/Kconfig +++ b/drivers/usb/typec/fusb302/Kconfig @@ -1,6 +1,6 @@ config TYPEC_FUSB302 tristate "Fairchild FUSB302 Type-C chip driver" - depends on I2C && POWER_SUPPLY + depends on I2C help The Fairchild FUSB302 Type-C chip driver that works with Type-C Port Controller Manager to provide USB PD and USB diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 664463de7098..eba6bb890b17 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -99,11 +98,6 @@ struct fusb302_chip { /* lock for sharing chip states */ struct mutex lock; - /* psy + psy status */ - struct power_supply *psy; - u32 current_limit; - u32 supply_voltage; - /* chip status */ enum toggling_mode toggling_mode; enum src_current_status src_current_status; @@ -862,13 +856,11 @@ static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) chip->vbus_on = on; fusb302_log(chip, "vbus := %s", on ? "On" : "Off"); } - if (chip->charge_on == charge) { + if (chip->charge_on == charge) fusb302_log(chip, "charge is already %s", charge ? "On" : "Off"); - } else { + else chip->charge_on = charge; - power_supply_changed(chip->psy); - } done: mutex_unlock(&chip->lock); @@ -884,11 +876,6 @@ static int tcpm_set_current_limit(struct tcpc_dev *dev, u32 max_ma, u32 mv) fusb302_log(chip, "current limit: %d ma, %d mv (not implemented)", max_ma, mv); - chip->supply_voltage = mv; - chip->current_limit = max_ma; - - power_supply_changed(chip->psy); - return 0; } @@ -1682,43 +1669,6 @@ done: return IRQ_HANDLED; } -static int fusb302_psy_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct fusb302_chip *chip = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = chip->charge_on; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = chip->supply_voltage * 1000; /* mV -> µV */ - break; - case POWER_SUPPLY_PROP_CURRENT_MAX: - val->intval = chip->current_limit * 1000; /* mA -> µA */ - break; - default: - return -ENODATA; - } - - return 0; -} - -static enum power_supply_property fusb302_psy_properties[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_CURRENT_MAX, -}; - -static const struct power_supply_desc fusb302_psy_desc = { - .name = "fusb302-typec-source", - .type = POWER_SUPPLY_TYPE_USB_TYPE_C, - .properties = fusb302_psy_properties, - .num_properties = ARRAY_SIZE(fusb302_psy_properties), - .get_property = fusb302_psy_get_property, -}; - static int init_gpio(struct fusb302_chip *chip) { struct device_node *node; @@ -1781,7 +1731,6 @@ static int fusb302_probe(struct i2c_client *client, struct fusb302_chip *chip; struct i2c_adapter *adapter; struct device *dev = &client->dev; - struct power_supply_config cfg = {}; const char *name; int ret = 0; u32 v; @@ -1823,14 +1772,6 @@ static int fusb302_probe(struct i2c_client *client, return -EPROBE_DEFER; } - cfg.drv_data = chip; - chip->psy = devm_power_supply_register(dev, &fusb302_psy_desc, &cfg); - if (IS_ERR(chip->psy)) { - ret = PTR_ERR(chip->psy); - dev_err(chip->dev, "Error registering power-supply: %d\n", ret); - return ret; - } - ret = fusb302_debugfs_init(chip); if (ret < 0) return ret; diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index b160da35605f..7547097f8751 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -276,6 +277,11 @@ struct tcpm_port { u32 current_limit; u32 supply_voltage; + /* Used to export TA voltage and current */ + struct power_supply *psy; + struct power_supply_desc psy_desc; + enum power_supply_usb_type usb_type; + u32 bist_request; /* PD state for Vendor Defined Messages */ @@ -1895,6 +1901,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, int ret = -EINVAL; port->pps_data.supported = false; + port->usb_type = POWER_SUPPLY_USB_TYPE_PD; /* * Select the source PDO providing the most power which has a @@ -1915,8 +1922,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, min_src_mv = pdo_min_voltage(pdo); break; case PDO_TYPE_APDO: - if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) { port->pps_data.supported = true; + port->usb_type = + POWER_SUPPLY_USB_TYPE_PD_PPS; + } continue; default: tcpm_log(port, "Invalid source PDO type, ignoring"); @@ -2472,6 +2482,9 @@ static void tcpm_reset_port(struct tcpm_port *port) port->try_snk_count = 0; port->supply_voltage = 0; port->current_limit = 0; + port->usb_type = POWER_SUPPLY_USB_TYPE_C; + + power_supply_changed(port->psy); } static void tcpm_detach(struct tcpm_port *port) @@ -2999,6 +3012,8 @@ static void run_state_machine(struct tcpm_port *port) tcpm_check_send_discover(port); tcpm_pps_complete(port, port->pps_status); + power_supply_changed(port->psy); + break; /* Accessory states */ @@ -3849,7 +3864,7 @@ static int tcpm_try_role(const struct typec_capability *cap, int role) return ret; } -static int __maybe_unused tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) +static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) { unsigned int target_mw; int ret; @@ -3901,7 +3916,7 @@ swap_unlock: return ret; } -static int __maybe_unused tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) +static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) { unsigned int target_mw; int ret; @@ -3954,7 +3969,7 @@ swap_unlock: return ret; } -static int __maybe_unused tcpm_pps_activate(struct tcpm_port *port, bool activate) +static int tcpm_pps_activate(struct tcpm_port *port, bool activate) { int ret = 0; @@ -4159,6 +4174,230 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, } EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities); +/* Power Supply access to expose source power information */ +enum tcpm_psy_online_states { + TCPM_PSY_OFFLINE = 0, + TCPM_PSY_FIXED_ONLINE, + TCPM_PSY_PROG_ONLINE, +}; + +static enum power_supply_property tcpm_psy_props[] = { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +static int tcpm_psy_get_online(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->vbus_charge) { + if (port->pps_data.active) + val->intval = TCPM_PSY_PROG_ONLINE; + else + val->intval = TCPM_PSY_FIXED_ONLINE; + } else { + val->intval = TCPM_PSY_OFFLINE; + } + + return 0; +} + +static int tcpm_psy_get_voltage_min(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.min_volt * 1000; + else + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_voltage_max(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.max_volt * 1000; + else + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_voltage_now(struct tcpm_port *port, + union power_supply_propval *val) +{ + val->intval = port->supply_voltage * 1000; + + return 0; +} + +static int tcpm_psy_get_current_max(struct tcpm_port *port, + union power_supply_propval *val) +{ + if (port->pps_data.active) + val->intval = port->pps_data.max_curr * 1000; + else + val->intval = port->current_limit * 1000; + + return 0; +} + +static int tcpm_psy_get_current_now(struct tcpm_port *port, + union power_supply_propval *val) +{ + val->intval = port->current_limit * 1000; + + return 0; +} + +static int tcpm_psy_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tcpm_port *port = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = port->usb_type; + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = tcpm_psy_get_online(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = tcpm_psy_get_voltage_min(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + ret = tcpm_psy_get_voltage_max(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = tcpm_psy_get_voltage_now(port, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + ret = tcpm_psy_get_current_max(port, val); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = tcpm_psy_get_current_now(port, val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_psy_set_online(struct tcpm_port *port, + const union power_supply_propval *val) +{ + int ret; + + switch (val->intval) { + case TCPM_PSY_FIXED_ONLINE: + ret = tcpm_pps_activate(port, false); + break; + case TCPM_PSY_PROG_ONLINE: + ret = tcpm_pps_activate(port, true); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_psy_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct tcpm_port *port = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = tcpm_psy_set_online(port, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (val->intval < port->pps_data.min_volt * 1000 || + val->intval > port->pps_data.max_volt * 1000) + ret = -EINVAL; + else + ret = tcpm_pps_set_out_volt(port, val->intval / 1000); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + if (val->intval > port->pps_data.max_curr * 1000) + ret = -EINVAL; + else + ret = tcpm_pps_set_op_curr(port, val->intval / 1000); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int tcpm_psy_prop_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + return 1; + default: + return 0; + } +} + +static enum power_supply_usb_type tcpm_psy_usb_types[] = { + POWER_SUPPLY_USB_TYPE_C, + POWER_SUPPLY_USB_TYPE_PD, + POWER_SUPPLY_USB_TYPE_PD_PPS, +}; + +static const char *tcpm_psy_name_prefix = "tcpm-source-psy-"; + +static int devm_tcpm_psy_register(struct tcpm_port *port) +{ + struct power_supply_config psy_cfg = {}; + const char *port_dev_name = dev_name(port->dev); + size_t psy_name_len = strlen(tcpm_psy_name_prefix) + + strlen(port_dev_name) + 1; + char *psy_name; + + psy_cfg.drv_data = port; + psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL); + if (!psy_name) + return -ENOMEM; + + snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix, + port_dev_name); + port->psy_desc.name = psy_name; + port->psy_desc.type = POWER_SUPPLY_TYPE_USB, + port->psy_desc.usb_types = tcpm_psy_usb_types; + port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_types); + port->psy_desc.properties = tcpm_psy_props, + port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props), + port->psy_desc.get_property = tcpm_psy_get_prop, + port->psy_desc.set_property = tcpm_psy_set_prop, + port->psy_desc.property_is_writeable = tcpm_psy_prop_writeable, + + port->usb_type = POWER_SUPPLY_USB_TYPE_C; + + port->psy = devm_power_supply_register(port->dev, &port->psy_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(port->psy); +} + struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) { struct tcpm_port *port; @@ -4234,6 +4473,10 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) goto out_destroy_wq; } + err = devm_tcpm_psy_register(port); + if (err) + goto out_destroy_wq; + port->typec_port = typec_register_port(port->dev, &port->typec_caps); if (IS_ERR(port->typec_port)) { err = PTR_ERR(port->typec_port); From 64f7c494a3c02be664e287297144a2e84f9c7625 Mon Sep 17 00:00:00 2001 From: Adam Thomson Date: Mon, 23 Apr 2018 15:11:01 +0100 Subject: [PATCH 44/45] typec: tcpm: Add support for sink PPS related messages This commit adds sink side support for Get_Status, Status, Get_PPS_Status and PPS_Status handling. As there's the potential for a partner to respond with Not_Supported, handling of this message is also added. Sending of Not_Supported is added to handle messagescreceived but not yet handled. Signed-off-by: Adam Thomson Acked-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm.c | 143 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 9 deletions(-) diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 7547097f8751..4483dc4162b0 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -19,7 +19,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -114,6 +116,11 @@ S(SNK_TRYWAIT_VBUS), \ S(BIST_RX), \ \ + S(GET_STATUS_SEND), \ + S(GET_STATUS_SEND_TIMEOUT), \ + S(GET_PPS_STATUS_SEND), \ + S(GET_PPS_STATUS_SEND_TIMEOUT), \ + \ S(ERROR_RECOVERY), \ S(PORT_RESET), \ S(PORT_RESET_WAIT_OFF) @@ -144,6 +151,7 @@ enum pd_msg_request { PD_MSG_NONE = 0, PD_MSG_CTRL_REJECT, PD_MSG_CTRL_WAIT, + PD_MSG_CTRL_NOT_SUPP, PD_MSG_DATA_SINK_CAP, PD_MSG_DATA_SOURCE_CAP, }; @@ -1411,10 +1419,42 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, /* * PD (data, control) command handling functions */ +static inline enum tcpm_state ready_state(struct tcpm_port *port) +{ + if (port->pwr_role == TYPEC_SOURCE) + return SRC_READY; + else + return SNK_READY; +} static int tcpm_pd_send_control(struct tcpm_port *port, enum pd_ctrl_msg_type type); +static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + u32 p0 = le32_to_cpu(payload[0]); + unsigned int type = usb_pd_ado_type(p0); + + if (!type) { + tcpm_log(port, "Alert message received with no type"); + return; + } + + /* Just handling non-battery alerts for now */ + if (!(type & USB_PD_ADO_TYPE_BATT_STATUS_CHANGE)) { + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, GET_STATUS_SEND, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + } +} + static void tcpm_pd_data_request(struct tcpm_port *port, const struct pd_message *msg) { @@ -1502,6 +1542,14 @@ static void tcpm_pd_data_request(struct tcpm_port *port, tcpm_set_state(port, BIST_RX, 0); } break; + case PD_DATA_ALERT: + tcpm_handle_alert(port, msg->payload, cnt); + break; + case PD_DATA_BATT_STATUS: + case PD_DATA_GET_COUNTRY_INFO: + /* Currently unsupported */ + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + break; default: tcpm_log(port, "Unhandled data message type %#x", type); break; @@ -1584,6 +1632,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, break; case PD_CTRL_REJECT: case PD_CTRL_WAIT: + case PD_CTRL_NOT_SUPP: switch (port->state) { case SNK_NEGOTIATE_CAPABILITIES: /* USB PD specification, Figure 8-43 */ @@ -1703,12 +1752,75 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, break; } break; + case PD_CTRL_GET_SOURCE_CAP_EXT: + case PD_CTRL_GET_STATUS: + case PD_CTRL_FR_SWAP: + case PD_CTRL_GET_PPS_STATUS: + case PD_CTRL_GET_COUNTRY_CODES: + /* Currently not supported */ + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + break; default: tcpm_log(port, "Unhandled ctrl message type %#x", type); break; } } +static void tcpm_pd_ext_msg_request(struct tcpm_port *port, + const struct pd_message *msg) +{ + enum pd_ext_msg_type type = pd_header_type_le(msg->header); + unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header); + + if (!(msg->ext_msg.header && PD_EXT_HDR_CHUNKED)) { + tcpm_log(port, "Unchunked extended messages unsupported"); + return; + } + + if (data_size > PD_EXT_MAX_CHUNK_DATA) { + tcpm_log(port, "Chunk handling not yet supported"); + return; + } + + switch (type) { + case PD_EXT_STATUS: + /* + * If PPS related events raised then get PPS status to clear + * (see USB PD 3.0 Spec, 6.5.2.4) + */ + if (msg->ext_msg.data[USB_PD_EXT_SDB_EVENT_FLAGS] & + USB_PD_EXT_SDB_PPS_EVENTS) + tcpm_set_state(port, GET_PPS_STATUS_SEND, 0); + else + tcpm_set_state(port, ready_state(port), 0); + break; + case PD_EXT_PPS_STATUS: + /* + * For now the PPS status message is used to clear events + * and nothing more. + */ + tcpm_set_state(port, ready_state(port), 0); + break; + case PD_EXT_SOURCE_CAP_EXT: + case PD_EXT_GET_BATT_CAP: + case PD_EXT_GET_BATT_STATUS: + case PD_EXT_BATT_CAP: + case PD_EXT_GET_MANUFACTURER_INFO: + case PD_EXT_MANUFACTURER_INFO: + case PD_EXT_SECURITY_REQUEST: + case PD_EXT_SECURITY_RESPONSE: + case PD_EXT_FW_UPDATE_REQUEST: + case PD_EXT_FW_UPDATE_RESPONSE: + case PD_EXT_COUNTRY_INFO: + case PD_EXT_COUNTRY_CODES: + tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP); + break; + default: + tcpm_log(port, "Unhandled extended message type %#x", type); + break; + } +} + static void tcpm_pd_rx_handler(struct work_struct *work) { struct pd_rx_event *event = container_of(work, @@ -1749,7 +1861,9 @@ static void tcpm_pd_rx_handler(struct work_struct *work) "Data role mismatch, initiating error recovery"); tcpm_set_state(port, ERROR_RECOVERY, 0); } else { - if (cnt) + if (msg->header & PD_HEADER_EXT_HDR) + tcpm_pd_ext_msg_request(port, msg); + else if (cnt) tcpm_pd_data_request(port, msg); else tcpm_pd_ctrl_request(port, msg); @@ -1810,6 +1924,9 @@ static bool tcpm_send_queued_message(struct tcpm_port *port) case PD_MSG_CTRL_REJECT: tcpm_pd_send_control(port, PD_CTRL_REJECT); break; + case PD_MSG_CTRL_NOT_SUPP: + tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP); + break; case PD_MSG_DATA_SINK_CAP: tcpm_pd_send_sink_caps(port); break; @@ -2572,14 +2689,6 @@ static inline enum tcpm_state hard_reset_state(struct tcpm_port *port) return SNK_UNATTACHED; } -static inline enum tcpm_state ready_state(struct tcpm_port *port) -{ - if (port->pwr_role == TYPEC_SOURCE) - return SRC_READY; - else - return SNK_READY; -} - static inline enum tcpm_state unattached_state(struct tcpm_port *port) { if (port->port_type == TYPEC_PORT_DRP) { @@ -3279,6 +3388,22 @@ static void run_state_machine(struct tcpm_port *port) /* Always switch to unattached state */ tcpm_set_state(port, unattached_state(port), 0); break; + case GET_STATUS_SEND: + tcpm_pd_send_control(port, PD_CTRL_GET_STATUS); + tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case GET_STATUS_SEND_TIMEOUT: + 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_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case GET_PPS_STATUS_SEND_TIMEOUT: + tcpm_set_state(port, ready_state(port), 0); + break; case ERROR_RECOVERY: tcpm_swap_complete(port, -EPROTO); tcpm_pps_complete(port, -EPROTO); From 0c718676ab3f02e98e85e7049e03d2879c62cb10 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 25 Apr 2018 14:24:34 +0200 Subject: [PATCH 45/45] typec: tcpm: fix compiler warning about stupid things gcc thinks it is too smart and gives off a "you might be using this variable before it is initialized" warning in tcpm_pd_build_request(), because it can not follow the logic through the tcpm_pd_select_pdo() call. So just make gcc quiet by initializing things to 0, to prevent the myriad of people complaining that we now have a build warning. Cc: Adam Thomson Cc: Heikki Krogerus Cc: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c index 4483dc4162b0..1ee259bc14a5 100644 --- a/drivers/usb/typec/tcpm.c +++ b/drivers/usb/typec/tcpm.c @@ -2206,8 +2206,9 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) unsigned int mv, ma, mw, flags; unsigned int max_ma, max_mw; enum pd_pdo_type type; - int src_pdo_index, snk_pdo_index; u32 pdo, matching_snk_pdo; + int src_pdo_index = 0; + int snk_pdo_index = 0; int ret; ret = tcpm_pd_select_pdo(port, &snk_pdo_index, &src_pdo_index);