From ed194d1367698a0872a2b75bbe06b3932ce9df3a Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Date: Mon, 10 Sep 2018 11:20:00 +0200 Subject: usb: core: remove local_irq_save() around ->complete() handler The core disabled interrupts before invocation the ->complete handler because the handler might have expected that interrupts are disabled. All handlers were audited and use proper locking now. With it, the core code no longer needs to disable interrupts before invoking the ->complete handler. Remove local_irq_save() statement before invoking the ->complete handler. Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/core/hcd.c | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 1c21955fe7c0..f985d2303095 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1755,20 +1755,7 @@ static void __usb_hcd_giveback_urb(struct urb *urb) /* pass ownership to the completion handler */ urb->status = status; - - /* - * We disable local IRQs here avoid possible deadlock because - * drivers may call spin_lock() to hold lock which might be - * acquired in one hard interrupt handler. - * - * The local_irq_save()/local_irq_restore() around complete() - * will be removed if current USB drivers have been cleaned up - * and no one may trigger the above deadlock situation when - * running complete() in tasklet. - */ - local_irq_save(flags); urb->complete(urb); - local_irq_restore(flags); usb_anchor_resume_wakeups(anchor); atomic_dec(&urb->use_count); -- cgit v1.2.3-58-ga151 From 71741bd6776aff40bc6e57d458a65d7e41190274 Mon Sep 17 00:00:00 2001 From: Salil Kapur <salilkapur93@gmail.com> Date: Sun, 5 Aug 2018 21:28:08 -0700 Subject: USB: Removing NULL check for pool since dma_pool_destroy is safe Removing NULL check for pool since dma_pool_destroy is safe Signed-off-by: Salil Kapur <salilkapur93@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/core/buffer.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c index 77eef8acff94..f641342cdec0 100644 --- a/drivers/usb/core/buffer.c +++ b/drivers/usb/core/buffer.c @@ -101,12 +101,8 @@ void hcd_buffer_destroy(struct usb_hcd *hcd) return; for (i = 0; i < HCD_BUFFER_POOLS; i++) { - struct dma_pool *pool = hcd->pool[i]; - - if (pool) { - dma_pool_destroy(pool); - hcd->pool[i] = NULL; - } + dma_pool_destroy(hcd->pool[i]); + hcd->pool[i] = NULL; } } -- cgit v1.2.3-58-ga151 From d1e348491a72c4118a1838377393349d780b2717 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy <vz@mleia.com> Date: Thu, 9 Aug 2018 18:30:13 +0300 Subject: usb storage: group dependent USB storage Kconfig entries together Instead of explicit setting of USB_STORAGE dependency for every underlying build entries, exploit if USB_STORAGE / endif block. The change is a trivial non-functional cleanup, it shortens the Kconfig file and it is expected to reduce zconf parser workload a little. Dependencies of USB_UAS build option are left aside deliberately. Signed-off-by: Vladimir Zapolskiy <vz@mleia.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/storage/Kconfig | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig index ec84758f0e23..8d7d76439f77 100644 --- a/drivers/usb/storage/Kconfig +++ b/drivers/usb/storage/Kconfig @@ -23,16 +23,16 @@ config USB_STORAGE To compile this driver as a module, choose M here: the module will be called usb-storage. +if USB_STORAGE + config USB_STORAGE_DEBUG bool "USB Mass Storage verbose debug" - depends on USB_STORAGE help Say Y here in order to have the USB Mass Storage code generate verbose debugging messages. config USB_STORAGE_REALTEK tristate "Realtek Card Reader support" - depends on USB_STORAGE help Say Y here to include additional code to support the power-saving function for Realtek RTS51xx USB card readers. @@ -46,7 +46,6 @@ config REALTEK_AUTOPM config USB_STORAGE_DATAFAB tristate "Datafab Compact Flash Reader support" - depends on USB_STORAGE help Support for certain Datafab CompactFlash readers. Datafab has a web page at <http://www.datafab.com/>. @@ -55,7 +54,6 @@ config USB_STORAGE_DATAFAB config USB_STORAGE_FREECOM tristate "Freecom USB/ATAPI Bridge support" - depends on USB_STORAGE help Support for the Freecom USB to IDE/ATAPI adaptor. Freecom has a web page at <http://www.freecom.de/>. @@ -64,7 +62,6 @@ config USB_STORAGE_FREECOM config USB_STORAGE_ISD200 tristate "ISD-200 USB/ATA Bridge support" - depends on USB_STORAGE ---help--- Say Y here if you want to use USB Mass Store devices based on the In-Systems Design ISD-200 USB/ATA bridge. @@ -82,7 +79,6 @@ config USB_STORAGE_ISD200 config USB_STORAGE_USBAT tristate "USBAT/USBAT02-based storage support" - depends on USB_STORAGE help Say Y here to include additional code to support storage devices based on the SCM/Shuttle USBAT/USBAT02 processors. @@ -105,7 +101,6 @@ config USB_STORAGE_USBAT config USB_STORAGE_SDDR09 tristate "SanDisk SDDR-09 (and other SmartMedia, including DPCM) support" - depends on USB_STORAGE help Say Y here to include additional code to support the Sandisk SDDR-09 SmartMedia reader in the USB Mass Storage driver. @@ -115,7 +110,6 @@ config USB_STORAGE_SDDR09 config USB_STORAGE_SDDR55 tristate "SanDisk SDDR-55 SmartMedia support" - depends on USB_STORAGE help Say Y here to include additional code to support the Sandisk SDDR-55 SmartMedia reader in the USB Mass Storage driver. @@ -124,7 +118,6 @@ config USB_STORAGE_SDDR55 config USB_STORAGE_JUMPSHOT tristate "Lexar Jumpshot Compact Flash Reader" - depends on USB_STORAGE help Say Y here to include additional code to support the Lexar Jumpshot USB CompactFlash reader. @@ -133,7 +126,6 @@ config USB_STORAGE_JUMPSHOT config USB_STORAGE_ALAUDA tristate "Olympus MAUSB-10/Fuji DPC-R1 support" - depends on USB_STORAGE help Say Y here to include additional code to support the Olympus MAUSB-10 and Fujifilm DPC-R1 USB Card reader/writer devices. @@ -145,7 +137,6 @@ config USB_STORAGE_ALAUDA config USB_STORAGE_ONETOUCH tristate "Support OneTouch Button on Maxtor Hard Drives" - depends on USB_STORAGE depends on INPUT=y || INPUT=USB_STORAGE help Say Y here to include additional code to support the Maxtor OneTouch @@ -160,7 +151,6 @@ config USB_STORAGE_ONETOUCH config USB_STORAGE_KARMA tristate "Support for Rio Karma music player" - depends on USB_STORAGE help Say Y here to include additional code to support the Rio Karma USB interface. @@ -174,7 +164,6 @@ config USB_STORAGE_KARMA config USB_STORAGE_CYPRESS_ATACB tristate "SAT emulation on Cypress USB/ATA Bridge with ATACB" - depends on USB_STORAGE ---help--- Say Y here if you want to use SAT (ata pass through) on devices based on the Cypress USB/ATA bridge supporting ATACB. This will allow you @@ -188,7 +177,6 @@ config USB_STORAGE_CYPRESS_ATACB config USB_STORAGE_ENE_UB6250 tristate "USB ENE card reader support" depends on SCSI - depends on USB_STORAGE ---help--- Say Y here if you wish to control a ENE SD/MS Card reader. Note that this driver does not support SM cards. @@ -200,6 +188,8 @@ config USB_STORAGE_ENE_UB6250 To compile this driver as a module, choose M here: the module will be called ums-eneub6250. +endif # USB_STORAGE + config USB_UAS tristate "USB Attached SCSI" depends on SCSI && USB_STORAGE -- cgit v1.2.3-58-ga151 From 2ccaabeb459a5a40852dec843ca88df7cf86a967 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy <vz@mleia.com> Date: Thu, 9 Aug 2018 18:30:14 +0300 Subject: usb storage: remove inherited SCSI dependency for USB_STORAGE_ENE_UB6250 Because USB_STORAGE build symbol strictly depends on SCSI build symbol, there is no need to specify it again. In addition USB_STORAGE_ENE_UB6250 entry description repeats a note about SCSI dependency from the parent USB_STORAGE entry description, hence the change removes this duplication. Signed-off-by: Vladimir Zapolskiy <vz@mleia.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/storage/Kconfig | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig index 8d7d76439f77..6fd427284b12 100644 --- a/drivers/usb/storage/Kconfig +++ b/drivers/usb/storage/Kconfig @@ -176,15 +176,10 @@ config USB_STORAGE_CYPRESS_ATACB config USB_STORAGE_ENE_UB6250 tristate "USB ENE card reader support" - depends on SCSI ---help--- Say Y here if you wish to control a ENE SD/MS Card reader. Note that this driver does not support SM cards. - This option depends on 'SCSI' support being enabled, but you - probably also need 'SCSI device support: SCSI disk support' - (BLK_DEV_SD) for most USB storage devices. - To compile this driver as a module, choose M here: the module will be called ums-eneub6250. -- cgit v1.2.3-58-ga151 From 697fa834c3103cda43107bce1e1c3cfb7a4603ac Mon Sep 17 00:00:00 2001 From: Colin Ian King <colin.king@canonical.com> Date: Mon, 6 Aug 2018 12:14:14 +0100 Subject: USB: typec: fsusb302: remove unused variables snk_pdo and toggling_mode_name Variables snk_pdo and toggling_mode_name are defined but are not used and hence can be removed. Cleans up clang warnings: warning: 'snk_pdo' defined but not used [-Wunused-const-variable=] warning: 'toggling_mode_name' defined but not used [-Wunused-const-variable=] Signed-off-by: Colin Ian King <colin.king@canonical.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/fusb302/fusb302.c | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 82bed9810be6..ad7cfed1cea7 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -48,13 +48,6 @@ enum toggling_mode { TOGGLING_MODE_SRC, }; -static const char * const toggling_mode_name[] = { - [TOGGLINE_MODE_OFF] = "toggling_OFF", - [TOGGLING_MODE_DRP] = "toggling_DRP", - [TOGGLING_MODE_SNK] = "toggling_SNK", - [TOGGLING_MODE_SRC] = "toggling_SRC", -}; - enum src_current_status { SRC_CURRENT_DEFAULT, SRC_CURRENT_MEDIUM, @@ -1178,10 +1171,6 @@ static const u32 src_pdo[] = { PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), }; -static const u32 snk_pdo[] = { - PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), -}; - static const struct tcpc_config fusb302_tcpc_config = { .src_pdo = src_pdo, .nr_src_pdo = ARRAY_SIZE(src_pdo), -- cgit v1.2.3-58-ga151 From ffa8a31b5b3b81f12a9d77a574cc0b25bb8e856e Mon Sep 17 00:00:00 2001 From: Linus Walleij <linus.walleij@linaro.org> Date: Sat, 1 Sep 2018 18:03:12 +0200 Subject: usb: host: fotg2: add silicon clock handling When used in a system with software-controlled silicon clocks, the FOTG210 needs to grab, prepare and enable the clock. This is needed on for example the Cortina Gemini, where the platform will by default gate off the clock unless the peripheral (in this case the USB driver) grabs and enables the clock. If there is no clock available on the platform, we live without it. Make sure to percolate probe deferrals. Signed-off-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/fotg210-hcd.c | 33 +++++++++++++++++++++++++++++---- drivers/usb/host/fotg210.h | 3 +++ 2 files changed, 32 insertions(+), 4 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index e64eb47770c8..058ff82ea789 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -31,6 +31,7 @@ #include <linux/uaccess.h> #include <linux/platform_device.h> #include <linux/io.h> +#include <linux/clk.h> #include <asm/byteorder.h> #include <asm/irq.h> @@ -5596,7 +5597,7 @@ static int fotg210_hcd_probe(struct platform_device *pdev) hcd->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(hcd->regs)) { retval = PTR_ERR(hcd->regs); - goto failed; + goto failed_put_hcd; } hcd->rsrc_start = res->start; @@ -5606,22 +5607,42 @@ static int fotg210_hcd_probe(struct platform_device *pdev) fotg210->caps = hcd->regs; + /* It's OK not to supply this clock */ + fotg210->pclk = clk_get(dev, "PCLK"); + if (!IS_ERR(fotg210->pclk)) { + retval = clk_prepare_enable(fotg210->pclk); + if (retval) { + dev_err(dev, "failed to enable PCLK\n"); + goto failed_put_hcd; + } + } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { + /* + * Percolate deferrals, for anything else, + * just live without the clocking. + */ + retval = PTR_ERR(fotg210->pclk); + goto failed_dis_clk; + } + retval = fotg210_setup(hcd); if (retval) - goto failed; + goto failed_dis_clk; fotg210_init(fotg210); retval = usb_add_hcd(hcd, irq, IRQF_SHARED); if (retval) { dev_err(dev, "failed to add hcd with err %d\n", retval); - goto failed; + goto failed_dis_clk; } device_wakeup_enable(hcd->self.controller); return retval; -failed: +failed_dis_clk: + if (!IS_ERR(fotg210->pclk)) + clk_disable_unprepare(fotg210->pclk); +failed_put_hcd: usb_put_hcd(hcd); fail_create_hcd: dev_err(dev, "init %s fail, %d\n", dev_name(dev), retval); @@ -5637,6 +5658,10 @@ static int fotg210_hcd_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct usb_hcd *hcd = dev_get_drvdata(dev); + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); + + if (!IS_ERR(fotg210->pclk)) + clk_disable_unprepare(fotg210->pclk); if (!hcd) return 0; diff --git a/drivers/usb/host/fotg210.h b/drivers/usb/host/fotg210.h index 7fcd785c7bc8..28f6467c0cbf 100644 --- a/drivers/usb/host/fotg210.h +++ b/drivers/usb/host/fotg210.h @@ -182,6 +182,9 @@ struct fotg210_hcd { /* one per controller */ # define COUNT(x) #endif + /* silicon clock */ + struct clk *pclk; + /* debug files */ struct dentry *debug_dir; }; -- cgit v1.2.3-58-ga151 From 87f88dfcde0ecde2a1136b8364099dddb9895b12 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Tue, 28 Aug 2018 10:57:25 -0400 Subject: USB: OHCI: Remove USB bus reset delay from OHCI handover code Paul pointed out that the 50-ms sleep during OHCI initialization takes up a large fraction of a system's boot time. Things get worse when there are two OHCI controllers present, each requiring 50 ms. However, there really is no need to send a 50-ms reset signal out all the root-hub ports during initialization. The ports themselves will be disabled, and the only way to enable a port is to reset it. Therefore all attached USB devices will receive a proper reset in any case. The controller reset does not need to be long enough to reset those other devices, so the 50-ms delay isn't necessary. Without the delay, there is no remaining incentive for skipping the reset when the controller is already in the RESET state. This patch removes the test, issuing the command unconditionally, and removes the following delay. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Suggested-by: Paul Menzel <pmenzel@molgen.mpg.de> Tested-by: Paul Menzel <pmenzel@molgen.mpg.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/pci-quirks.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 3625a5c1a41b..3ce71cbfbb58 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -783,15 +783,9 @@ static void quirk_usb_handoff_ohci(struct pci_dev *pdev) /* disable interrupts */ writel((u32) ~0, base + OHCI_INTRDISABLE); - /* Reset the USB bus, if the controller isn't already in RESET */ - if (control & OHCI_HCFS) { - /* Go into RESET, preserving RWC (and possibly IR) */ - writel(control & OHCI_CTRL_MASK, base + OHCI_CONTROL); - readl(base + OHCI_CONTROL); - - /* drive bus reset for at least 50 ms (7.1.7.5) */ - msleep(50); - } + /* Go into the USB_RESET state, preserving RWC (and possibly IR) */ + writel(control & OHCI_CTRL_MASK, base + OHCI_CONTROL); + readl(base + OHCI_CONTROL); /* software reset of the controller, preserving HcFmInterval */ if (!no_fminterval) -- cgit v1.2.3-58-ga151 From 23feefda22392d44ee4101dfcf946bc87a6c74b3 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" <gustavo@embeddedor.com> Date: Thu, 23 Aug 2018 12:55:27 -0500 Subject: usb: iowarrior: replace kmalloc with kmalloc_array A common flaw in the kernel is integer overflow during memory allocation size calculations. In an effort to reduce the frequency of these bugs, kmalloc_array was implemented, which allocates memory for an array, while at the same time detects integer overflow. This patch replaces cases of: kmalloc(a * b, gfp) with: kmalloc_array(a, b, gfp) Reviewed-by: Kees Cook <keescook@chromium.org> Signed-off-by: Gustavo A. R. Silva <gustavo@embeddedor.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/misc/iowarrior.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index c2991b8a65ce..ba05dd80a020 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -808,8 +808,8 @@ static int iowarrior_probe(struct usb_interface *interface, dev->int_in_endpoint->bInterval); /* create an internal buffer for interrupt data from the device */ dev->read_queue = - kmalloc(((dev->report_size + 1) * MAX_INTERRUPT_BUFFER), - GFP_KERNEL); + kmalloc_array(dev->report_size + 1, MAX_INTERRUPT_BUFFER, + GFP_KERNEL); if (!dev->read_queue) goto error; /* Get the serial-number of the chip */ -- cgit v1.2.3-58-ga151 From 9d20bca54b6a92dff75e85dce29b202847b48232 Mon Sep 17 00:00:00 2001 From: Ding Xiang <dingxiang@cmss.chinamobile.com> Date: Thu, 30 Aug 2018 17:31:18 +0800 Subject: usb: misc: fix obsolete function simple_strtoul is obsolete, and use kstrtoint instead Signed-off-by: Ding Xiang <dingxiang@cmss.chinamobile.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/misc/trancevibrator.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/misc/trancevibrator.c b/drivers/usb/misc/trancevibrator.c index b3e1f553954a..ac357ce2d1a6 100644 --- a/drivers/usb/misc/trancevibrator.c +++ b/drivers/usb/misc/trancevibrator.c @@ -46,7 +46,9 @@ static ssize_t speed_store(struct device *dev, struct device_attribute *attr, struct trancevibrator *tv = usb_get_intfdata(intf); int temp, retval, old; - temp = simple_strtoul(buf, NULL, 10); + retval = kstrtoint(buf, 10, &temp); + if (retval) + return retval; if (temp > 255) temp = 255; else if (temp < 0) -- cgit v1.2.3-58-ga151 From 1973d029d6e91c2e364492a01e53cc8e7cc66b34 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Date: Thu, 30 Aug 2018 13:30:11 +0300 Subject: USB: wusbcore: Switch to bitmap_zalloc() Switch to bitmap_zalloc() to show clearly what we are allocating. Besides that it returns pointer of bitmap type instead of opaque void *. Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/wusbcore/wa-rpipe.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/wusbcore/wa-rpipe.c b/drivers/usb/wusbcore/wa-rpipe.c index 38884aac862b..a5734cbcd5ad 100644 --- a/drivers/usb/wusbcore/wa-rpipe.c +++ b/drivers/usb/wusbcore/wa-rpipe.c @@ -470,9 +470,7 @@ error: int wa_rpipes_create(struct wahc *wa) { wa->rpipes = le16_to_cpu(wa->wa_descr->wNumRPipes); - wa->rpipe_bm = kcalloc(BITS_TO_LONGS(wa->rpipes), - sizeof(unsigned long), - GFP_KERNEL); + wa->rpipe_bm = bitmap_zalloc(wa->rpipes, GFP_KERNEL); if (wa->rpipe_bm == NULL) return -ENOMEM; return 0; @@ -487,7 +485,7 @@ void wa_rpipes_destroy(struct wahc *wa) dev_err(dev, "BUG: pipes not released on exit: %*pb\n", wa->rpipes, wa->rpipe_bm); } - kfree(wa->rpipe_bm); + bitmap_free(wa->rpipe_bm); } /* -- cgit v1.2.3-58-ga151 From 0eae49582b4dee1a0e96007e1dea5122db98371a Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Wed, 29 Aug 2018 10:36:50 +0800 Subject: usb: mtu3: disable vbus rise/fall interrupts of ltssm The vbus rise & fall interrupts are used to enable and disable U3 function of device automatically, this cause some issues when class driver is initialized as deactivated, and will skip over software-controlled connect by pullup(), but UDC wants to keep disconnect until usb_gadget_activate() is called which calls pullup() if needed. So we disable vbus rise & fall interrupts and just use pullup() to enable & disable U3 function, and reset mtu3 state when disconnect instead when vbus fall. Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/mtu3/mtu3_core.c | 4 ++-- drivers/usb/mtu3/mtu3_gadget.c | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index eecfd0671362..2ec1da641a19 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -181,8 +181,8 @@ static void mtu3_intr_enable(struct mtu3 *mtu) if (mtu->is_u3_ip) { /* Enable U3 LTSSM interrupts */ - value = HOT_RST_INTR | WARM_RST_INTR | VBUS_RISE_INTR | - VBUS_FALL_INTR | ENTER_U3_INTR | EXIT_U3_INTR; + value = HOT_RST_INTR | WARM_RST_INTR | + ENTER_U3_INTR | EXIT_U3_INTR; mtu3_writel(mbase, U3D_LTSSM_INTR_ENABLE, value); } diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 5c60a8c5a0b5..bbcd3332471d 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -585,6 +585,17 @@ static const struct usb_gadget_ops mtu3_gadget_ops = { .udc_stop = mtu3_gadget_stop, }; +static void mtu3_state_reset(struct mtu3 *mtu) +{ + mtu->address = 0; + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + mtu->may_wakeup = 0; + mtu->u1_enable = 0; + mtu->u2_enable = 0; + mtu->delayed_status = false; + mtu->test_mode = false; +} + static void init_hw_ep(struct mtu3 *mtu, struct mtu3_ep *mep, u32 epnum, u32 is_in) { @@ -702,6 +713,7 @@ void mtu3_gadget_disconnect(struct mtu3 *mtu) spin_lock(&mtu->lock); } + mtu3_state_reset(mtu); usb_gadget_set_state(&mtu->g, USB_STATE_NOTATTACHED); } @@ -712,12 +724,6 @@ void mtu3_gadget_reset(struct mtu3 *mtu) /* report disconnect, if we didn't flush EP state */ if (mtu->g.speed != USB_SPEED_UNKNOWN) mtu3_gadget_disconnect(mtu); - - mtu->address = 0; - mtu->ep0_state = MU3D_EP0_STATE_SETUP; - mtu->may_wakeup = 0; - mtu->u1_enable = 0; - mtu->u2_enable = 0; - mtu->delayed_status = false; - mtu->test_mode = false; + else + mtu3_state_reset(mtu); } -- cgit v1.2.3-58-ga151 From 0a6ab90c0a8fc8ece91ad2bf7e3310ebb563b32b Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Fri, 31 Aug 2018 18:01:54 +0800 Subject: usb: core: phy: clean up return value check about devm_of_phy_get_by_index() Use IS_ERR() instead of IS_ERR_OR_NULL() because devm_of_phy_get_by_index() never return NULL value; But still need ignore the error of -ENODEV, for more information, please refer to: [0] https://lkml.org/lkml/2018/4/19/88 [1] https://patchwork.kernel.org/patch/10160181/ Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Reviewed-by: Johan Hovold <johan@kernel.org> Reviewed-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/core/phy.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/core/phy.c b/drivers/usb/core/phy.c index 9879767452a2..38b2c776c4b4 100644 --- a/drivers/usb/core/phy.c +++ b/drivers/usb/core/phy.c @@ -23,10 +23,11 @@ static int usb_phy_roothub_add_phy(struct device *dev, int index, struct list_head *list) { struct usb_phy_roothub *roothub_entry; - struct phy *phy = devm_of_phy_get_by_index(dev, dev->of_node, index); + struct phy *phy; - if (IS_ERR_OR_NULL(phy)) { - if (!phy || PTR_ERR(phy) == -ENODEV) + phy = devm_of_phy_get_by_index(dev, dev->of_node, index); + if (IS_ERR(phy)) { + if (PTR_ERR(phy) == -ENODEV) return 0; else return PTR_ERR(phy); -- cgit v1.2.3-58-ga151 From d6142b91e9cc249b3aa22c90fade67e2e2d52cdb Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Date: Tue, 11 Sep 2018 09:37:12 +0200 Subject: usb: core: remove flags variable in __usb_hcd_giveback_urb() In commit ed194d1367698 ("usb: core: remove local_irq_save() around ->complete() handler") I removed the only user of the flags variable and forgot to remove the variable, leading to warning because it is unused now. Remove the unused variable. Fixes: ed194d1367698 ("usb: core: remove local_irq_save() around ->complete() handler") Reported-by: Stephen Rothwell <sfr@canb.auug.org.au> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Gustavo A. R. Silva <gustavo@embeddedor.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/core/hcd.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index f985d2303095..487025d31d44 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1738,7 +1738,6 @@ static void __usb_hcd_giveback_urb(struct urb *urb) struct usb_hcd *hcd = bus_to_hcd(urb->dev->bus); struct usb_anchor *anchor = urb->anchor; int status = urb->unlinked; - unsigned long flags; urb->hcpriv = NULL; if (unlikely((urb->transfer_flags & URB_SHORT_NOT_OK) && -- cgit v1.2.3-58-ga151 From b0aa30f33b6a4e0b755321e631eb5f870678eae6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Thu, 24 May 2018 17:49:34 +0300 Subject: usb: gadget: uvc: configfs: Don't wrap groups unnecessarily Various configfs groups (represented by config_group) are wrapped in structures that they're the only member of. This allows adding other data fields to groups, but it unnecessarily makes the code more complex. Remove the outer structures and use config_group directly to simplify the code. Groups can still be wrapped individually in the future if other data fields need to be added. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 302 +++++++++++------------------ 1 file changed, 117 insertions(+), 185 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index b51f0d278826..1df94b25abe1 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -162,9 +162,7 @@ static void uvcg_control_header_drop(struct config_group *group, } /* control/header */ -static struct uvcg_control_header_grp { - struct config_group group; -} uvcg_control_header_grp; +static struct config_group uvcg_control_header_grp; static struct configfs_group_operations uvcg_control_header_grp_ops = { .make_item = uvcg_control_header_make, @@ -177,31 +175,22 @@ static const struct config_item_type uvcg_control_header_grp_type = { }; /* control/processing/default */ -static struct uvcg_default_processing { - struct config_group group; -} uvcg_default_processing; - -static inline struct uvcg_default_processing -*to_uvcg_default_processing(struct config_item *item) -{ - return container_of(to_config_group(item), - struct uvcg_default_processing, group); -} +static struct config_group uvcg_default_processing_grp; #define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_processing_##cname##_show( \ struct config_item *item, char *page) \ { \ - struct uvcg_default_processing *dp = to_uvcg_default_processing(item); \ + struct config_group *group = to_config_group(item); \ struct f_uvc_opts *opts; \ struct config_item *opts_item; \ - struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ struct uvc_processing_unit_descriptor *pd; \ int result; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ - opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ opts = to_f_uvc_opts(opts_item); \ pd = &opts->uvc_processing; \ \ @@ -229,17 +218,17 @@ UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, identity_conv); static ssize_t uvcg_default_processing_bm_controls_show( struct config_item *item, char *page) { - struct uvcg_default_processing *dp = to_uvcg_default_processing(item); + struct config_group *group = to_config_group(item); struct f_uvc_opts *opts; struct config_item *opts_item; - struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; struct uvc_processing_unit_descriptor *pd; int result, i; char *pg = page; mutex_lock(su_mutex); /* for navigating configfs hierarchy */ - opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent; + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); pd = &opts->uvc_processing; @@ -274,40 +263,29 @@ static const struct config_item_type uvcg_default_processing_type = { /* struct uvcg_processing {}; */ /* control/processing */ -static struct uvcg_processing_grp { - struct config_group group; -} uvcg_processing_grp; +static struct config_group uvcg_processing_grp; static const struct config_item_type uvcg_processing_grp_type = { .ct_owner = THIS_MODULE, }; /* control/terminal/camera/default */ -static struct uvcg_default_camera { - struct config_group group; -} uvcg_default_camera; - -static inline struct uvcg_default_camera -*to_uvcg_default_camera(struct config_item *item) -{ - return container_of(to_config_group(item), - struct uvcg_default_camera, group); -} +static struct config_group uvcg_default_camera_grp; #define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_camera_##cname##_show( \ struct config_item *item, char *page) \ { \ - struct uvcg_default_camera *dc = to_uvcg_default_camera(item); \ + struct config_group *group = to_config_group(item); \ struct f_uvc_opts *opts; \ struct config_item *opts_item; \ - struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ struct uvc_camera_terminal_descriptor *cd; \ int result; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ - opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent-> \ ci_parent; \ opts = to_f_uvc_opts(opts_item); \ cd = &opts->uvc_camera_terminal; \ @@ -343,17 +321,17 @@ UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength, static ssize_t uvcg_default_camera_bm_controls_show( struct config_item *item, char *page) { - struct uvcg_default_camera *dc = to_uvcg_default_camera(item); + struct config_group *group = to_config_group(item); struct f_uvc_opts *opts; struct config_item *opts_item; - struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; + struct mutex *su_mutex = &group->cg_subsys->su_mutex; struct uvc_camera_terminal_descriptor *cd; int result, i; char *pg = page; mutex_lock(su_mutex); /* for navigating configfs hierarchy */ - opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent-> + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent-> ci_parent; opts = to_f_uvc_opts(opts_item); cd = &opts->uvc_camera_terminal; @@ -391,40 +369,29 @@ static const struct config_item_type uvcg_default_camera_type = { /* struct uvcg_camera {}; */ /* control/terminal/camera */ -static struct uvcg_camera_grp { - struct config_group group; -} uvcg_camera_grp; +static struct config_group uvcg_camera_grp; static const struct config_item_type uvcg_camera_grp_type = { .ct_owner = THIS_MODULE, }; /* control/terminal/output/default */ -static struct uvcg_default_output { - struct config_group group; -} uvcg_default_output; - -static inline struct uvcg_default_output -*to_uvcg_default_output(struct config_item *item) -{ - return container_of(to_config_group(item), - struct uvcg_default_output, group); -} +static struct config_group uvcg_default_output_grp; #define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_output_##cname##_show( \ - struct config_item *item, char *page) \ + struct config_item *item, char *page) \ { \ - struct uvcg_default_output *dout = to_uvcg_default_output(item); \ + struct config_group *group = to_config_group(item); \ struct f_uvc_opts *opts; \ struct config_item *opts_item; \ - struct mutex *su_mutex = &dout->group.cg_subsys->su_mutex; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ struct uvc_output_terminal_descriptor *cd; \ int result; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ - opts_item = dout->group.cg_item.ci_parent->ci_parent-> \ + opts_item = group->cg_item.ci_parent->ci_parent-> \ ci_parent->ci_parent; \ opts = to_f_uvc_opts(opts_item); \ cd = &opts->uvc_output_terminal; \ @@ -469,39 +436,32 @@ static const struct config_item_type uvcg_default_output_type = { /* struct uvcg_output {}; */ /* control/terminal/output */ -static struct uvcg_output_grp { - struct config_group group; -} uvcg_output_grp; +static struct config_group uvcg_output_grp; static const struct config_item_type uvcg_output_grp_type = { .ct_owner = THIS_MODULE, }; /* control/terminal */ -static struct uvcg_terminal_grp { - struct config_group group; -} uvcg_terminal_grp; +static struct config_group uvcg_terminal_grp; static const struct config_item_type uvcg_terminal_grp_type = { .ct_owner = THIS_MODULE, }; /* control/class/{fs} */ -static struct uvcg_control_class { - struct config_group group; -} uvcg_control_class_fs, uvcg_control_class_ss; - +static struct config_group uvcg_control_class_fs_grp; +static struct config_group uvcg_control_class_ss_grp; static inline struct uvc_descriptor_header **uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o) { - struct uvcg_control_class *cl = container_of(to_config_group(i), - struct uvcg_control_class, group); + struct config_group *group = to_config_group(i); - if (cl == &uvcg_control_class_fs) + if (group == &uvcg_control_class_fs_grp) return o->uvc_fs_control_cls; - if (cl == &uvcg_control_class_ss) + if (group == &uvcg_control_class_ss_grp) return o->uvc_ss_control_cls; return NULL; @@ -593,36 +553,28 @@ static const struct config_item_type uvcg_control_class_type = { }; /* control/class */ -static struct uvcg_control_class_grp { - struct config_group group; -} uvcg_control_class_grp; +static struct config_group uvcg_control_class_grp; static const struct config_item_type uvcg_control_class_grp_type = { .ct_owner = THIS_MODULE, }; /* control */ -static struct uvcg_control_grp { - struct config_group group; -} uvcg_control_grp; +static struct config_group uvcg_control_grp; static const struct config_item_type uvcg_control_grp_type = { .ct_owner = THIS_MODULE, }; /* streaming/uncompressed */ -static struct uvcg_uncompressed_grp { - struct config_group group; -} uvcg_uncompressed_grp; +static struct config_group uvcg_uncompressed_grp; /* streaming/mjpeg */ -static struct uvcg_mjpeg_grp { - struct config_group group; -} uvcg_mjpeg_grp; +static struct config_group uvcg_mjpeg_grp; static struct config_item *fmt_parent[] = { - &uvcg_uncompressed_grp.group.cg_item, - &uvcg_mjpeg_grp.group.cg_item, + &uvcg_uncompressed_grp.cg_item, + &uvcg_mjpeg_grp.cg_item, }; enum uvcg_format_type { @@ -893,9 +845,7 @@ static void uvcg_streaming_header_drop(struct config_group *group, } /* streaming/header */ -static struct uvcg_streaming_header_grp { - struct config_group group; -} uvcg_streaming_header_grp; +static struct config_group uvcg_streaming_header_grp; static struct configfs_group_operations uvcg_streaming_header_grp_ops = { .make_item = uvcg_streaming_header_make, @@ -1670,32 +1620,22 @@ static const struct config_item_type uvcg_mjpeg_grp_type = { }; /* streaming/color_matching/default */ -static struct uvcg_default_color_matching { - struct config_group group; -} uvcg_default_color_matching; - -static inline struct uvcg_default_color_matching -*to_uvcg_default_color_matching(struct config_item *item) -{ - return container_of(to_config_group(item), - struct uvcg_default_color_matching, group); -} +static struct config_group uvcg_default_color_matching_grp; #define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_color_matching_##cname##_show( \ - struct config_item *item, char *page) \ + struct config_item *item, char *page) \ { \ - struct uvcg_default_color_matching *dc = \ - to_uvcg_default_color_matching(item); \ + struct config_group *group = to_config_group(item); \ struct f_uvc_opts *opts; \ struct config_item *opts_item; \ - struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex; \ + struct mutex *su_mutex = &group->cg_subsys->su_mutex; \ struct uvc_color_matching_descriptor *cd; \ int result; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ - opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent; \ + opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; \ opts = to_f_uvc_opts(opts_item); \ cd = &opts->uvc_color_matching; \ \ @@ -1737,33 +1677,29 @@ static const struct config_item_type uvcg_default_color_matching_type = { /* struct uvcg_color_matching {}; */ /* streaming/color_matching */ -static struct uvcg_color_matching_grp { - struct config_group group; -} uvcg_color_matching_grp; +static struct config_group uvcg_color_matching_grp; static const struct config_item_type uvcg_color_matching_grp_type = { .ct_owner = THIS_MODULE, }; /* streaming/class/{fs|hs|ss} */ -static struct uvcg_streaming_class { - struct config_group group; -} uvcg_streaming_class_fs, uvcg_streaming_class_hs, uvcg_streaming_class_ss; - +static struct config_group uvcg_streaming_class_fs_grp; +static struct config_group uvcg_streaming_class_hs_grp; +static struct config_group uvcg_streaming_class_ss_grp; static inline struct uvc_descriptor_header ***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o) { - struct uvcg_streaming_class *cl = container_of(to_config_group(i), - struct uvcg_streaming_class, group); + struct config_group *group = to_config_group(i); - if (cl == &uvcg_streaming_class_fs) + if (group == &uvcg_streaming_class_fs_grp) return &o->uvc_fs_streaming_cls; - if (cl == &uvcg_streaming_class_hs) + if (group == &uvcg_streaming_class_hs_grp) return &o->uvc_hs_streaming_cls; - if (cl == &uvcg_streaming_class_ss) + if (group == &uvcg_streaming_class_ss_grp) return &o->uvc_ss_streaming_cls; return NULL; @@ -2092,18 +2028,14 @@ static const struct config_item_type uvcg_streaming_class_type = { }; /* streaming/class */ -static struct uvcg_streaming_class_grp { - struct config_group group; -} uvcg_streaming_class_grp; +static struct config_group uvcg_streaming_class_grp; static const struct config_item_type uvcg_streaming_class_grp_type = { .ct_owner = THIS_MODULE, }; /* streaming */ -static struct uvcg_streaming_grp { - struct config_group group; -} uvcg_streaming_grp; +static struct config_group uvcg_streaming_grp; static const struct config_item_type uvcg_streaming_grp_type = { .ct_owner = THIS_MODULE, @@ -2193,114 +2125,114 @@ static const struct config_item_type uvc_func_type = { int uvcg_attach_configfs(struct f_uvc_opts *opts) { - config_group_init_type_name(&uvcg_control_header_grp.group, + config_group_init_type_name(&uvcg_control_header_grp, "header", &uvcg_control_header_grp_type); - config_group_init_type_name(&uvcg_default_processing.group, + config_group_init_type_name(&uvcg_default_processing_grp, "default", &uvcg_default_processing_type); - config_group_init_type_name(&uvcg_processing_grp.group, + config_group_init_type_name(&uvcg_processing_grp, "processing", &uvcg_processing_grp_type); - configfs_add_default_group(&uvcg_default_processing.group, - &uvcg_processing_grp.group); + configfs_add_default_group(&uvcg_default_processing_grp, + &uvcg_processing_grp); - config_group_init_type_name(&uvcg_default_camera.group, + config_group_init_type_name(&uvcg_default_camera_grp, "default", &uvcg_default_camera_type); - config_group_init_type_name(&uvcg_camera_grp.group, + config_group_init_type_name(&uvcg_camera_grp, "camera", &uvcg_camera_grp_type); - configfs_add_default_group(&uvcg_default_camera.group, - &uvcg_camera_grp.group); + configfs_add_default_group(&uvcg_default_camera_grp, + &uvcg_camera_grp); - config_group_init_type_name(&uvcg_default_output.group, + config_group_init_type_name(&uvcg_default_output_grp, "default", &uvcg_default_output_type); - config_group_init_type_name(&uvcg_output_grp.group, + config_group_init_type_name(&uvcg_output_grp, "output", &uvcg_output_grp_type); - configfs_add_default_group(&uvcg_default_output.group, - &uvcg_output_grp.group); + configfs_add_default_group(&uvcg_default_output_grp, + &uvcg_output_grp); - config_group_init_type_name(&uvcg_terminal_grp.group, + config_group_init_type_name(&uvcg_terminal_grp, "terminal", &uvcg_terminal_grp_type); - configfs_add_default_group(&uvcg_camera_grp.group, - &uvcg_terminal_grp.group); - configfs_add_default_group(&uvcg_output_grp.group, - &uvcg_terminal_grp.group); + configfs_add_default_group(&uvcg_camera_grp, + &uvcg_terminal_grp); + configfs_add_default_group(&uvcg_output_grp, + &uvcg_terminal_grp); - config_group_init_type_name(&uvcg_control_class_fs.group, + config_group_init_type_name(&uvcg_control_class_fs_grp, "fs", &uvcg_control_class_type); - config_group_init_type_name(&uvcg_control_class_ss.group, + config_group_init_type_name(&uvcg_control_class_ss_grp, "ss", &uvcg_control_class_type); - config_group_init_type_name(&uvcg_control_class_grp.group, + config_group_init_type_name(&uvcg_control_class_grp, "class", &uvcg_control_class_grp_type); - configfs_add_default_group(&uvcg_control_class_fs.group, - &uvcg_control_class_grp.group); - configfs_add_default_group(&uvcg_control_class_ss.group, - &uvcg_control_class_grp.group); + configfs_add_default_group(&uvcg_control_class_fs_grp, + &uvcg_control_class_grp); + configfs_add_default_group(&uvcg_control_class_ss_grp, + &uvcg_control_class_grp); - config_group_init_type_name(&uvcg_control_grp.group, + config_group_init_type_name(&uvcg_control_grp, "control", &uvcg_control_grp_type); - configfs_add_default_group(&uvcg_control_header_grp.group, - &uvcg_control_grp.group); - configfs_add_default_group(&uvcg_processing_grp.group, - &uvcg_control_grp.group); - configfs_add_default_group(&uvcg_terminal_grp.group, - &uvcg_control_grp.group); - configfs_add_default_group(&uvcg_control_class_grp.group, - &uvcg_control_grp.group); - - config_group_init_type_name(&uvcg_streaming_header_grp.group, + configfs_add_default_group(&uvcg_control_header_grp, + &uvcg_control_grp); + configfs_add_default_group(&uvcg_processing_grp, + &uvcg_control_grp); + configfs_add_default_group(&uvcg_terminal_grp, + &uvcg_control_grp); + configfs_add_default_group(&uvcg_control_class_grp, + &uvcg_control_grp); + + config_group_init_type_name(&uvcg_streaming_header_grp, "header", &uvcg_streaming_header_grp_type); - config_group_init_type_name(&uvcg_uncompressed_grp.group, + config_group_init_type_name(&uvcg_uncompressed_grp, "uncompressed", &uvcg_uncompressed_grp_type); - config_group_init_type_name(&uvcg_mjpeg_grp.group, + config_group_init_type_name(&uvcg_mjpeg_grp, "mjpeg", &uvcg_mjpeg_grp_type); - config_group_init_type_name(&uvcg_default_color_matching.group, + config_group_init_type_name(&uvcg_default_color_matching_grp, "default", &uvcg_default_color_matching_type); - config_group_init_type_name(&uvcg_color_matching_grp.group, + config_group_init_type_name(&uvcg_color_matching_grp, "color_matching", &uvcg_color_matching_grp_type); - configfs_add_default_group(&uvcg_default_color_matching.group, - &uvcg_color_matching_grp.group); + configfs_add_default_group(&uvcg_default_color_matching_grp, + &uvcg_color_matching_grp); - config_group_init_type_name(&uvcg_streaming_class_fs.group, + config_group_init_type_name(&uvcg_streaming_class_fs_grp, "fs", &uvcg_streaming_class_type); - config_group_init_type_name(&uvcg_streaming_class_hs.group, + config_group_init_type_name(&uvcg_streaming_class_hs_grp, "hs", &uvcg_streaming_class_type); - config_group_init_type_name(&uvcg_streaming_class_ss.group, + config_group_init_type_name(&uvcg_streaming_class_ss_grp, "ss", &uvcg_streaming_class_type); - config_group_init_type_name(&uvcg_streaming_class_grp.group, + config_group_init_type_name(&uvcg_streaming_class_grp, "class", &uvcg_streaming_class_grp_type); - configfs_add_default_group(&uvcg_streaming_class_fs.group, - &uvcg_streaming_class_grp.group); - configfs_add_default_group(&uvcg_streaming_class_hs.group, - &uvcg_streaming_class_grp.group); - configfs_add_default_group(&uvcg_streaming_class_ss.group, - &uvcg_streaming_class_grp.group); - - config_group_init_type_name(&uvcg_streaming_grp.group, + configfs_add_default_group(&uvcg_streaming_class_fs_grp, + &uvcg_streaming_class_grp); + configfs_add_default_group(&uvcg_streaming_class_hs_grp, + &uvcg_streaming_class_grp); + configfs_add_default_group(&uvcg_streaming_class_ss_grp, + &uvcg_streaming_class_grp); + + config_group_init_type_name(&uvcg_streaming_grp, "streaming", &uvcg_streaming_grp_type); - configfs_add_default_group(&uvcg_streaming_header_grp.group, - &uvcg_streaming_grp.group); - configfs_add_default_group(&uvcg_uncompressed_grp.group, - &uvcg_streaming_grp.group); - configfs_add_default_group(&uvcg_mjpeg_grp.group, - &uvcg_streaming_grp.group); - configfs_add_default_group(&uvcg_color_matching_grp.group, - &uvcg_streaming_grp.group); - configfs_add_default_group(&uvcg_streaming_class_grp.group, - &uvcg_streaming_grp.group); + configfs_add_default_group(&uvcg_streaming_header_grp, + &uvcg_streaming_grp); + configfs_add_default_group(&uvcg_uncompressed_grp, + &uvcg_streaming_grp); + configfs_add_default_group(&uvcg_mjpeg_grp, + &uvcg_streaming_grp); + configfs_add_default_group(&uvcg_color_matching_grp, + &uvcg_streaming_grp); + configfs_add_default_group(&uvcg_streaming_class_grp, + &uvcg_streaming_grp); config_group_init_type_name(&opts->func_inst.group, "", &uvc_func_type); - configfs_add_default_group(&uvcg_control_grp.group, + configfs_add_default_group(&uvcg_control_grp, &opts->func_inst.group); - configfs_add_default_group(&uvcg_streaming_grp.group, + configfs_add_default_group(&uvcg_streaming_grp, &opts->func_inst.group); return 0; -- cgit v1.2.3-58-ga151 From f7d8109e31bbe785b13c8cfdafc56e9351bccef1 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Thu, 24 May 2018 17:49:34 +0300 Subject: usb: gadget: uvc: configfs: Add section header comments The UVC configfs implementation is large and difficult to navigate. Add a bit more air to the code to make it easier to read. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 120 ++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 29 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 1df94b25abe1..dbc95c9558de 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -12,6 +12,10 @@ #include "u_uvc.h" #include "uvc_configfs.h" +/* ----------------------------------------------------------------------------- + * Global Utility Structures and Macros + */ + #define UVCG_STREAMING_CONTROL_SIZE 1 #define UVC_ATTR(prefix, cname, aname) \ @@ -37,7 +41,11 @@ static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) func_inst.group); } -/* control/header/<NAME> */ +/* ----------------------------------------------------------------------------- + * control/header/<NAME> + * control/header + */ + DECLARE_UVC_HEADER_DESCRIPTOR(1); struct uvcg_control_header { @@ -161,7 +169,6 @@ static void uvcg_control_header_drop(struct config_group *group, kfree(h); } -/* control/header */ static struct config_group uvcg_control_header_grp; static struct configfs_group_operations uvcg_control_header_grp_ops = { @@ -174,7 +181,10 @@ static const struct config_item_type uvcg_control_header_grp_type = { .ct_owner = THIS_MODULE, }; -/* control/processing/default */ +/* ----------------------------------------------------------------------------- + * control/processing/default + */ + static struct config_group uvcg_default_processing_grp; #define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \ @@ -260,16 +270,20 @@ static const struct config_item_type uvcg_default_processing_type = { .ct_owner = THIS_MODULE, }; -/* struct uvcg_processing {}; */ +/* ----------------------------------------------------------------------------- + * control/processing + */ -/* control/processing */ static struct config_group uvcg_processing_grp; static const struct config_item_type uvcg_processing_grp_type = { .ct_owner = THIS_MODULE, }; -/* control/terminal/camera/default */ +/* ----------------------------------------------------------------------------- + * control/terminal/camera/default + */ + static struct config_group uvcg_default_camera_grp; #define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \ @@ -366,16 +380,20 @@ static const struct config_item_type uvcg_default_camera_type = { .ct_owner = THIS_MODULE, }; -/* struct uvcg_camera {}; */ +/* ----------------------------------------------------------------------------- + * control/terminal/camera + */ -/* control/terminal/camera */ static struct config_group uvcg_camera_grp; static const struct config_item_type uvcg_camera_grp_type = { .ct_owner = THIS_MODULE, }; -/* control/terminal/output/default */ +/* ----------------------------------------------------------------------------- + * control/terminal/output/default + */ + static struct config_group uvcg_default_output_grp; #define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \ @@ -433,23 +451,30 @@ static const struct config_item_type uvcg_default_output_type = { .ct_owner = THIS_MODULE, }; -/* struct uvcg_output {}; */ +/* ----------------------------------------------------------------------------- + * control/terminal/output + */ -/* control/terminal/output */ static struct config_group uvcg_output_grp; static const struct config_item_type uvcg_output_grp_type = { .ct_owner = THIS_MODULE, }; -/* control/terminal */ +/* ----------------------------------------------------------------------------- + * control/terminal + */ + static struct config_group uvcg_terminal_grp; static const struct config_item_type uvcg_terminal_grp_type = { .ct_owner = THIS_MODULE, }; -/* control/class/{fs} */ +/* ----------------------------------------------------------------------------- + * control/class/{fs|ss} + */ + static struct config_group uvcg_control_class_fs_grp; static struct config_group uvcg_control_class_ss_grp; @@ -552,24 +577,32 @@ static const struct config_item_type uvcg_control_class_type = { .ct_owner = THIS_MODULE, }; -/* control/class */ +/* ----------------------------------------------------------------------------- + * control/class + */ + static struct config_group uvcg_control_class_grp; static const struct config_item_type uvcg_control_class_grp_type = { .ct_owner = THIS_MODULE, }; -/* control */ +/* ----------------------------------------------------------------------------- + * control + */ + static struct config_group uvcg_control_grp; static const struct config_item_type uvcg_control_grp_type = { .ct_owner = THIS_MODULE, }; -/* streaming/uncompressed */ -static struct config_group uvcg_uncompressed_grp; +/* ----------------------------------------------------------------------------- + * streaming/uncompressed + * streaming/mjpeg + */ -/* streaming/mjpeg */ +static struct config_group uvcg_uncompressed_grp; static struct config_group uvcg_mjpeg_grp; static struct config_item *fmt_parent[] = { @@ -658,7 +691,11 @@ struct uvcg_format_ptr { struct list_head entry; }; -/* streaming/header/<NAME> */ +/* ----------------------------------------------------------------------------- + * streaming/header/<NAME> + * streaming/header + */ + struct uvcg_streaming_header { struct config_item item; struct uvc_input_header_descriptor desc; @@ -844,7 +881,6 @@ static void uvcg_streaming_header_drop(struct config_group *group, kfree(h); } -/* streaming/header */ static struct config_group uvcg_streaming_header_grp; static struct configfs_group_operations uvcg_streaming_header_grp_ops = { @@ -857,7 +893,10 @@ static const struct config_item_type uvcg_streaming_header_grp_type = { .ct_owner = THIS_MODULE, }; -/* streaming/<mode>/<format>/<NAME> */ +/* ----------------------------------------------------------------------------- + * streaming/<mode>/<format>/<NAME> + */ + struct uvcg_frame { struct { u8 b_length; @@ -1168,7 +1207,10 @@ static void uvcg_frame_drop(struct config_group *group, struct config_item *item mutex_unlock(&opts->lock); } -/* streaming/uncompressed/<NAME> */ +/* ----------------------------------------------------------------------------- + * streaming/uncompressed/<NAME> + */ + struct uvcg_uncompressed { struct uvcg_format fmt; struct uvc_format_uncompressed desc; @@ -1425,7 +1467,10 @@ static const struct config_item_type uvcg_uncompressed_grp_type = { .ct_owner = THIS_MODULE, }; -/* streaming/mjpeg/<NAME> */ +/* ----------------------------------------------------------------------------- + * streaming/mjpeg/<NAME> + */ + struct uvcg_mjpeg { struct uvcg_format fmt; struct uvc_format_mjpeg desc; @@ -1619,7 +1664,10 @@ static const struct config_item_type uvcg_mjpeg_grp_type = { .ct_owner = THIS_MODULE, }; -/* streaming/color_matching/default */ +/* ----------------------------------------------------------------------------- + * streaming/color_matching/default + */ + static struct config_group uvcg_default_color_matching_grp; #define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv) \ @@ -1674,16 +1722,20 @@ static const struct config_item_type uvcg_default_color_matching_type = { .ct_owner = THIS_MODULE, }; -/* struct uvcg_color_matching {}; */ +/* ----------------------------------------------------------------------------- + * streaming/color_matching + */ -/* streaming/color_matching */ static struct config_group uvcg_color_matching_grp; static const struct config_item_type uvcg_color_matching_grp_type = { .ct_owner = THIS_MODULE, }; -/* streaming/class/{fs|hs|ss} */ +/* ----------------------------------------------------------------------------- + * streaming/class/{fs|hs|ss} + */ + static struct config_group uvcg_streaming_class_fs_grp; static struct config_group uvcg_streaming_class_hs_grp; static struct config_group uvcg_streaming_class_ss_grp; @@ -2027,20 +2079,30 @@ static const struct config_item_type uvcg_streaming_class_type = { .ct_owner = THIS_MODULE, }; -/* streaming/class */ +/* ----------------------------------------------------------------------------- + * streaming/class + */ + static struct config_group uvcg_streaming_class_grp; static const struct config_item_type uvcg_streaming_class_grp_type = { .ct_owner = THIS_MODULE, }; -/* streaming */ +/* ----------------------------------------------------------------------------- + * streaming + */ + static struct config_group uvcg_streaming_grp; static const struct config_item_type uvcg_streaming_grp_type = { .ct_owner = THIS_MODULE, }; +/* ----------------------------------------------------------------------------- + * UVC function + */ + static void uvc_attr_release(struct config_item *item) { struct f_uvc_opts *opts = to_f_uvc_opts(item); -- cgit v1.2.3-58-ga151 From 9f644a64884f97f0d92f0689afc7fcf177b6ee92 Mon Sep 17 00:00:00 2001 From: Marcus Folkesson <marcus.folkesson@gmail.com> Date: Sun, 2 Sep 2018 19:37:04 +0200 Subject: usb: chipidea: imx: do not use preprocessor conditionals for PM Use preprocessor conditionals for CONFIG_PM and CONFIG_PM_SLEEP is not necessary since SET_SYSTEM_SLEEP_PM_OPS and SET_RUNTIME_PM_OPS does that internally. It is also the preferred way according to our coding style guidelines. Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com> Signed-off-by: Peter Chen <peter.chen@nxp.com> --- drivers/usb/chipidea/ci_hdrc_imx.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 19f5f5f2a48a..ab79d23ca6ec 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -364,8 +364,7 @@ static void ci_hdrc_imx_shutdown(struct platform_device *pdev) ci_hdrc_imx_remove(pdev); } -#ifdef CONFIG_PM -static int imx_controller_suspend(struct device *dev) +static int __maybe_unused imx_controller_suspend(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); @@ -377,7 +376,7 @@ static int imx_controller_suspend(struct device *dev) return 0; } -static int imx_controller_resume(struct device *dev) +static int __maybe_unused imx_controller_resume(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret = 0; @@ -408,8 +407,7 @@ clk_disable: return ret; } -#ifdef CONFIG_PM_SLEEP -static int ci_hdrc_imx_suspend(struct device *dev) +static int __maybe_unused ci_hdrc_imx_suspend(struct device *dev) { int ret; @@ -431,7 +429,7 @@ static int ci_hdrc_imx_suspend(struct device *dev) return imx_controller_suspend(dev); } -static int ci_hdrc_imx_resume(struct device *dev) +static int __maybe_unused ci_hdrc_imx_resume(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret; @@ -445,9 +443,8 @@ static int ci_hdrc_imx_resume(struct device *dev) return ret; } -#endif /* CONFIG_PM_SLEEP */ -static int ci_hdrc_imx_runtime_suspend(struct device *dev) +static int __maybe_unused ci_hdrc_imx_runtime_suspend(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret; @@ -466,13 +463,11 @@ static int ci_hdrc_imx_runtime_suspend(struct device *dev) return imx_controller_suspend(dev); } -static int ci_hdrc_imx_runtime_resume(struct device *dev) +static int __maybe_unused ci_hdrc_imx_runtime_resume(struct device *dev) { return imx_controller_resume(dev); } -#endif /* CONFIG_PM */ - static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ci_hdrc_imx_suspend, ci_hdrc_imx_resume) SET_RUNTIME_PM_OPS(ci_hdrc_imx_runtime_suspend, -- cgit v1.2.3-58-ga151 From 1dedbdf2bbb1ede8d96f35f9845ecae179dc1988 Mon Sep 17 00:00:00 2001 From: Nicolas Adell <nicolas.adell@actia.fr> Date: Mon, 27 Aug 2018 15:59:56 +0200 Subject: usb: chipidea: imx: enable OTG overcurrent in case USB subsystem is already started When initializing the USB subsystem before starting the kernel, OTG overcurrent detection is disabled. In case the OTG polarity of overcurrent is low active, the overcurrent detection is never enabled again and events cannot be reported as expected. Because imx usb overcurrent polarity is low active by default, only detection needs to be enable in usbmisc init function. Signed-off-by: Nicolas Adell <nicolas.adell@actia.fr> Signed-off-by: Peter Chen <peter.chen@nxp.com> --- drivers/usb/chipidea/usbmisc_imx.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 34ad5bf8acd8..424ecb1f003f 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -343,6 +343,8 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) } else if (data->oc_polarity == 1) { /* High active */ reg &= ~(MX6_BM_OVER_CUR_DIS | MX6_BM_OVER_CUR_POLARITY); + } else { + reg &= ~(MX6_BM_OVER_CUR_DIS); } writel(reg, usbmisc->base + data->index * 4); -- cgit v1.2.3-58-ga151 From 1f06072cd22fbbd2e961b49c8e4fa9f7a0c120d6 Mon Sep 17 00:00:00 2001 From: Marcus Folkesson <marcus.folkesson@gmail.com> Date: Sun, 2 Sep 2018 19:36:50 +0200 Subject: usb: chipidea: imx: make MODULE_LICENCE and SPDX-identifier match The SPDX-License-Identifier is set to GPL-2.0+, which correspond to MODULE_LICENSE "GPL". Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com> Signed-off-by: Peter Chen <peter.chen@nxp.com> --- drivers/usb/chipidea/ci_hdrc_imx.c | 2 +- drivers/usb/chipidea/usbmisc_imx.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index ab79d23ca6ec..09b37c0d075d 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -487,7 +487,7 @@ static struct platform_driver ci_hdrc_imx_driver = { module_platform_driver(ci_hdrc_imx_driver); MODULE_ALIAS("platform:imx-usb"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("CI HDRC i.MX USB binding"); MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>"); diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 424ecb1f003f..def80ff547e4 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -635,6 +635,6 @@ static struct platform_driver usbmisc_imx_driver = { module_platform_driver(usbmisc_imx_driver); MODULE_ALIAS("platform:usbmisc-imx"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("driver for imx usb non-core registers"); MODULE_AUTHOR("Richard Zhao <richard.zhao@freescale.com>"); -- cgit v1.2.3-58-ga151 From 16caf1fa37db4722d8d8c7bc26177279949d75a6 Mon Sep 17 00:00:00 2001 From: Loic Poulain <loic.poulain@linaro.org> Date: Tue, 4 Sep 2018 17:18:55 +0200 Subject: usb: chipidea: Add dynamic pinctrl selection Some hardware implementations require to configure pins differently according to the USB role (host/device), this can be an update of the pins routing or a simple GPIO value change. This patch introduces new optional "host" and "device" pinctrls. If these pinctrls are defined by the device, they are respectively selected on host/device role start. If a default pinctrl exist, it is restored on host/device role stop. Signed-off-by: Loic Poulain <loic.poulain@linaro.org> Signed-off-by: Peter Chen <peter.chen@nxp.com> --- drivers/usb/chipidea/core.c | 19 +++++++++++++++++++ drivers/usb/chipidea/host.c | 9 +++++++++ drivers/usb/chipidea/udc.c | 9 +++++++++ include/linux/usb/chipidea.h | 6 ++++++ 4 files changed, 43 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 85fc6db48e44..7bfcbb23c2a4 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -53,6 +53,7 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/pm_runtime.h> +#include <linux/pinctrl/consumer.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/usb/otg.h> @@ -723,6 +724,24 @@ static int ci_get_platdata(struct device *dev, else cable->connected = false; } + + platdata->pctl = devm_pinctrl_get(dev); + if (!IS_ERR(platdata->pctl)) { + struct pinctrl_state *p; + + p = pinctrl_lookup_state(platdata->pctl, "default"); + if (!IS_ERR(p)) + platdata->pins_default = p; + + p = pinctrl_lookup_state(platdata->pctl, "host"); + if (!IS_ERR(p)) + platdata->pins_host = p; + + p = pinctrl_lookup_state(platdata->pctl, "device"); + if (!IS_ERR(p)) + platdata->pins_device = p; + } + return 0; } diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 4638d9b066be..d858a82c4f44 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -13,6 +13,7 @@ #include <linux/usb/hcd.h> #include <linux/usb/chipidea.h> #include <linux/regulator/consumer.h> +#include <linux/pinctrl/consumer.h> #include "../host/ehci.h" @@ -153,6 +154,10 @@ static int host_start(struct ci_hdrc *ci) } } + if (ci->platdata->pins_host) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_host); + ret = usb_add_hcd(hcd, 0, 0); if (ret) { goto disable_reg; @@ -197,6 +202,10 @@ static void host_stop(struct ci_hdrc *ci) } ci->hcd = NULL; ci->otg.host = NULL; + + if (ci->platdata->pins_host && ci->platdata->pins_default) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_default); } diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 9852ec5e6e01..829e947cabf5 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -15,6 +15,7 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/pm_runtime.h> +#include <linux/pinctrl/consumer.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/usb/otg-fsm.h> @@ -1965,6 +1966,10 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) static int udc_id_switch_for_device(struct ci_hdrc *ci) { + if (ci->platdata->pins_device) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_device); + if (ci->is_otg) /* Clear and enable BSV irq */ hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, @@ -1983,6 +1988,10 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci) hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); ci->vbus_active = 0; + + if (ci->platdata->pins_device && ci->platdata->pins_default) + pinctrl_select_state(ci->platdata->pctl, + ci->platdata->pins_default); } /** diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index 07f99362bc90..63758c399e4e 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -77,6 +77,12 @@ struct ci_hdrc_platform_data { struct ci_hdrc_cable vbus_extcon; struct ci_hdrc_cable id_extcon; u32 phy_clkgate_delay_us; + + /* pins */ + struct pinctrl *pctl; + struct pinctrl_state *pins_default; + struct pinctrl_state *pins_host; + struct pinctrl_state *pins_device; }; /* Default offset of capability registers */ -- cgit v1.2.3-58-ga151 From 8b97d73c4d72a2abf58f8e49062a7ee1e5f1334e Mon Sep 17 00:00:00 2001 From: Loic Poulain <loic.poulain@linaro.org> Date: Tue, 4 Sep 2018 17:18:57 +0200 Subject: usb: chipidea: Prevent unbalanced IRQ disable The ChipIdea IRQ is disabled before scheduling the otg work and re-enabled on otg work completion. However if the job is already scheduled we have to undo the effect of disable_irq int order to balance the IRQ disable-depth value. Fixes: be6b0c1bd0be ("usb: chipidea: using one inline function to cover queue work operations") Signed-off-by: Loic Poulain <loic.poulain@linaro.org> Signed-off-by: Peter Chen <peter.chen@nxp.com> --- drivers/usb/chipidea/otg.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h index 7e7428e48bfa..4f8b8179ec96 100644 --- a/drivers/usb/chipidea/otg.h +++ b/drivers/usb/chipidea/otg.h @@ -17,7 +17,8 @@ void ci_handle_vbus_change(struct ci_hdrc *ci); static inline void ci_otg_queue_work(struct ci_hdrc *ci) { disable_irq_nosync(ci->irq); - queue_work(ci->wq, &ci->work); + if (queue_work(ci->wq, &ci->work) == false) + enable_irq(ci->irq); } #endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ -- cgit v1.2.3-58-ga151 From 59739131e0ca06db7560f9073fff2fb83f6bc2a5 Mon Sep 17 00:00:00 2001 From: Loic Poulain <loic.poulain@linaro.org> Date: Tue, 4 Sep 2018 17:18:58 +0200 Subject: usb: chipidea: Fix otg event handler At OTG work running time, it's possible that several events need to be addressed (e.g. ID and VBUS events). The current implementation handles only one event at a time which leads to ignoring the other one. Fix it. Signed-off-by: Loic Poulain <loic.poulain@linaro.org> Signed-off-by: Peter Chen <peter.chen@nxp.com> --- drivers/usb/chipidea/otg.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index db4ceffcf2a6..f25d4827fd49 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -203,14 +203,17 @@ static void ci_otg_work(struct work_struct *work) } pm_runtime_get_sync(ci->dev); + if (ci->id_event) { ci->id_event = false; ci_handle_id_switch(ci); - } else if (ci->b_sess_valid_event) { + } + + if (ci->b_sess_valid_event) { ci->b_sess_valid_event = false; ci_handle_vbus_change(ci); - } else - dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); + } + pm_runtime_put_sync(ci->dev); enable_irq(ci->irq); -- cgit v1.2.3-58-ga151 From 38c6528d40d8085183916b3ef851ddea66fd84dd Mon Sep 17 00:00:00 2001 From: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Date: Tue, 21 Aug 2018 13:28:23 +0100 Subject: usb: typec: fusb302: Populate tcpc fwnode for TCPM property handling This update populates the tcpc handle's fwnode pointer with the child usb-connector node, if it exists, so that TCPM can perform generic property handling to define the ports capabilities. Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/fusb302/fusb302.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index ad7cfed1cea7..1cb0cd2a4e63 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -1725,6 +1725,9 @@ static int fusb302_probe(struct i2c_client *client, chip->tcpc_dev.config = &chip->tcpc_config; mutex_init(&chip->lock); + chip->tcpc_dev.fwnode = + device_get_named_child_node(dev, "connector"); + if (!device_property_read_u32(dev, "fcs,operating-sink-microwatt", &v)) chip->tcpc_config.operating_snk_mw = v / 1000; -- cgit v1.2.3-58-ga151 From 658f24f4523e41cda6a389c38b763f4c0cad6fbc Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:51 +0200 Subject: usb: usbtmc: Add ioctl for generic requests on control Add USBTMC_IOCTL_CTRL_REQUEST to send arbitrary requests on the control pipe. Used by specific applications of IVI Foundation, Inc. to implement VISA API functions: viUsbControlIn/Out. The maximum length of control request is set to 4k. This ioctl does not support compatibility for 32 bit applications running on 64 bit systems. However all other convenient ioctls of the USBTMC driver can still be used in 32 bit applications as well. Note that 32 bit applications running on 32 bit target systems are not affected by this limitation. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 69 ++++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/usb/tmc.h | 15 ++++++++++ 2 files changed, 84 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 83ffa5a14c3d..7e69bd05c631 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -5,6 +5,7 @@ * Copyright (C) 2007 Stefan Kopp, Gechingen, Germany * Copyright (C) 2008 Novell, Inc. * Copyright (C) 2008 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (C) 2018 IVI Foundation, Inc. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -36,6 +37,9 @@ /* Default USB timeout (in milliseconds) */ #define USBTMC_TIMEOUT 5000 +/* I/O buffer size used in generic read/write functions */ +#define USBTMC_BUFSIZE (4096) + /* * Maximum number of read cycles to empty bulk in endpoint during CLEAR and * ABORT_BULK_IN requests. Ends the loop if (for whatever reason) a short @@ -1250,6 +1254,67 @@ exit: return rv; } +static int usbtmc_ioctl_request(struct usbtmc_device_data *data, + void __user *arg) +{ + struct device *dev = &data->intf->dev; + struct usbtmc_ctrlrequest request; + u8 *buffer = NULL; + int rv; + unsigned long res; + + res = copy_from_user(&request, arg, sizeof(struct usbtmc_ctrlrequest)); + if (res) + return -EFAULT; + + buffer = kmalloc(request.req.wLength, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (request.req.wLength > USBTMC_BUFSIZE) + return -EMSGSIZE; + + if (request.req.wLength) { + buffer = kmalloc(request.req.wLength, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if ((request.req.bRequestType & USB_DIR_IN) == 0) { + /* Send control data to device */ + res = copy_from_user(buffer, request.data, + request.req.wLength); + if (res) { + rv = -EFAULT; + goto exit; + } + } + } + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + request.req.bRequest, + request.req.bRequestType, + request.req.wValue, + request.req.wIndex, + buffer, request.req.wLength, USB_CTRL_GET_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "%s failed %d\n", __func__, rv); + goto exit; + } + + if (rv && (request.req.bRequestType & USB_DIR_IN)) { + /* Read control data from device */ + res = copy_to_user(request.data, buffer, rv); + if (res) + rv = -EFAULT; + } + + exit: + kfree(buffer); + return rv; +} + /* * Get the usb timeout value */ @@ -1366,6 +1431,10 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) retval = usbtmc_ioctl_abort_bulk_in(data); break; + case USBTMC_IOCTL_CTRL_REQUEST: + retval = usbtmc_ioctl_request(data, (void __user *)arg); + break; + case USBTMC_IOCTL_GET_TIMEOUT: retval = usbtmc_ioctl_get_timeout(file_data, (void __user *)arg); diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 729af2f861a4..5e12928ed1e5 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -4,6 +4,7 @@ * Copyright (C) 2008 Novell, Inc. * Copyright (C) 2008 Greg Kroah-Hartman <gregkh@suse.de> * Copyright (C) 2015 Dave Penkler <dpenkler@gmail.com> + * Copyright (C) 2018 IVI Foundation, Inc. * * This file holds USB constants defined by the USB Device Class * and USB488 Subclass Definitions for Test and Measurement devices @@ -40,6 +41,19 @@ #define USBTMC488_REQUEST_GOTO_LOCAL 161 #define USBTMC488_REQUEST_LOCAL_LOCKOUT 162 +struct usbtmc_request { + __u8 bRequestType; + __u8 bRequest; + __u16 wValue; + __u16 wIndex; + __u16 wLength; +} __attribute__ ((packed)); + +struct usbtmc_ctrlrequest { + struct usbtmc_request req; + void __user *data; /* pointer to user space */ +} __attribute__ ((packed)); + struct usbtmc_termchar { __u8 term_char; __u8 term_char_enabled; @@ -53,6 +67,7 @@ struct usbtmc_termchar { #define USBTMC_IOCTL_ABORT_BULK_IN _IO(USBTMC_IOC_NR, 4) #define USBTMC_IOCTL_CLEAR_OUT_HALT _IO(USBTMC_IOC_NR, 6) #define USBTMC_IOCTL_CLEAR_IN_HALT _IO(USBTMC_IOC_NR, 7) +#define USBTMC_IOCTL_CTRL_REQUEST _IOWR(USBTMC_IOC_NR, 8, struct usbtmc_ctrlrequest) #define USBTMC_IOCTL_GET_TIMEOUT _IOR(USBTMC_IOC_NR, 9, __u32) #define USBTMC_IOCTL_SET_TIMEOUT _IOW(USBTMC_IOC_NR, 10, __u32) #define USBTMC_IOCTL_EOM_ENABLE _IOW(USBTMC_IOC_NR, 11, __u8) -- cgit v1.2.3-58-ga151 From 4ddc645f40e90fa3bc7af3a3f3bd7d29e671a775 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:52 +0200 Subject: usb: usbtmc: Add ioctl for vendor specific write The new ioctl USBTMC_IOCTL_WRITE sends a generic message to bulk OUT. This ioctl is used for vendor specific or asynchronous I/O as well. The message is split into chunks of 4k (page size). Message size is aligned to 32 bit boundaries. With flag USBTMC_FLAG_ASYNC the ioctl is non blocking. With flag USBTMC_FLAG_APPEND additional urbs are queued and out_status/out_transfer_size is not reset. EPOLLOUT | EPOLLWRNORM is signaled when all submitted urbs are completed. Flush flying urbs when file handle is closed or device is suspended or reset. This ioctl does not support compatibility for 32 bit applications running on 64 bit systems. However all other convenient ioctls of the USBTMC driver can still be used in 32 bit applications as well. Note that 32 bit applications running on 32 bit target systems are not affected by this limitation. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 376 ++++++++++++++++++++++++++++++++++++++++++- include/uapi/linux/usb/tmc.h | 14 ++ 2 files changed, 388 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 7e69bd05c631..915c3fefc4e3 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -37,6 +37,8 @@ /* Default USB timeout (in milliseconds) */ #define USBTMC_TIMEOUT 5000 +/* Max number of urbs used in write transfers */ +#define MAX_URBS_IN_FLIGHT 16 /* I/O buffer size used in generic read/write functions */ #define USBTMC_BUFSIZE (4096) @@ -125,13 +127,24 @@ struct usbtmc_file_data { u32 timeout; u8 srq_byte; atomic_t srq_asserted; + u8 eom_val; u8 term_char; bool term_char_enabled; + + spinlock_t err_lock; /* lock for errors */ + + struct usb_anchor submitted; + + /* data for generic_write */ + struct semaphore limit_write_sem; + u32 out_transfer_size; + int out_status; }; /* Forward declarations */ static struct usb_driver usbtmc_driver; +static void usbtmc_draw_down(struct usbtmc_file_data *file_data); static void usbtmc_delete(struct kref *kref) { @@ -157,6 +170,10 @@ static int usbtmc_open(struct inode *inode, struct file *filp) if (!file_data) return -ENOMEM; + spin_lock_init(&file_data->err_lock); + sema_init(&file_data->limit_write_sem, MAX_URBS_IN_FLIGHT); + init_usb_anchor(&file_data->submitted); + data = usb_get_intfdata(intf); /* Protect reference to data from file structure until release */ kref_get(&data->kref); @@ -182,6 +199,36 @@ static int usbtmc_open(struct inode *inode, struct file *filp) return 0; } +/* + * usbtmc_flush - called before file handle is closed + */ +static int usbtmc_flush(struct file *file, fl_owner_t id) +{ + struct usbtmc_file_data *file_data; + struct usbtmc_device_data *data; + + file_data = file->private_data; + if (file_data == NULL) + return -ENODEV; + + data = file_data->data; + + /* wait for io to stop */ + mutex_lock(&data->io_mutex); + + usbtmc_draw_down(file_data); + + spin_lock_irq(&file_data->err_lock); + file_data->out_status = 0; + file_data->out_transfer_size = 0; + spin_unlock_irq(&file_data->err_lock); + + wake_up_interruptible_all(&data->waitq); + mutex_unlock(&data->io_mutex); + + return 0; +} + static int usbtmc_release(struct inode *inode, struct file *file) { struct usbtmc_file_data *file_data = file->private_data; @@ -614,6 +661,238 @@ static int usbtmc488_ioctl_trigger(struct usbtmc_file_data *file_data) return 0; } +static struct urb *usbtmc_create_urb(void) +{ + const size_t bufsize = USBTMC_BUFSIZE; + u8 *dmabuf = NULL; + struct urb *urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!urb) + return NULL; + + dmabuf = kmalloc(bufsize, GFP_KERNEL); + if (!dmabuf) { + usb_free_urb(urb); + return NULL; + } + + urb->transfer_buffer = dmabuf; + urb->transfer_buffer_length = bufsize; + urb->transfer_flags |= URB_FREE_BUFFER; + return urb; +} + +static void usbtmc_write_bulk_cb(struct urb *urb) +{ + struct usbtmc_file_data *file_data = urb->context; + int wakeup = 0; + unsigned long flags; + + spin_lock_irqsave(&file_data->err_lock, flags); + file_data->out_transfer_size += urb->actual_length; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(&file_data->data->intf->dev, + "%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + + if (!file_data->out_status) { + file_data->out_status = urb->status; + wakeup = 1; + } + } + spin_unlock_irqrestore(&file_data->err_lock, flags); + + dev_dbg(&file_data->data->intf->dev, + "%s - write bulk total size: %u\n", + __func__, file_data->out_transfer_size); + + up(&file_data->limit_write_sem); + if (usb_anchor_empty(&file_data->submitted) || wakeup) + wake_up_interruptible(&file_data->data->waitq); +} + +static ssize_t usbtmc_generic_write(struct usbtmc_file_data *file_data, + const void __user *user_buffer, + u32 transfer_size, + u32 *transferred, + u32 flags) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev; + u32 done = 0; + u32 remaining; + unsigned long expire; + const u32 bufsize = USBTMC_BUFSIZE; + struct urb *urb = NULL; + int retval = 0; + u32 timeout; + + *transferred = 0; + + /* Get pointer to private data structure */ + dev = &data->intf->dev; + + dev_dbg(dev, "%s: size=%u flags=0x%X sema=%u\n", + __func__, transfer_size, flags, + file_data->limit_write_sem.count); + + if (flags & USBTMC_FLAG_APPEND) { + spin_lock_irq(&file_data->err_lock); + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + if (retval < 0) + return retval; + } else { + spin_lock_irq(&file_data->err_lock); + file_data->out_transfer_size = 0; + file_data->out_status = 0; + spin_unlock_irq(&file_data->err_lock); + } + + remaining = transfer_size; + if (remaining > INT_MAX) + remaining = INT_MAX; + + timeout = file_data->timeout; + expire = msecs_to_jiffies(timeout); + + while (remaining > 0) { + u32 this_part, aligned; + u8 *buffer = NULL; + + if (flags & USBTMC_FLAG_ASYNC) { + if (down_trylock(&file_data->limit_write_sem)) { + retval = (done)?(0):(-EAGAIN); + goto exit; + } + } else { + retval = down_timeout(&file_data->limit_write_sem, + expire); + if (retval < 0) { + retval = -ETIMEDOUT; + goto error; + } + } + + spin_lock_irq(&file_data->err_lock); + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + if (retval < 0) { + up(&file_data->limit_write_sem); + goto error; + } + + /* prepare next urb to send */ + urb = usbtmc_create_urb(); + if (!urb) { + retval = -ENOMEM; + up(&file_data->limit_write_sem); + goto error; + } + buffer = urb->transfer_buffer; + + if (remaining > bufsize) + this_part = bufsize; + else + this_part = remaining; + + if (copy_from_user(buffer, user_buffer + done, this_part)) { + retval = -EFAULT; + up(&file_data->limit_write_sem); + goto error; + } + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, this_part, true); + + /* fill bulk with 32 bit alignment to meet USBTMC specification + * (size + 3 & ~3) rounds up and simplifies user code + */ + aligned = (this_part + 3) & ~3; + dev_dbg(dev, "write(size:%u align:%u done:%u)\n", + (unsigned int)this_part, + (unsigned int)aligned, + (unsigned int)done); + + usb_fill_bulk_urb(urb, data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out), + urb->transfer_buffer, aligned, + usbtmc_write_bulk_cb, file_data); + + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + up(&file_data->limit_write_sem); + goto error; + } + + usb_free_urb(urb); + urb = NULL; /* urb will be finally released by usb driver */ + + remaining -= this_part; + done += this_part; + } + + /* All urbs are on the fly */ + if (!(flags & USBTMC_FLAG_ASYNC)) { + if (!usb_wait_anchor_empty_timeout(&file_data->submitted, + timeout)) { + retval = -ETIMEDOUT; + goto error; + } + } + + retval = 0; + goto exit; + +error: + usb_kill_anchored_urbs(&file_data->submitted); +exit: + usb_free_urb(urb); + + spin_lock_irq(&file_data->err_lock); + if (!(flags & USBTMC_FLAG_ASYNC)) + done = file_data->out_transfer_size; + if (!retval && file_data->out_status) + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + + *transferred = done; + + dev_dbg(dev, "%s: done=%u, retval=%d, urbstat=%d\n", + __func__, done, retval, file_data->out_status); + + return retval; +} + +static ssize_t usbtmc_ioctl_generic_write(struct usbtmc_file_data *file_data, + void __user *arg) +{ + struct usbtmc_message msg; + ssize_t retval = 0; + + /* mutex already locked */ + + if (copy_from_user(&msg, arg, sizeof(struct usbtmc_message))) + return -EFAULT; + + retval = usbtmc_generic_write(file_data, msg.message, + msg.transfer_size, &msg.transferred, + msg.flags); + + if (put_user(msg.transferred, + &((struct usbtmc_message __user *)arg)->transferred)) + return -EFAULT; + + return retval; +} + /* * Sends a REQUEST_DEV_DEP_MSG_IN message on the Bulk-OUT endpoint. * @transfer_size: number of bytes to request from the device. @@ -1081,6 +1360,15 @@ static int usbtmc_ioctl_clear_in_halt(struct usbtmc_device_data *data) return 0; } +static int usbtmc_ioctl_cancel_io(struct usbtmc_file_data *file_data) +{ + spin_lock_irq(&file_data->err_lock); + file_data->out_status = -ECANCELED; + spin_unlock_irq(&file_data->err_lock); + usb_kill_anchored_urbs(&file_data->submitted); + return 0; +} + static int get_capabilities(struct usbtmc_device_data *data) { struct device *dev = &data->usb_dev->dev; @@ -1455,6 +1743,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) (void __user *)arg); break; + case USBTMC_IOCTL_WRITE: + retval = usbtmc_ioctl_generic_write(file_data, + (void __user *)arg); + break; + case USBTMC488_IOCTL_GET_CAPS: retval = copy_to_user((void __user *)arg, &data->usb488_caps, @@ -1515,7 +1808,19 @@ static __poll_t usbtmc_poll(struct file *file, poll_table *wait) poll_wait(file, &data->waitq, wait); - mask = (atomic_read(&file_data->srq_asserted)) ? EPOLLPRI : 0; + mask = 0; + if (atomic_read(&file_data->srq_asserted)) + mask |= EPOLLPRI; + + if (usb_anchor_empty(&file_data->submitted)) + mask |= (EPOLLOUT | EPOLLWRNORM); + + spin_lock_irq(&file_data->err_lock); + if (file_data->out_status) + mask |= EPOLLERR; + spin_unlock_irq(&file_data->err_lock); + + dev_dbg(&data->intf->dev, "poll mask = %x\n", mask); no_poll: mutex_unlock(&data->io_mutex); @@ -1528,6 +1833,7 @@ static const struct file_operations fops = { .write = usbtmc_write, .open = usbtmc_open, .release = usbtmc_release, + .flush = usbtmc_flush, .unlocked_ioctl = usbtmc_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = usbtmc_ioctl, @@ -1753,6 +2059,7 @@ err_put: static void usbtmc_disconnect(struct usb_interface *intf) { struct usbtmc_device_data *data = usb_get_intfdata(intf); + struct list_head *elem; usb_deregister_dev(intf, &usbtmc_class); sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp); @@ -1760,14 +2067,46 @@ static void usbtmc_disconnect(struct usb_interface *intf) mutex_lock(&data->io_mutex); data->zombie = 1; wake_up_interruptible_all(&data->waitq); + list_for_each(elem, &data->file_list) { + struct usbtmc_file_data *file_data; + + file_data = list_entry(elem, + struct usbtmc_file_data, + file_elem); + usb_kill_anchored_urbs(&file_data->submitted); + } mutex_unlock(&data->io_mutex); usbtmc_free_int(data); kref_put(&data->kref, usbtmc_delete); } +static void usbtmc_draw_down(struct usbtmc_file_data *file_data) +{ + int time; + + time = usb_wait_anchor_empty_timeout(&file_data->submitted, 1000); + if (!time) + usb_kill_anchored_urbs(&file_data->submitted); +} + static int usbtmc_suspend(struct usb_interface *intf, pm_message_t message) { - /* this driver does not have pending URBs */ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + struct list_head *elem; + + if (!data) + return 0; + + mutex_lock(&data->io_mutex); + list_for_each(elem, &data->file_list) { + struct usbtmc_file_data *file_data; + + file_data = list_entry(elem, + struct usbtmc_file_data, + file_elem); + usbtmc_draw_down(file_data); + } + mutex_unlock(&data->io_mutex); return 0; } @@ -1776,6 +2115,37 @@ static int usbtmc_resume(struct usb_interface *intf) return 0; } +static int usbtmc_pre_reset(struct usb_interface *intf) +{ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + struct list_head *elem; + + if (!data) + return 0; + + mutex_lock(&data->io_mutex); + + list_for_each(elem, &data->file_list) { + struct usbtmc_file_data *file_data; + + file_data = list_entry(elem, + struct usbtmc_file_data, + file_elem); + usbtmc_ioctl_cancel_io(file_data); + } + + return 0; +} + +static int usbtmc_post_reset(struct usb_interface *intf) +{ + struct usbtmc_device_data *data = usb_get_intfdata(intf); + + mutex_unlock(&data->io_mutex); + + return 0; +} + static struct usb_driver usbtmc_driver = { .name = "usbtmc", .id_table = usbtmc_devices, @@ -1783,6 +2153,8 @@ static struct usb_driver usbtmc_driver = { .disconnect = usbtmc_disconnect, .suspend = usbtmc_suspend, .resume = usbtmc_resume, + .pre_reset = usbtmc_pre_reset, + .post_reset = usbtmc_post_reset, }; module_usb_driver(usbtmc_driver); diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 5e12928ed1e5..44dc88f3479d 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -59,6 +59,19 @@ struct usbtmc_termchar { __u8 term_char_enabled; } __attribute__ ((packed)); +/* + * usbtmc_message->flags: + */ +#define USBTMC_FLAG_ASYNC 0x0001 +#define USBTMC_FLAG_APPEND 0x0002 + +struct usbtmc_message { + __u32 transfer_size; /* size of bytes to transfer */ + __u32 transferred; /* size of received/written bytes */ + __u32 flags; /* bit 0: 0 = synchronous; 1 = asynchronous */ + void __user *message; /* pointer to header and data in user space */ +} __attribute__ ((packed)); + /* Request values for USBTMC driver's ioctl entry point */ #define USBTMC_IOC_NR 91 #define USBTMC_IOCTL_INDICATOR_PULSE _IO(USBTMC_IOC_NR, 1) @@ -72,6 +85,7 @@ struct usbtmc_termchar { #define USBTMC_IOCTL_SET_TIMEOUT _IOW(USBTMC_IOC_NR, 10, __u32) #define USBTMC_IOCTL_EOM_ENABLE _IOW(USBTMC_IOC_NR, 11, __u8) #define USBTMC_IOCTL_CONFIG_TERMCHAR _IOW(USBTMC_IOC_NR, 12, struct usbtmc_termchar) +#define USBTMC_IOCTL_WRITE _IOWR(USBTMC_IOC_NR, 13, struct usbtmc_message) #define USBTMC488_IOCTL_GET_CAPS _IOR(USBTMC_IOC_NR, 17, unsigned char) #define USBTMC488_IOCTL_READ_STB _IOR(USBTMC_IOC_NR, 18, unsigned char) -- cgit v1.2.3-58-ga151 From b14984518ee60ef7662aa6520b76ae6046e08857 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:53 +0200 Subject: usb: usbtmc: Add ioctl USBTMC_IOCTL_WRITE_RESULT ioctl USBTMC_IOCTL_WRITE_RESULT copies current out_transfer_size to given __u32 pointer and returns current out_status of the last (asnynchronous) USBTMC_IOCTL_WRITE call. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 25 +++++++++++++++++++++++++ include/uapi/linux/usb/tmc.h | 1 + 2 files changed, 26 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 915c3fefc4e3..eec382ab1a44 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -893,6 +893,26 @@ static ssize_t usbtmc_ioctl_generic_write(struct usbtmc_file_data *file_data, return retval; } +/* + * Get the generic write result + */ +static ssize_t usbtmc_ioctl_write_result(struct usbtmc_file_data *file_data, + void __user *arg) +{ + u32 transferred; + int retval; + + spin_lock_irq(&file_data->err_lock); + transferred = file_data->out_transfer_size; + retval = file_data->out_status; + spin_unlock_irq(&file_data->err_lock); + + if (put_user(transferred, (__u32 __user *)arg)) + return -EFAULT; + + return retval; +} + /* * Sends a REQUEST_DEV_DEP_MSG_IN message on the Bulk-OUT endpoint. * @transfer_size: number of bytes to request from the device. @@ -1748,6 +1768,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) (void __user *)arg); break; + case USBTMC_IOCTL_WRITE_RESULT: + retval = usbtmc_ioctl_write_result(file_data, + (void __user *)arg); + break; + case USBTMC488_IOCTL_GET_CAPS: retval = copy_to_user((void __user *)arg, &data->usb488_caps, diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 44dc88f3479d..0166ba5452d5 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -86,6 +86,7 @@ struct usbtmc_message { #define USBTMC_IOCTL_EOM_ENABLE _IOW(USBTMC_IOC_NR, 11, __u8) #define USBTMC_IOCTL_CONFIG_TERMCHAR _IOW(USBTMC_IOC_NR, 12, struct usbtmc_termchar) #define USBTMC_IOCTL_WRITE _IOWR(USBTMC_IOC_NR, 13, struct usbtmc_message) +#define USBTMC_IOCTL_WRITE_RESULT _IOWR(USBTMC_IOC_NR, 15, __u32) #define USBTMC488_IOCTL_GET_CAPS _IOR(USBTMC_IOC_NR, 17, unsigned char) #define USBTMC488_IOCTL_READ_STB _IOR(USBTMC_IOC_NR, 18, unsigned char) -- cgit v1.2.3-58-ga151 From bb99794a4792068cb4bfd40e99e0f9d8fe7872fa Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:54 +0200 Subject: usb: usbtmc: Add ioctl for vendor specific read The USBTMC_IOCTL_READ call provides for generic synchronous and asynchronous reads on bulk IN to implement vendor specific library routines. Depending on transfer_size the function submits one or more urbs (up to 16) each with a size of up to 4kB. The flag USBTMC_FLAG_IGNORE_TRAILER can be used when the transmission size is already known. Then the function does not truncate the transfer_size to a multiple of 4 kB, but does reserve extra space to receive the final short or zero length packet. Note that the instrument is allowed to send up to wMaxPacketSize - 1 bytes at the end of a message to avoid sending a zero length packet. With flag USBTMC_FLAG_ASYNC the ioctl is non blocking. When no received data is available, the read function submits as many urbs as needed to receive transfer_size bytes. However the number of flying urbs (=4kB) is limited to 16 even with subsequent calls of this ioctl. Returns -EAGAIN when non blocking and no data is received. Signals EPOLLIN | EPOLLRDNORM when asynchronous urbs are ready to be read. In non blocking mode the usbtmc_message.message pointer may be NULL and the ioctl just submits urbs to initiate receiving data. However if data is already available due to a previous non blocking call the ioctl will return -EINVAL when the message pointer is NULL. This ioctl does not support compatibility for 32 bit applications running on 64 bit systems. However all other convenient ioctls of the USBTMC driver can still be used in 32 bit applications as well. Note that 32 bit applications running on 32 bit target systems are not affected by this limitation. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 336 ++++++++++++++++++++++++++++++++++++++++++- include/uapi/linux/usb/tmc.h | 2 + 2 files changed, 337 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index eec382ab1a44..45ccdd087d6f 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -85,6 +85,9 @@ struct usbtmc_device_data { u8 bTag_last_write; /* needed for abort */ u8 bTag_last_read; /* needed for abort */ + /* packet size of IN bulk */ + u16 wMaxPacketSize; + /* data for interrupt in endpoint handling */ u8 bNotify1; u8 bNotify2; @@ -140,6 +143,13 @@ struct usbtmc_file_data { struct semaphore limit_write_sem; u32 out_transfer_size; int out_status; + + /* data for generic_read */ + u32 in_transfer_size; + int in_status; + int in_urbs_used; + struct usb_anchor in_anchor; + wait_queue_head_t wait_bulk_in; }; /* Forward declarations */ @@ -173,6 +183,8 @@ static int usbtmc_open(struct inode *inode, struct file *filp) spin_lock_init(&file_data->err_lock); sema_init(&file_data->limit_write_sem, MAX_URBS_IN_FLIGHT); init_usb_anchor(&file_data->submitted); + init_usb_anchor(&file_data->in_anchor); + init_waitqueue_head(&file_data->wait_bulk_in); data = usb_get_intfdata(intf); /* Protect reference to data from file structure until release */ @@ -219,6 +231,9 @@ static int usbtmc_flush(struct file *file, fl_owner_t id) usbtmc_draw_down(file_data); spin_lock_irq(&file_data->err_lock); + file_data->in_status = 0; + file_data->in_transfer_size = 0; + file_data->in_urbs_used = 0; file_data->out_status = 0; file_data->out_transfer_size = 0; spin_unlock_irq(&file_data->err_lock); @@ -682,6 +697,307 @@ static struct urb *usbtmc_create_urb(void) return urb; } +static void usbtmc_read_bulk_cb(struct urb *urb) +{ + struct usbtmc_file_data *file_data = urb->context; + int status = urb->status; + unsigned long flags; + + /* sync/async unlink faults aren't errors */ + if (status) { + if (!(/* status == -ENOENT || */ + status == -ECONNRESET || + status == -EREMOTEIO || /* Short packet */ + status == -ESHUTDOWN)) + dev_err(&file_data->data->intf->dev, + "%s - nonzero read bulk status received: %d\n", + __func__, status); + + spin_lock_irqsave(&file_data->err_lock, flags); + if (!file_data->in_status) + file_data->in_status = status; + spin_unlock_irqrestore(&file_data->err_lock, flags); + } + + spin_lock_irqsave(&file_data->err_lock, flags); + file_data->in_transfer_size += urb->actual_length; + dev_dbg(&file_data->data->intf->dev, + "%s - total size: %u current: %d status: %d\n", + __func__, file_data->in_transfer_size, + urb->actual_length, status); + spin_unlock_irqrestore(&file_data->err_lock, flags); + usb_anchor_urb(urb, &file_data->in_anchor); + + wake_up_interruptible(&file_data->wait_bulk_in); + wake_up_interruptible(&file_data->data->waitq); +} + +static inline bool usbtmc_do_transfer(struct usbtmc_file_data *file_data) +{ + bool data_or_error; + + spin_lock_irq(&file_data->err_lock); + data_or_error = !usb_anchor_empty(&file_data->in_anchor) + || file_data->in_status; + spin_unlock_irq(&file_data->err_lock); + dev_dbg(&file_data->data->intf->dev, "%s: returns %d\n", __func__, + data_or_error); + return data_or_error; +} + +static ssize_t usbtmc_generic_read(struct usbtmc_file_data *file_data, + void __user *user_buffer, + u32 transfer_size, + u32 *transferred, + u32 flags) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev = &data->intf->dev; + u32 done = 0; + u32 remaining; + const u32 bufsize = USBTMC_BUFSIZE; + int retval = 0; + u32 max_transfer_size; + unsigned long expire; + int bufcount = 1; + int again = 0; + + /* mutex already locked */ + + *transferred = done; + + max_transfer_size = transfer_size; + + if (flags & USBTMC_FLAG_IGNORE_TRAILER) { + /* The device may send extra alignment bytes (up to + * wMaxPacketSize – 1) to avoid sending a zero-length + * packet + */ + remaining = transfer_size; + if ((max_transfer_size % data->wMaxPacketSize) == 0) + max_transfer_size += (data->wMaxPacketSize - 1); + } else { + /* round down to bufsize to avoid truncated data left */ + if (max_transfer_size > bufsize) { + max_transfer_size = + roundup(max_transfer_size + 1 - bufsize, + bufsize); + } + remaining = max_transfer_size; + } + + spin_lock_irq(&file_data->err_lock); + + if (file_data->in_status) { + /* return the very first error */ + retval = file_data->in_status; + spin_unlock_irq(&file_data->err_lock); + goto error; + } + + if (flags & USBTMC_FLAG_ASYNC) { + if (usb_anchor_empty(&file_data->in_anchor)) + again = 1; + + if (file_data->in_urbs_used == 0) { + file_data->in_transfer_size = 0; + file_data->in_status = 0; + } + } else { + file_data->in_transfer_size = 0; + file_data->in_status = 0; + } + + if (max_transfer_size == 0) { + bufcount = 0; + } else { + bufcount = roundup(max_transfer_size, bufsize) / bufsize; + if (bufcount > file_data->in_urbs_used) + bufcount -= file_data->in_urbs_used; + else + bufcount = 0; + + if (bufcount + file_data->in_urbs_used > MAX_URBS_IN_FLIGHT) { + bufcount = MAX_URBS_IN_FLIGHT - + file_data->in_urbs_used; + } + } + spin_unlock_irq(&file_data->err_lock); + + dev_dbg(dev, "%s: requested=%u flags=0x%X size=%u bufs=%d used=%d\n", + __func__, transfer_size, flags, + max_transfer_size, bufcount, file_data->in_urbs_used); + + while (bufcount > 0) { + u8 *dmabuf = NULL; + struct urb *urb = usbtmc_create_urb(); + + if (!urb) { + retval = -ENOMEM; + goto error; + } + + dmabuf = urb->transfer_buffer; + + usb_fill_bulk_urb(urb, data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, data->bulk_in), + dmabuf, bufsize, + usbtmc_read_bulk_cb, file_data); + + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + /* urb is anchored. We can release our reference. */ + usb_free_urb(urb); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + goto error; + } + file_data->in_urbs_used++; + bufcount--; + } + + if (again) { + dev_dbg(dev, "%s: ret=again\n", __func__); + return -EAGAIN; + } + + if (user_buffer == NULL) + return -EINVAL; + + expire = msecs_to_jiffies(file_data->timeout); + + while (max_transfer_size > 0) { + u32 this_part; + struct urb *urb = NULL; + + if (!(flags & USBTMC_FLAG_ASYNC)) { + dev_dbg(dev, "%s: before wait time %lu\n", + __func__, expire); + retval = wait_event_interruptible_timeout( + file_data->wait_bulk_in, + usbtmc_do_transfer(file_data), + expire); + + dev_dbg(dev, "%s: wait returned %d\n", + __func__, retval); + + if (retval <= 0) { + if (retval == 0) + retval = -ETIMEDOUT; + goto error; + } + } + + urb = usb_get_from_anchor(&file_data->in_anchor); + if (!urb) { + if (!(flags & USBTMC_FLAG_ASYNC)) { + /* synchronous case: must not happen */ + retval = -EFAULT; + goto error; + } + + /* asynchronous case: ready, do not block or wait */ + *transferred = done; + dev_dbg(dev, "%s: (async) done=%u ret=0\n", + __func__, done); + return 0; + } + + file_data->in_urbs_used--; + + if (max_transfer_size > urb->actual_length) + max_transfer_size -= urb->actual_length; + else + max_transfer_size = 0; + + if (remaining > urb->actual_length) + this_part = urb->actual_length; + else + this_part = remaining; + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, 16, 1, + urb->transfer_buffer, urb->actual_length, true); + + if (copy_to_user(user_buffer + done, + urb->transfer_buffer, this_part)) { + usb_free_urb(urb); + retval = -EFAULT; + goto error; + } + + remaining -= this_part; + done += this_part; + + spin_lock_irq(&file_data->err_lock); + if (urb->status) { + /* return the very first error */ + retval = file_data->in_status; + spin_unlock_irq(&file_data->err_lock); + usb_free_urb(urb); + goto error; + } + spin_unlock_irq(&file_data->err_lock); + + if (urb->actual_length < bufsize) { + /* short packet or ZLP received => ready */ + usb_free_urb(urb); + retval = 1; + break; + } + + if (!(flags & USBTMC_FLAG_ASYNC) && + max_transfer_size > (bufsize * file_data->in_urbs_used)) { + /* resubmit, since other buffers still not enough */ + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + usb_free_urb(urb); + goto error; + } + file_data->in_urbs_used++; + } + usb_free_urb(urb); + retval = 0; + } + +error: + *transferred = done; + + dev_dbg(dev, "%s: before kill\n", __func__); + /* Attention: killing urbs can take long time (2 ms) */ + usb_kill_anchored_urbs(&file_data->submitted); + dev_dbg(dev, "%s: after kill\n", __func__); + usb_scuttle_anchored_urbs(&file_data->in_anchor); + file_data->in_urbs_used = 0; + file_data->in_status = 0; /* no spinlock needed here */ + dev_dbg(dev, "%s: done=%u ret=%d\n", __func__, done, retval); + + return retval; +} + +static ssize_t usbtmc_ioctl_generic_read(struct usbtmc_file_data *file_data, + void __user *arg) +{ + struct usbtmc_message msg; + ssize_t retval = 0; + + /* mutex already locked */ + + if (copy_from_user(&msg, arg, sizeof(struct usbtmc_message))) + return -EFAULT; + + retval = usbtmc_generic_read(file_data, msg.message, + msg.transfer_size, &msg.transferred, + msg.flags); + + if (put_user(msg.transferred, + &((struct usbtmc_message __user *)arg)->transferred)) + return -EFAULT; + + return retval; +} + static void usbtmc_write_bulk_cb(struct urb *urb) { struct usbtmc_file_data *file_data = urb->context; @@ -1383,6 +1699,7 @@ static int usbtmc_ioctl_clear_in_halt(struct usbtmc_device_data *data) static int usbtmc_ioctl_cancel_io(struct usbtmc_file_data *file_data) { spin_lock_irq(&file_data->err_lock); + file_data->in_status = -ECANCELED; file_data->out_status = -ECANCELED; spin_unlock_irq(&file_data->err_lock); usb_kill_anchored_urbs(&file_data->submitted); @@ -1768,6 +2085,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) (void __user *)arg); break; + case USBTMC_IOCTL_READ: + retval = usbtmc_ioctl_generic_read(file_data, + (void __user *)arg); + break; + case USBTMC_IOCTL_WRITE_RESULT: retval = usbtmc_ioctl_write_result(file_data, (void __user *)arg); @@ -1833,15 +2155,24 @@ static __poll_t usbtmc_poll(struct file *file, poll_table *wait) poll_wait(file, &data->waitq, wait); + /* Note that EPOLLPRI is now assigned to SRQ, and + * EPOLLIN|EPOLLRDNORM to normal read data. + */ mask = 0; if (atomic_read(&file_data->srq_asserted)) mask |= EPOLLPRI; + /* Note that the anchor submitted includes all urbs for BULK IN + * and OUT. So EPOLLOUT is signaled when BULK OUT is empty and + * all BULK IN urbs are completed and moved to in_anchor. + */ if (usb_anchor_empty(&file_data->submitted)) mask |= (EPOLLOUT | EPOLLWRNORM); + if (!usb_anchor_empty(&file_data->in_anchor)) + mask |= (EPOLLIN | EPOLLRDNORM); spin_lock_irq(&file_data->err_lock); - if (file_data->out_status) + if (file_data->in_status || file_data->out_status) mask |= EPOLLERR; spin_unlock_irq(&file_data->err_lock); @@ -2003,6 +2334,7 @@ static int usbtmc_probe(struct usb_interface *intf, } data->bulk_in = bulk_in->bEndpointAddress; + data->wMaxPacketSize = usb_endpoint_maxp(bulk_in); dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n", data->bulk_in); data->bulk_out = bulk_out->bEndpointAddress; @@ -2099,6 +2431,7 @@ static void usbtmc_disconnect(struct usb_interface *intf) struct usbtmc_file_data, file_elem); usb_kill_anchored_urbs(&file_data->submitted); + usb_scuttle_anchored_urbs(&file_data->in_anchor); } mutex_unlock(&data->io_mutex); usbtmc_free_int(data); @@ -2112,6 +2445,7 @@ static void usbtmc_draw_down(struct usbtmc_file_data *file_data) time = usb_wait_anchor_empty_timeout(&file_data->submitted, 1000); if (!time) usb_kill_anchored_urbs(&file_data->submitted); + usb_scuttle_anchored_urbs(&file_data->in_anchor); } static int usbtmc_suspend(struct usb_interface *intf, pm_message_t message) diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 0166ba5452d5..f0fd0d4334ec 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -64,6 +64,7 @@ struct usbtmc_termchar { */ #define USBTMC_FLAG_ASYNC 0x0001 #define USBTMC_FLAG_APPEND 0x0002 +#define USBTMC_FLAG_IGNORE_TRAILER 0x0004 struct usbtmc_message { __u32 transfer_size; /* size of bytes to transfer */ @@ -86,6 +87,7 @@ struct usbtmc_message { #define USBTMC_IOCTL_EOM_ENABLE _IOW(USBTMC_IOC_NR, 11, __u8) #define USBTMC_IOCTL_CONFIG_TERMCHAR _IOW(USBTMC_IOC_NR, 12, struct usbtmc_termchar) #define USBTMC_IOCTL_WRITE _IOWR(USBTMC_IOC_NR, 13, struct usbtmc_message) +#define USBTMC_IOCTL_READ _IOWR(USBTMC_IOC_NR, 14, struct usbtmc_message) #define USBTMC_IOCTL_WRITE_RESULT _IOWR(USBTMC_IOC_NR, 15, __u32) #define USBTMC488_IOCTL_GET_CAPS _IOR(USBTMC_IOC_NR, 17, unsigned char) -- cgit v1.2.3-58-ga151 From 46ecc9d54efc11bf99689901f867854d264cbc0b Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:55 +0200 Subject: usb: usbtmc: Add ioctl USBTMC_IOCTL_CANCEL_IO ioctl USBTMC_IOCTL_CANCEL_IO stops and kills all flying urbs of last USBTMC_IOCTL_READ and USBTMC_IOCTL_WRITE function calls. A subsequent call to USBTMC_IOCTL_READ or USBTMC_IOCTL_WRITE_RESULT returns -ECANCELED with information about current transferred data. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 4 ++++ include/uapi/linux/usb/tmc.h | 3 +++ 2 files changed, 7 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 45ccdd087d6f..0d8aa4bc3fa7 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -2126,6 +2126,10 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case USBTMC488_IOCTL_TRIGGER: retval = usbtmc488_ioctl_trigger(file_data); break; + + case USBTMC_IOCTL_CANCEL_IO: + retval = usbtmc_ioctl_cancel_io(file_data); + break; } skip_io_on_zombie: diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index f0fd0d4334ec..42e275d1d385 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -97,6 +97,9 @@ struct usbtmc_message { #define USBTMC488_IOCTL_LOCAL_LOCKOUT _IO(USBTMC_IOC_NR, 21) #define USBTMC488_IOCTL_TRIGGER _IO(USBTMC_IOC_NR, 22) +/* Cancel and cleanup asynchronous calls */ +#define USBTMC_IOCTL_CANCEL_IO _IO(USBTMC_IOC_NR, 35) + /* Driver encoded usb488 capabilities */ #define USBTMC488_CAPABILITY_TRIGGER 1 #define USBTMC488_CAPABILITY_SIMPLE 2 -- cgit v1.2.3-58-ga151 From 987b81998b41563113f714009e7e748e1211026d Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:56 +0200 Subject: usb: usbtmc: Add ioctl USBTMC_IOCTL_CLEANUP_IO The ioctl USBTMC_IOCTL_CLEANUP_IO kills all submitted urbs to OUT and IN bulk, and clears all received data from IN bulk. Internal transfer counters and error states are reset. An application should use this ioctl after an asnychronous transfer was canceled and/or error handling has finished. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 19 +++++++++++++++++++ include/uapi/linux/usb/tmc.h | 1 + 2 files changed, 20 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 0d8aa4bc3fa7..dc6c04fdfdff 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1706,6 +1706,21 @@ static int usbtmc_ioctl_cancel_io(struct usbtmc_file_data *file_data) return 0; } +static int usbtmc_ioctl_cleanup_io(struct usbtmc_file_data *file_data) +{ + usb_kill_anchored_urbs(&file_data->submitted); + usb_scuttle_anchored_urbs(&file_data->in_anchor); + spin_lock_irq(&file_data->err_lock); + file_data->in_status = 0; + file_data->in_transfer_size = 0; + file_data->out_status = 0; + file_data->out_transfer_size = 0; + spin_unlock_irq(&file_data->err_lock); + + file_data->in_urbs_used = 0; + return 0; +} + static int get_capabilities(struct usbtmc_device_data *data) { struct device *dev = &data->usb_dev->dev; @@ -2130,6 +2145,10 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case USBTMC_IOCTL_CANCEL_IO: retval = usbtmc_ioctl_cancel_io(file_data); break; + + case USBTMC_IOCTL_CLEANUP_IO: + retval = usbtmc_ioctl_cleanup_io(file_data); + break; } skip_io_on_zombie: diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 42e275d1d385..5a69d9dc967d 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -99,6 +99,7 @@ struct usbtmc_message { /* Cancel and cleanup asynchronous calls */ #define USBTMC_IOCTL_CANCEL_IO _IO(USBTMC_IOC_NR, 35) +#define USBTMC_IOCTL_CLEANUP_IO _IO(USBTMC_IOC_NR, 36) /* Driver encoded usb488 capabilities */ #define USBTMC488_CAPABILITY_TRIGGER 1 -- cgit v1.2.3-58-ga151 From b19bbdc5f45171295defbfa2a1846a2776b942bc Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:57 +0200 Subject: usb: usbtmc: Fix suspend/resume Submitted urbs are not allowed when system is suspended. Thus the submitted urb waiting at interrupt pipe is killed during suspend callback and submitted again when system resumes. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index dc6c04fdfdff..e4c80b44b55a 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -2306,7 +2306,9 @@ static void usbtmc_free_int(struct usbtmc_device_data *data) return; usb_kill_urb(data->iin_urb); kfree(data->iin_buffer); + data->iin_buffer = NULL; usb_free_urb(data->iin_urb); + data->iin_urb = NULL; kref_put(&data->kref, usbtmc_delete); } @@ -2488,13 +2490,25 @@ static int usbtmc_suspend(struct usb_interface *intf, pm_message_t message) file_elem); usbtmc_draw_down(file_data); } + + if (data->iin_ep_present && data->iin_urb) + usb_kill_urb(data->iin_urb); + mutex_unlock(&data->io_mutex); return 0; } static int usbtmc_resume(struct usb_interface *intf) { - return 0; + struct usbtmc_device_data *data = usb_get_intfdata(intf); + int retcode = 0; + + if (data->iin_ep_present && data->iin_urb) + retcode = usb_submit_urb(data->iin_urb, GFP_KERNEL); + if (retcode) + dev_err(&intf->dev, "Failed to submit iin_urb\n"); + + return retcode; } static int usbtmc_pre_reset(struct usb_interface *intf) -- cgit v1.2.3-58-ga151 From 739240a9f6ac4d4c841081029874b3521744e490 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:58 +0200 Subject: usb: usbtmc: Add ioctl USBTMC488_IOCTL_WAIT_SRQ Wait until an SRQ (service request) is received on the interrupt pipe or until the given period of time is expired. In contrast to the poll() function this ioctl does not return when other (a)synchronous I/O operations fail with EPOLLERR. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 57 ++++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/usb/tmc.h | 1 + 2 files changed, 58 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index e4c80b44b55a..e177bac777f4 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -130,6 +130,7 @@ struct usbtmc_file_data { u32 timeout; u8 srq_byte; atomic_t srq_asserted; + atomic_t closing; u8 eom_val; u8 term_char; @@ -193,6 +194,8 @@ static int usbtmc_open(struct inode *inode, struct file *filp) mutex_lock(&data->io_mutex); file_data->data = data; + atomic_set(&file_data->closing, 0); + /* copy default values from device settings */ file_data->timeout = USBTMC_TIMEOUT; file_data->term_char = data->TermChar; @@ -223,6 +226,7 @@ static int usbtmc_flush(struct file *file, fl_owner_t id) if (file_data == NULL) return -ENODEV; + atomic_set(&file_data->closing, 1); data = file_data->data; /* wait for io to stop */ @@ -576,6 +580,54 @@ static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, return rv; } +static int usbtmc488_ioctl_wait_srq(struct usbtmc_file_data *file_data, + __u32 __user *arg) +{ + struct usbtmc_device_data *data = file_data->data; + struct device *dev = &data->intf->dev; + int rv; + u32 timeout; + unsigned long expire; + + if (!data->iin_ep_present) { + dev_dbg(dev, "no interrupt endpoint present\n"); + return -EFAULT; + } + + if (get_user(timeout, arg)) + return -EFAULT; + + expire = msecs_to_jiffies(timeout); + + mutex_unlock(&data->io_mutex); + + rv = wait_event_interruptible_timeout( + data->waitq, + atomic_read(&file_data->srq_asserted) != 0 || + atomic_read(&file_data->closing), + expire); + + mutex_lock(&data->io_mutex); + + /* Note! disconnect or close could be called in the meantime */ + if (atomic_read(&file_data->closing) || data->zombie) + rv = -ENODEV; + + if (rv < 0) { + /* dev can be invalid now! */ + pr_debug("%s - wait interrupted %d\n", __func__, rv); + return rv; + } + + if (rv == 0) { + dev_dbg(dev, "%s - wait timed out\n", __func__); + return -ETIMEDOUT; + } + + dev_dbg(dev, "%s - srq asserted\n", __func__); + return 0; +} + static int usbtmc488_ioctl_simple(struct usbtmc_device_data *data, void __user *arg, unsigned int cmd) { @@ -2142,6 +2194,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) retval = usbtmc488_ioctl_trigger(file_data); break; + case USBTMC488_IOCTL_WAIT_SRQ: + retval = usbtmc488_ioctl_wait_srq(file_data, + (__u32 __user *)arg); + break; + case USBTMC_IOCTL_CANCEL_IO: retval = usbtmc_ioctl_cancel_io(file_data); break; diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 5a69d9dc967d..e228ad7fc141 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -96,6 +96,7 @@ struct usbtmc_message { #define USBTMC488_IOCTL_GOTO_LOCAL _IO(USBTMC_IOC_NR, 20) #define USBTMC488_IOCTL_LOCAL_LOCKOUT _IO(USBTMC_IOC_NR, 21) #define USBTMC488_IOCTL_TRIGGER _IO(USBTMC_IOC_NR, 22) +#define USBTMC488_IOCTL_WAIT_SRQ _IOW(USBTMC_IOC_NR, 23, __u32) /* Cancel and cleanup asynchronous calls */ #define USBTMC_IOCTL_CANCEL_IO _IO(USBTMC_IOC_NR, 35) -- cgit v1.2.3-58-ga151 From 8409e96f012a777ad9ca2050d567d766e43ec343 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:50:59 +0200 Subject: usb: usbtmc: add ioctl USBTMC_IOCTL_MSG_IN_ATTR add ioctl USBTMC_IOCTL_MSG_IN_ATTR that returns the specific bmTransferAttributes field of the last DEV_DEP_MSG_IN Bulk-IN header. This header is received by the read() function. The meaning of the (u8) bitmap bmTransferAttributes is: Bit 0 = EOM flag is set when the last transfer of a USBTMC message is received. Bit 1 = is set when the last byte is a termchar (e.g. '\n'). Note that this bit is always zero when the device does not support the termchar feature or when termchar detection is not enabled (see ioctl USBTMC_IOCTL_CONFIG_TERMCHAR). Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 8 ++++++++ include/uapi/linux/usb/tmc.h | 2 ++ 2 files changed, 10 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index e177bac777f4..4cda74e9e11b 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -131,6 +131,7 @@ struct usbtmc_file_data { u8 srq_byte; atomic_t srq_asserted; atomic_t closing; + u8 bmTransferAttributes; /* member of DEV_DEP_MSG_IN */ u8 eom_val; u8 term_char; @@ -1435,6 +1436,8 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, (buffer[6] << 16) + (buffer[7] << 24); + file_data->bmTransferAttributes = buffer[8]; + if (n_characters > this_part) { dev_err(dev, "Device wants to return more data than requested: %u > %zu\n", n_characters, count); if (data->auto_abort) @@ -2199,6 +2202,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) (__u32 __user *)arg); break; + case USBTMC_IOCTL_MSG_IN_ATTR: + retval = put_user(file_data->bmTransferAttributes, + (__u8 __user *)arg); + break; + case USBTMC_IOCTL_CANCEL_IO: retval = usbtmc_ioctl_cancel_io(file_data); break; diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index e228ad7fc141..55ca365b66d4 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -98,6 +98,8 @@ struct usbtmc_message { #define USBTMC488_IOCTL_TRIGGER _IO(USBTMC_IOC_NR, 22) #define USBTMC488_IOCTL_WAIT_SRQ _IOW(USBTMC_IOC_NR, 23, __u32) +#define USBTMC_IOCTL_MSG_IN_ATTR _IOR(USBTMC_IOC_NR, 24, __u8) + /* Cancel and cleanup asynchronous calls */ #define USBTMC_IOCTL_CANCEL_IO _IO(USBTMC_IOC_NR, 35) #define USBTMC_IOCTL_CLEANUP_IO _IO(USBTMC_IOC_NR, 36) -- cgit v1.2.3-58-ga151 From ec34d08eff71b6cc69bacd70906cf9ff0d8c87a4 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:00 +0200 Subject: usb: usbtmc: Add ioctl USBTMC_IOCTL_AUTO_ABORT Add ioctl USBTMC_IOCTL_AUTO_ABORT to configure auto_abort for each specific file handle. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 23 ++++++++++++++++------- include/uapi/linux/usb/tmc.h | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 4cda74e9e11b..3ed2146fb670 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -136,6 +136,7 @@ struct usbtmc_file_data { u8 eom_val; u8 term_char; bool term_char_enabled; + bool auto_abort; spinlock_t err_lock; /* lock for errors */ @@ -201,6 +202,7 @@ static int usbtmc_open(struct inode *inode, struct file *filp) file_data->timeout = USBTMC_TIMEOUT; file_data->term_char = data->TermChar; file_data->term_char_enabled = data->TermCharEnabled; + file_data->auto_abort = data->auto_abort; file_data->eom_val = 1; INIT_LIST_HEAD(&file_data->file_elem); @@ -1376,7 +1378,7 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, retval = send_request_dev_dep_msg_in(file_data, count); if (retval < 0) { - if (data->auto_abort) + if (file_data->auto_abort) usbtmc_ioctl_abort_bulk_out(data); goto exit; } @@ -1401,7 +1403,7 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, if (retval < 0) { dev_dbg(dev, "Unable to read data, error %d\n", retval); - if (data->auto_abort) + if (file_data->auto_abort) usbtmc_ioctl_abort_bulk_in(data); goto exit; } @@ -1411,21 +1413,21 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, /* Sanity checks for the header */ if (actual < USBTMC_HEADER_SIZE) { dev_err(dev, "Device sent too small first packet: %u < %u\n", actual, USBTMC_HEADER_SIZE); - if (data->auto_abort) + if (file_data->auto_abort) usbtmc_ioctl_abort_bulk_in(data); goto exit; } if (buffer[0] != 2) { dev_err(dev, "Device sent reply with wrong MsgID: %u != 2\n", buffer[0]); - if (data->auto_abort) + if (file_data->auto_abort) usbtmc_ioctl_abort_bulk_in(data); goto exit; } if (buffer[1] != data->bTag_last_write) { dev_err(dev, "Device sent reply with wrong bTag: %u != %u\n", buffer[1], data->bTag_last_write); - if (data->auto_abort) + if (file_data->auto_abort) usbtmc_ioctl_abort_bulk_in(data); goto exit; } @@ -1440,7 +1442,7 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, if (n_characters > this_part) { dev_err(dev, "Device wants to return more data than requested: %u > %zu\n", n_characters, count); - if (data->auto_abort) + if (file_data->auto_abort) usbtmc_ioctl_abort_bulk_in(data); goto exit; } @@ -1582,7 +1584,7 @@ static ssize_t usbtmc_write(struct file *filp, const char __user *buf, if (retval < 0) { dev_err(&data->intf->dev, "Unable to send data, error %d\n", retval); - if (data->auto_abort) + if (file_data->auto_abort) usbtmc_ioctl_abort_bulk_out(data); goto exit; } @@ -2091,6 +2093,7 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) struct usbtmc_file_data *file_data; struct usbtmc_device_data *data; int retval = -EBADRQC; + __u8 tmp_byte; file_data = file->private_data; data = file_data->data; @@ -2207,6 +2210,12 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) (__u8 __user *)arg); break; + case USBTMC_IOCTL_AUTO_ABORT: + retval = get_user(tmp_byte, (unsigned char __user *)arg); + if (retval == 0) + file_data->auto_abort = !!tmp_byte; + break; + case USBTMC_IOCTL_CANCEL_IO: retval = usbtmc_ioctl_cancel_io(file_data); break; diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 55ca365b66d4..4b36108b9cca 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -99,6 +99,7 @@ struct usbtmc_message { #define USBTMC488_IOCTL_WAIT_SRQ _IOW(USBTMC_IOC_NR, 23, __u32) #define USBTMC_IOCTL_MSG_IN_ATTR _IOR(USBTMC_IOC_NR, 24, __u8) +#define USBTMC_IOCTL_AUTO_ABORT _IOW(USBTMC_IOC_NR, 25, __u8) /* Cancel and cleanup asynchronous calls */ #define USBTMC_IOCTL_CANCEL_IO _IO(USBTMC_IOC_NR, 35) -- cgit v1.2.3-58-ga151 From 4d5e18d9ed93fcdf1bd625aac80048f6cd0063bc Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:01 +0200 Subject: usb: usbtmc: Optimize usbtmc_write Use new usbtmc_generic_write function to maximize bandwidth during long data transfer. The maximum output transfer size is limited to INT_MAX (=2GB). Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 170 ++++++++++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 64 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 3ed2146fb670..c476b53b6237 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1509,94 +1509,136 @@ static ssize_t usbtmc_write(struct file *filp, const char __user *buf, { struct usbtmc_file_data *file_data; struct usbtmc_device_data *data; + struct urb *urb = NULL; + ssize_t retval = 0; u8 *buffer; - int retval; - int actual; - unsigned long int n_bytes; - int remaining; - int done; - int this_part; + u32 remaining, done; + u32 transfersize, aligned, buflen; file_data = filp->private_data; data = file_data->data; - buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - mutex_lock(&data->io_mutex); + if (data->zombie) { retval = -ENODEV; goto exit; } - remaining = count; done = 0; - while (remaining > 0) { - if (remaining > USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE) { - this_part = USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE; - buffer[8] = 0; - } else { - this_part = remaining; - buffer[8] = file_data->eom_val; - } + spin_lock_irq(&file_data->err_lock); + file_data->out_transfer_size = 0; + file_data->out_status = 0; + spin_unlock_irq(&file_data->err_lock); - /* Setup IO buffer for DEV_DEP_MSG_OUT message */ - buffer[0] = 1; - buffer[1] = data->bTag; - buffer[2] = ~data->bTag; - buffer[3] = 0; /* Reserved */ - buffer[4] = this_part >> 0; - buffer[5] = this_part >> 8; - buffer[6] = this_part >> 16; - buffer[7] = this_part >> 24; - /* buffer[8] is set above... */ - buffer[9] = 0; /* Reserved */ - buffer[10] = 0; /* Reserved */ - buffer[11] = 0; /* Reserved */ - - if (copy_from_user(&buffer[USBTMC_HEADER_SIZE], buf + done, this_part)) { - retval = -EFAULT; - goto exit; - } + if (!count) + goto exit; - n_bytes = roundup(USBTMC_HEADER_SIZE + this_part, 4); - memset(buffer + USBTMC_HEADER_SIZE + this_part, 0, n_bytes - (USBTMC_HEADER_SIZE + this_part)); + if (down_trylock(&file_data->limit_write_sem)) { + /* previous calls were async */ + retval = -EBUSY; + goto exit; + } - do { - retval = usb_bulk_msg(data->usb_dev, - usb_sndbulkpipe(data->usb_dev, - data->bulk_out), - buffer, n_bytes, - &actual, file_data->timeout); - if (retval != 0) - break; - n_bytes -= actual; - } while (n_bytes); - - data->bTag_last_write = data->bTag; + urb = usbtmc_create_urb(); + if (!urb) { + retval = -ENOMEM; + up(&file_data->limit_write_sem); + goto exit; + } + + buffer = urb->transfer_buffer; + buflen = urb->transfer_buffer_length; + + if (count > INT_MAX) { + transfersize = INT_MAX; + buffer[8] = 0; + } else { + transfersize = count; + buffer[8] = file_data->eom_val; + } + + /* Setup IO buffer for DEV_DEP_MSG_OUT message */ + buffer[0] = 1; + buffer[1] = data->bTag; + buffer[2] = ~data->bTag; + buffer[3] = 0; /* Reserved */ + buffer[4] = transfersize >> 0; + buffer[5] = transfersize >> 8; + buffer[6] = transfersize >> 16; + buffer[7] = transfersize >> 24; + /* buffer[8] is set above... */ + buffer[9] = 0; /* Reserved */ + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + remaining = transfersize; + + if (transfersize + USBTMC_HEADER_SIZE > buflen) { + transfersize = buflen - USBTMC_HEADER_SIZE; + aligned = buflen; + } else { + aligned = (transfersize + (USBTMC_HEADER_SIZE + 3)) & ~3; + } + + if (copy_from_user(&buffer[USBTMC_HEADER_SIZE], buf, transfersize)) { + retval = -EFAULT; + up(&file_data->limit_write_sem); + goto exit; + } + + dev_dbg(&data->intf->dev, "%s(size:%u align:%u)\n", __func__, + (unsigned int)transfersize, (unsigned int)aligned); + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, aligned, true); + + usb_fill_bulk_urb(urb, data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out), + urb->transfer_buffer, aligned, + usbtmc_write_bulk_cb, file_data); + + usb_anchor_urb(urb, &file_data->submitted); + retval = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(retval)) { + usb_unanchor_urb(urb); + up(&file_data->limit_write_sem); + goto exit; + } + + remaining -= transfersize; + + data->bTag_last_write = data->bTag; + data->bTag++; + + if (!data->bTag) data->bTag++; - if (!data->bTag) - data->bTag++; + /* call generic_write even when remaining = 0 */ + retval = usbtmc_generic_write(file_data, buf + transfersize, remaining, + &done, USBTMC_FLAG_APPEND); + /* truncate alignment bytes */ + if (done > remaining) + done = remaining; - if (retval < 0) { - dev_err(&data->intf->dev, - "Unable to send data, error %d\n", retval); - if (file_data->auto_abort) - usbtmc_ioctl_abort_bulk_out(data); - goto exit; - } + /*add size of first urb*/ + done += transfersize; - remaining -= this_part; - done += this_part; + if (retval < 0) { + usb_kill_anchored_urbs(&file_data->submitted); + + dev_err(&data->intf->dev, + "Unable to send data, error %d\n", (int)retval); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; } - retval = count; + retval = done; exit: + usb_free_urb(urb); mutex_unlock(&data->io_mutex); - kfree(buffer); return retval; } -- cgit v1.2.3-58-ga151 From d7604ff0dc018f21d0363a8ebd424bf84cf41020 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:02 +0200 Subject: usb: usbtmc: Optimize usbtmc_read Use new usbtmc_generic_read function to maximize bandwidth during long data transfer. Also fix reading of zero length packet (ZLP) or trailing short packet. The maximum input transfer size is limited to INT_MAX (=2GB). Also remove redundant return in send_request_dev_dep_msg_in(). Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 188 +++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 100 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index c476b53b6237..26a779d0c89b 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1293,7 +1293,7 @@ static ssize_t usbtmc_ioctl_write_result(struct usbtmc_file_data *file_data, * Also updates bTag_last_write. */ static int send_request_dev_dep_msg_in(struct usbtmc_file_data *file_data, - size_t transfer_size) + u32 transfer_size) { struct usbtmc_device_data *data = file_data->data; int retval; @@ -1336,12 +1336,11 @@ static int send_request_dev_dep_msg_in(struct usbtmc_file_data *file_data, data->bTag++; kfree(buffer); - if (retval < 0) { - dev_err(&data->intf->dev, "usb_bulk_msg in send_request_dev_dep_msg_in() returned %d\n", retval); - return retval; - } + if (retval < 0) + dev_err(&data->intf->dev, "%s returned %d\n", + __func__, retval); - return 0; + return retval; } static ssize_t usbtmc_read(struct file *filp, char __user *buf, @@ -1350,20 +1349,20 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, struct usbtmc_file_data *file_data; struct usbtmc_device_data *data; struct device *dev; + const u32 bufsize = USBTMC_BUFSIZE; u32 n_characters; u8 *buffer; int actual; - size_t done; - size_t remaining; + u32 done = 0; + u32 remaining; int retval; - size_t this_part; /* Get pointer to private data structure */ file_data = filp->private_data; data = file_data->data; dev = &data->intf->dev; - buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + buffer = kmalloc(bufsize, GFP_KERNEL); if (!buffer) return -ENOMEM; @@ -1373,7 +1372,10 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, goto exit; } - dev_dbg(dev, "usb_bulk_msg_in: count(%zu)\n", count); + if (count > INT_MAX) + count = INT_MAX; + + dev_dbg(dev, "%s(count:%zu)\n", __func__, count); retval = send_request_dev_dep_msg_in(file_data, count); @@ -1385,114 +1387,100 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, /* Loop until we have fetched everything we requested */ remaining = count; - this_part = remaining; - done = 0; - - while (remaining > 0) { - /* Send bulk URB */ - retval = usb_bulk_msg(data->usb_dev, - usb_rcvbulkpipe(data->usb_dev, - data->bulk_in), - buffer, USBTMC_SIZE_IOBUFFER, &actual, - file_data->timeout); - - dev_dbg(dev, "usb_bulk_msg: retval(%u), done(%zu), remaining(%zu), actual(%d)\n", retval, done, remaining, actual); - /* Store bTag (in case we need to abort) */ - data->bTag_last_read = data->bTag; - - if (retval < 0) { - dev_dbg(dev, "Unable to read data, error %d\n", retval); - if (file_data->auto_abort) - usbtmc_ioctl_abort_bulk_in(data); - goto exit; - } + /* Send bulk URB */ + retval = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, bufsize, &actual, + file_data->timeout); - /* Parse header in first packet */ - if (done == 0) { - /* Sanity checks for the header */ - if (actual < USBTMC_HEADER_SIZE) { - dev_err(dev, "Device sent too small first packet: %u < %u\n", actual, USBTMC_HEADER_SIZE); - if (file_data->auto_abort) - usbtmc_ioctl_abort_bulk_in(data); - goto exit; - } + dev_dbg(dev, "%s: bulk_msg retval(%u), actual(%d)\n", + __func__, retval, actual); - if (buffer[0] != 2) { - dev_err(dev, "Device sent reply with wrong MsgID: %u != 2\n", buffer[0]); - if (file_data->auto_abort) - usbtmc_ioctl_abort_bulk_in(data); - goto exit; - } + /* Store bTag (in case we need to abort) */ + data->bTag_last_read = data->bTag; - if (buffer[1] != data->bTag_last_write) { - dev_err(dev, "Device sent reply with wrong bTag: %u != %u\n", buffer[1], data->bTag_last_write); - if (file_data->auto_abort) - usbtmc_ioctl_abort_bulk_in(data); - goto exit; - } + if (retval < 0) { + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - /* How many characters did the instrument send? */ - n_characters = buffer[4] + - (buffer[5] << 8) + - (buffer[6] << 16) + - (buffer[7] << 24); + /* Sanity checks for the header */ + if (actual < USBTMC_HEADER_SIZE) { + dev_err(dev, "Device sent too small first packet: %u < %u\n", + actual, USBTMC_HEADER_SIZE); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - file_data->bmTransferAttributes = buffer[8]; + if (buffer[0] != 2) { + dev_err(dev, "Device sent reply with wrong MsgID: %u != 2\n", + buffer[0]); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - if (n_characters > this_part) { - dev_err(dev, "Device wants to return more data than requested: %u > %zu\n", n_characters, count); - if (file_data->auto_abort) - usbtmc_ioctl_abort_bulk_in(data); - goto exit; - } + if (buffer[1] != data->bTag_last_write) { + dev_err(dev, "Device sent reply with wrong bTag: %u != %u\n", + buffer[1], data->bTag_last_write); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - /* Remove the USBTMC header */ - actual -= USBTMC_HEADER_SIZE; + /* How many characters did the instrument send? */ + n_characters = buffer[4] + + (buffer[5] << 8) + + (buffer[6] << 16) + + (buffer[7] << 24); - /* Check if the message is smaller than requested */ - if (remaining > n_characters) - remaining = n_characters; - /* Remove padding if it exists */ - if (actual > remaining) - actual = remaining; + file_data->bmTransferAttributes = buffer[8]; - dev_dbg(dev, "Bulk-IN header: N_characters(%u), bTransAttr(%u)\n", n_characters, buffer[8]); + dev_dbg(dev, "Bulk-IN header: N_characters(%u), bTransAttr(%u)\n", + n_characters, buffer[8]); - remaining -= actual; + if (n_characters > remaining) { + dev_err(dev, "Device wants to return more data than requested: %u > %zu\n", + n_characters, count); + if (file_data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } - /* Terminate if end-of-message bit received from device */ - if ((buffer[8] & 0x01) && (actual >= n_characters)) - remaining = 0; + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, actual, true); - dev_dbg(dev, "Bulk-IN header: remaining(%zu), buf(%p), buffer(%p) done(%zu)\n", remaining,buf,buffer,done); + remaining = n_characters; + /* Remove the USBTMC header */ + actual -= USBTMC_HEADER_SIZE; - /* Copy buffer to user space */ - if (copy_to_user(buf + done, &buffer[USBTMC_HEADER_SIZE], actual)) { - /* There must have been an addressing problem */ - retval = -EFAULT; - goto exit; - } - done += actual; - } - else { - if (actual > remaining) - actual = remaining; + /* Remove padding if it exists */ + if (actual > remaining) + actual = remaining; - remaining -= actual; + remaining -= actual; - dev_dbg(dev, "Bulk-IN header cont: actual(%u), done(%zu), remaining(%zu), buf(%p), buffer(%p)\n", actual, done, remaining,buf,buffer); + /* Copy buffer to user space */ + if (copy_to_user(buf, &buffer[USBTMC_HEADER_SIZE], actual)) { + /* There must have been an addressing problem */ + retval = -EFAULT; + goto exit; + } - /* Copy buffer to user space */ - if (copy_to_user(buf + done, buffer, actual)) { - /* There must have been an addressing problem */ - retval = -EFAULT; - goto exit; - } - done += actual; - } + if ((actual + USBTMC_HEADER_SIZE) == bufsize) { + retval = usbtmc_generic_read(file_data, buf + actual, + remaining, + &done, + USBTMC_FLAG_IGNORE_TRAILER); + if (retval < 0) + goto exit; } + done += actual; /* Update file position value */ *f_pos = *f_pos + done; -- cgit v1.2.3-58-ga151 From dfee02ac4bce6374c9769fe31f20794309341fa0 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:03 +0200 Subject: usb: usbtmc: Fix ioctl USBTMC_IOCTL_CLEAR Remove calculation of max_size (=wMaxPacketSize) and wrong condition (actual == max_size) in while loop. A device clear should always flush the complete Bulk-IN FIFO. Insert a sleep of 50 ms between subsequent CHECK_CLEAR_STATUS control requests to avoid stressing the instrument with repeated requests. Some instruments need time to cleanup internal I/O buffers. Polling and nonbraked requests slow down the response time of devices. Use USBTMC_BUFSIZE (4k) instead of USBTMC_SIZE_IOBUFFER (2k). Using USBTMC_SIZE_IOBUFFER is deprecated. Check only bit 0 (field bmClear) of the CHECK_CLEAR_STATUS response, since other bits are reserved and can change in future versions. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 26a779d0c89b..11b2c8632d91 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1632,20 +1632,17 @@ exit: static int usbtmc_ioctl_clear(struct usbtmc_device_data *data) { - struct usb_host_interface *current_setting; - struct usb_endpoint_descriptor *desc; struct device *dev; u8 *buffer; int rv; int n; int actual = 0; - int max_size; dev = &data->intf->dev; dev_dbg(dev, "Sending INITIATE_CLEAR request\n"); - buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + buffer = kmalloc(USBTMC_BUFSIZE, GFP_KERNEL); if (!buffer) return -ENOMEM; @@ -1653,7 +1650,7 @@ static int usbtmc_ioctl_clear(struct usbtmc_device_data *data) usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_INITIATE_CLEAR, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, - 0, 0, buffer, 1, USBTMC_TIMEOUT); + 0, 0, buffer, 1, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); goto exit; @@ -1667,22 +1664,6 @@ static int usbtmc_ioctl_clear(struct usbtmc_device_data *data) goto exit; } - max_size = 0; - current_setting = data->intf->cur_altsetting; - for (n = 0; n < current_setting->desc.bNumEndpoints; n++) { - desc = ¤t_setting->endpoint[n].desc; - if (desc->bEndpointAddress == data->bulk_in) - max_size = usb_endpoint_maxp(desc); - } - - if (max_size == 0) { - dev_err(dev, "Couldn't get wMaxPacketSize\n"); - rv = -EPERM; - goto exit; - } - - dev_dbg(dev, "wMaxPacketSize is %d\n", max_size); - n = 0; usbtmc_clear_check_status: @@ -1693,7 +1674,7 @@ usbtmc_clear_check_status: usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_CHECK_CLEAR_STATUS, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, - 0, 0, buffer, 2, USBTMC_TIMEOUT); + 0, 0, buffer, 2, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); goto exit; @@ -1710,15 +1691,19 @@ usbtmc_clear_check_status: goto exit; } - if (buffer[1] == 1) + if ((buffer[1] & 1) != 0) { do { dev_dbg(dev, "Reading from bulk in EP\n"); rv = usb_bulk_msg(data->usb_dev, usb_rcvbulkpipe(data->usb_dev, data->bulk_in), - buffer, USBTMC_SIZE_IOBUFFER, - &actual, USBTMC_TIMEOUT); + buffer, USBTMC_BUFSIZE, + &actual, USB_CTRL_GET_TIMEOUT); + + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, + 16, 1, buffer, actual, true); + n++; if (rv < 0) { @@ -1726,10 +1711,15 @@ usbtmc_clear_check_status: rv); goto exit; } - } while ((actual == max_size) && + } while ((actual == USBTMC_BUFSIZE) && (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + } else { + /* do not stress device with subsequent requests */ + msleep(50); + n++; + } - if (actual == max_size) { + if (n >= USBTMC_MAX_READS_TO_CLEAR_BULK_IN) { dev_err(dev, "Couldn't clear device buffer within %d cycles\n", USBTMC_MAX_READS_TO_CLEAR_BULK_IN); rv = -EPERM; @@ -1743,7 +1733,7 @@ usbtmc_clear_bulk_out_halt: rv = usb_clear_halt(data->usb_dev, usb_sndbulkpipe(data->usb_dev, data->bulk_out)); if (rv < 0) { - dev_err(dev, "usb_control_msg returned %d\n", rv); + dev_err(dev, "usb_clear_halt returned %d\n", rv); goto exit; } rv = 0; -- cgit v1.2.3-58-ga151 From cbe743f1333b23040d1312afd58224dbd58fcc25 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:04 +0200 Subject: usb: usbtmc: Fix ioctl USBTMC_IOCTL_ABORT_BULK_IN Add parameter 'tag' to function usbtmc_ioctl_abort_bulk_in_tag() for future versions. Remove calculation of max_size (=wMaxPacketSize) and wrong condition (actual == max_size) in while loop. An abort operation should always flush the complete Bulk-IN until a short packet is received. Return error code ENOMSG when transfer (specified by given tag) is not in progress and device returns code USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS. Use USBTMC_BUFSIZE (4k) instead of USBTMC_SIZE_IOBUFFER (2k). Using USBTMC_SIZE_IOBUFFER is deprecated. Use common macro USB_CTRL_GET_TIMEOUT instead of USBTMC_TIMEOUT. Check only bit 0 (field bmAbortBulkIn) of the CHECK_ABORT_BULK_IN_STATUS response, since other bits are reserved and can change in future versions. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 119 +++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 68 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 11b2c8632d91..0b05aaa0247c 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -270,18 +270,17 @@ static int usbtmc_release(struct inode *inode, struct file *file) return 0; } -static int usbtmc_ioctl_abort_bulk_in(struct usbtmc_device_data *data) +static int usbtmc_ioctl_abort_bulk_in_tag(struct usbtmc_device_data *data, + u8 tag) { u8 *buffer; struct device *dev; int rv; int n; int actual; - struct usb_host_interface *current_setting; - int max_size; dev = &data->intf->dev; - buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + buffer = kmalloc(USBTMC_BUFSIZE, GFP_KERNEL); if (!buffer) return -ENOMEM; @@ -289,86 +288,87 @@ static int usbtmc_ioctl_abort_bulk_in(struct usbtmc_device_data *data) usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_INITIATE_ABORT_BULK_IN, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, - data->bTag_last_read, data->bulk_in, - buffer, 2, USBTMC_TIMEOUT); + tag, data->bulk_in, + buffer, 2, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); goto exit; } - dev_dbg(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + dev_dbg(dev, "INITIATE_ABORT_BULK_IN returned %x with tag %02x\n", + buffer[0], buffer[1]); if (buffer[0] == USBTMC_STATUS_FAILED) { + /* No transfer in progress and the Bulk-OUT FIFO is empty. */ rv = 0; goto exit; } - if (buffer[0] != USBTMC_STATUS_SUCCESS) { - dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", - buffer[0]); - rv = -EPERM; + if (buffer[0] == USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS) { + /* The device returns this status if either: + * - There is a transfer in progress, but the specified bTag + * does not match. + * - There is no transfer in progress, but the Bulk-OUT FIFO + * is not empty. + */ + rv = -ENOMSG; goto exit; } - max_size = 0; - current_setting = data->intf->cur_altsetting; - for (n = 0; n < current_setting->desc.bNumEndpoints; n++) - if (current_setting->endpoint[n].desc.bEndpointAddress == - data->bulk_in) - max_size = usb_endpoint_maxp(¤t_setting->endpoint[n].desc); - - if (max_size == 0) { - dev_err(dev, "Couldn't get wMaxPacketSize\n"); + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", + buffer[0]); rv = -EPERM; goto exit; } - dev_dbg(&data->intf->dev, "wMaxPacketSize is %d\n", max_size); - n = 0; - do { - dev_dbg(dev, "Reading from bulk in EP\n"); +usbtmc_abort_bulk_in_status: + dev_dbg(dev, "Reading from bulk in EP\n"); - rv = usb_bulk_msg(data->usb_dev, - usb_rcvbulkpipe(data->usb_dev, - data->bulk_in), - buffer, USBTMC_SIZE_IOBUFFER, - &actual, USBTMC_TIMEOUT); + /* Data must be present. So use low timeout 300 ms */ + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_BUFSIZE, + &actual, 300); - n++; + print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE, 16, 1, + buffer, actual, true); - if (rv < 0) { - dev_err(dev, "usb_bulk_msg returned %d\n", rv); + n++; + + if (rv < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", rv); + if (rv != -ETIMEDOUT) goto exit; - } - } while ((actual == max_size) && - (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + } - if (actual == max_size) { + if (actual == USBTMC_BUFSIZE) + goto usbtmc_abort_bulk_in_status; + + if (n >= USBTMC_MAX_READS_TO_CLEAR_BULK_IN) { dev_err(dev, "Couldn't clear device buffer within %d cycles\n", USBTMC_MAX_READS_TO_CLEAR_BULK_IN); rv = -EPERM; goto exit; } - n = 0; - -usbtmc_abort_bulk_in_status: rv = usb_control_msg(data->usb_dev, usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_CHECK_ABORT_BULK_IN_STATUS, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, 0, data->bulk_in, buffer, 0x08, - USBTMC_TIMEOUT); + USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); goto exit; } - dev_dbg(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + dev_dbg(dev, "CHECK_ABORT_BULK_IN returned %x\n", buffer[0]); if (buffer[0] == USBTMC_STATUS_SUCCESS) { rv = 0; @@ -376,43 +376,26 @@ usbtmc_abort_bulk_in_status: } if (buffer[0] != USBTMC_STATUS_PENDING) { - dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + dev_err(dev, "CHECK_ABORT_BULK_IN returned %x\n", buffer[0]); rv = -EPERM; goto exit; } - if (buffer[1] == 1) - do { - dev_dbg(dev, "Reading from bulk in EP\n"); - - rv = usb_bulk_msg(data->usb_dev, - usb_rcvbulkpipe(data->usb_dev, - data->bulk_in), - buffer, USBTMC_SIZE_IOBUFFER, - &actual, USBTMC_TIMEOUT); - - n++; - - if (rv < 0) { - dev_err(dev, "usb_bulk_msg returned %d\n", rv); - goto exit; - } - } while ((actual == max_size) && - (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); - - if (actual == max_size) { - dev_err(dev, "Couldn't clear device buffer within %d cycles\n", - USBTMC_MAX_READS_TO_CLEAR_BULK_IN); - rv = -EPERM; - goto exit; + if ((buffer[1] & 1) > 0) { + /* The device has 1 or more queued packets the Host can read */ + goto usbtmc_abort_bulk_in_status; } - goto usbtmc_abort_bulk_in_status; - + /* The Host must send CHECK_ABORT_BULK_IN_STATUS at a later time. */ + rv = -EAGAIN; exit: kfree(buffer); return rv; +} +static int usbtmc_ioctl_abort_bulk_in(struct usbtmc_device_data *data) +{ + return usbtmc_ioctl_abort_bulk_in_tag(data, data->bTag_last_read); } static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) -- cgit v1.2.3-58-ga151 From 0e59088e7ff7aeda49dedadbf0e967761b909ad8 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:05 +0200 Subject: usb: usbtmc: Fix ioctl USBTMC_IOCTL_ABORT_BULK_OUT Add parameter 'tag' to function usbtmc_ioctl_abort_bulk_out_tag() for future versions. Use USBTMC_BUFSIZE (4k) instead of USBTMC_SIZE_IOBUFFER (2k). Using USBTMC_SIZE_IOBUFFER is deprecated. Insert a sleep of 50 ms between subsequent CHECK_ABORT_BULK_OUT_STATUS control requests to avoid stressing the instrument with repeated requests. Use common macro USB_CTRL_GET_TIMEOUT instead of USBTMC_TIMEOUT. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 0b05aaa0247c..329daa7425dc 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -398,7 +398,8 @@ static int usbtmc_ioctl_abort_bulk_in(struct usbtmc_device_data *data) return usbtmc_ioctl_abort_bulk_in_tag(data, data->bTag_last_read); } -static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) +static int usbtmc_ioctl_abort_bulk_out_tag(struct usbtmc_device_data *data, + u8 tag) { struct device *dev; u8 *buffer; @@ -415,8 +416,8 @@ static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_INITIATE_ABORT_BULK_OUT, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, - data->bTag_last_write, data->bulk_out, - buffer, 2, USBTMC_TIMEOUT); + tag, data->bulk_out, + buffer, 2, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); @@ -435,12 +436,14 @@ static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) n = 0; usbtmc_abort_bulk_out_check_status: + /* do not stress device with subsequent requests */ + msleep(50); rv = usb_control_msg(data->usb_dev, usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_CHECK_ABORT_BULK_OUT_STATUS, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, 0, data->bulk_out, buffer, 0x08, - USBTMC_TIMEOUT); + USB_CTRL_GET_TIMEOUT); n++; if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); @@ -474,6 +477,11 @@ exit: return rv; } +static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) +{ + return usbtmc_ioctl_abort_bulk_out_tag(data, data->bTag_last_write); +} + static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, void __user *arg) { -- cgit v1.2.3-58-ga151 From 63c97bbad5ae5ef411b9a6f2dccdd11e23f29f89 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:06 +0200 Subject: usb: usbtmc: Replace USBTMC_TIMEOUT macros for control messages Use common timeout macro USB_CTRL_GET_TIMEOUT (=5s) for all usb_control_msg() function calls. The macro USBTMC_TIMEOUT should only be used as default value for Bulk IN/OUT transfers. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 329daa7425dc..964c8e87dacb 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -521,7 +521,7 @@ static int usbtmc488_ioctl_read_stb(struct usbtmc_file_data *file_data, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, data->iin_bTag, data->ifnum, - buffer, 0x03, USBTMC_TIMEOUT); + buffer, 0x03, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "stb usb_control_msg returned %d\n", rv); goto exit; @@ -655,7 +655,7 @@ static int usbtmc488_ioctl_simple(struct usbtmc_device_data *data, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, wValue, data->ifnum, - buffer, 0x01, USBTMC_TIMEOUT); + buffer, 0x01, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "simple usb_control_msg failed %d\n", rv); goto exit; @@ -1802,7 +1802,7 @@ static int get_capabilities(struct usbtmc_device_data *data) rv = usb_control_msg(data->usb_dev, usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_GET_CAPABILITIES, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, - 0, 0, buffer, 0x18, USBTMC_TIMEOUT); + 0, 0, buffer, 0x18, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); goto err_out; @@ -1941,7 +1941,7 @@ static int usbtmc_ioctl_indicator_pulse(struct usbtmc_device_data *data) usb_rcvctrlpipe(data->usb_dev, 0), USBTMC_REQUEST_INDICATOR_PULSE, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, - 0, 0, buffer, 0x01, USBTMC_TIMEOUT); + 0, 0, buffer, 0x01, USB_CTRL_GET_TIMEOUT); if (rv < 0) { dev_err(dev, "usb_control_msg returned %d\n", rv); -- cgit v1.2.3-58-ga151 From e013477bc20763e28d95d74e5ca97411194984ec Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:07 +0200 Subject: usb: usbtmc: Add ioctl USBTMC_IOCTL_API_VERSION Add ioctl USBTMC_IOCTL_API_VERSION to get current API version of usbtmc driver. This is to allow an instrument library to determine whether the driver API is compatible with the implementation. The API may change in future versions. Therefore the macro USBTMC_API_VERSION should be incremented when changing tmc.h with new flags, ioctls or when changing a significant behavior of the driver. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 9 +++++++++ include/uapi/linux/usb/tmc.h | 1 + 2 files changed, 10 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 964c8e87dacb..72867a97ec00 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -22,6 +22,10 @@ #include <linux/compat.h> #include <linux/usb/tmc.h> +/* Increment API VERSION when changing tmc.h with new flags or ioctls + * or when changing a significant behavior of the driver. + */ +#define USBTMC_API_VERSION (2) #define USBTMC_HEADER_SIZE 12 #define USBTMC_MINOR_BASE 176 @@ -2179,6 +2183,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) (void __user *)arg); break; + case USBTMC_IOCTL_API_VERSION: + retval = put_user(USBTMC_API_VERSION, + (__u32 __user *)arg); + break; + case USBTMC488_IOCTL_GET_CAPS: retval = copy_to_user((void __user *)arg, &data->usb488_caps, diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h index 4b36108b9cca..fdd4d88a7b95 100644 --- a/include/uapi/linux/usb/tmc.h +++ b/include/uapi/linux/usb/tmc.h @@ -89,6 +89,7 @@ struct usbtmc_message { #define USBTMC_IOCTL_WRITE _IOWR(USBTMC_IOC_NR, 13, struct usbtmc_message) #define USBTMC_IOCTL_READ _IOWR(USBTMC_IOC_NR, 14, struct usbtmc_message) #define USBTMC_IOCTL_WRITE_RESULT _IOWR(USBTMC_IOC_NR, 15, __u32) +#define USBTMC_IOCTL_API_VERSION _IOR(USBTMC_IOC_NR, 16, __u32) #define USBTMC488_IOCTL_GET_CAPS _IOR(USBTMC_IOC_NR, 17, unsigned char) #define USBTMC488_IOCTL_READ_STB _IOR(USBTMC_IOC_NR, 18, unsigned char) -- cgit v1.2.3-58-ga151 From fd784cad03530d82d31c41759a46dd2ddfbf8f6f Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:09 +0200 Subject: usb: usbtmc: Remove redundant code Remove redundant code and fix debug messages. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 72867a97ec00..5b6cdb1237ab 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1745,12 +1745,9 @@ static int usbtmc_ioctl_clear_out_halt(struct usbtmc_device_data *data) rv = usb_clear_halt(data->usb_dev, usb_sndbulkpipe(data->usb_dev, data->bulk_out)); - if (rv < 0) { - dev_err(&data->usb_dev->dev, "usb_control_msg returned %d\n", - rv); - return rv; - } - return 0; + if (rv < 0) + dev_err(&data->usb_dev->dev, "%s returned %d\n", __func__, rv); + return rv; } static int usbtmc_ioctl_clear_in_halt(struct usbtmc_device_data *data) @@ -1760,12 +1757,9 @@ static int usbtmc_ioctl_clear_in_halt(struct usbtmc_device_data *data) rv = usb_clear_halt(data->usb_dev, usb_rcvbulkpipe(data->usb_dev, data->bulk_in)); - if (rv < 0) { - dev_err(&data->usb_dev->dev, "usb_control_msg returned %d\n", - rv); - return rv; - } - return 0; + if (rv < 0) + dev_err(&data->usb_dev->dev, "%s returned %d\n", __func__, rv); + return rv; } static int usbtmc_ioctl_cancel_io(struct usbtmc_file_data *file_data) @@ -2189,11 +2183,8 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; case USBTMC488_IOCTL_GET_CAPS: - retval = copy_to_user((void __user *)arg, - &data->usb488_caps, - sizeof(data->usb488_caps)); - if (retval) - retval = -EFAULT; + retval = put_user(data->usb488_caps, + (unsigned char __user *)arg); break; case USBTMC488_IOCTL_READ_STB: -- cgit v1.2.3-58-ga151 From 386be9094f097289f6f68826c90c24bb55db43cf Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:10 +0200 Subject: usb: usbtmc: Remove redundant macro USBTMC_SIZE_IOBUFFER Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 5b6cdb1237ab..ad3932ca4d8d 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -30,12 +30,6 @@ #define USBTMC_HEADER_SIZE 12 #define USBTMC_MINOR_BASE 176 -/* - * Size of driver internal IO buffer. Must be multiple of 4 and at least as - * large as wMaxPacketSize (which is usually 512 bytes). - */ -#define USBTMC_SIZE_IOBUFFER 2048 - /* Minimum USB timeout (in milliseconds) */ #define USBTMC_MIN_TIMEOUT 100 /* Default USB timeout (in milliseconds) */ -- cgit v1.2.3-58-ga151 From 5848828387c748d13890df1e3a1038fe91c0373d Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:11 +0200 Subject: usb: usbtmc: Fix split quoted string in debug message Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index ad3932ca4d8d..b9e505cbe6b4 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -2499,8 +2499,8 @@ static int usbtmc_probe(struct usb_interface *intf, retcode = usb_register_dev(intf, &usbtmc_class); if (retcode) { - dev_err(&intf->dev, "Not able to get a minor" - " (base %u, slice default): %d\n", USBTMC_MINOR_BASE, + dev_err(&intf->dev, "Not able to get a minor (base %u, slice default): %d\n", + USBTMC_MINOR_BASE, retcode); goto error_register; } -- cgit v1.2.3-58-ga151 From b32abf8f5d83049fb4576c3edb9f31f3515791a5 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido@kiener-muenchen.de> Date: Wed, 12 Sep 2018 10:51:12 +0200 Subject: usb: usbtmc: Remove sysfs group TermChar and auto_abort As all the properties of the usbtmc driver can now be controlled on a per file descriptor basis by ioctl functions the sysfs interface is of limited use. We are not aware about applications that are using the sysfs parameter TermChar, TermCharEnabled or auto_abort. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Reviewed-by: Steve Bayless <steve_bayless@keysight.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- Documentation/ABI/stable/sysfs-driver-usb-usbtmc | 35 ---------- drivers/usb/class/usbtmc.c | 84 +----------------------- 2 files changed, 3 insertions(+), 116 deletions(-) (limited to 'drivers/usb') diff --git a/Documentation/ABI/stable/sysfs-driver-usb-usbtmc b/Documentation/ABI/stable/sysfs-driver-usb-usbtmc index e960cd027e1e..a9e123ba32cd 100644 --- a/Documentation/ABI/stable/sysfs-driver-usb-usbtmc +++ b/Documentation/ABI/stable/sysfs-driver-usb-usbtmc @@ -25,38 +25,3 @@ Description: 4.2.2. The files are read only. - - -What: /sys/bus/usb/drivers/usbtmc/*/TermChar -Date: August 2008 -Contact: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -Description: - This file is the TermChar value to be sent to the USB TMC - device as described by the document, "Universal Serial Bus Test - and Measurement Class Specification - (USBTMC) Revision 1.0" as published by the USB-IF. - - Note that the TermCharEnabled file determines if this value is - sent to the device or not. - - -What: /sys/bus/usb/drivers/usbtmc/*/TermCharEnabled -Date: August 2008 -Contact: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -Description: - This file determines if the TermChar is to be sent to the - device on every transaction or not. For more details about - this, please see the document, "Universal Serial Bus Test and - Measurement Class Specification (USBTMC) Revision 1.0" as - published by the USB-IF. - - -What: /sys/bus/usb/drivers/usbtmc/*/auto_abort -Date: August 2008 -Contact: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -Description: - This file determines if the transaction of the USB TMC - device is to be automatically aborted if there is any error. - For more details about this, please see the document, - "Universal Serial Bus Test and Measurement Class Specification - (USBTMC) Revision 1.0" as published by the USB-IF. diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index b9e505cbe6b4..0fcb81a1399b 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -102,11 +102,6 @@ struct usbtmc_device_data { /* coalesced usb488_caps from usbtmc_dev_capabilities */ __u8 usb488_caps; - /* attributes from the USB TMC spec for this device */ - u8 TermChar; - bool TermCharEnabled; - bool auto_abort; - bool zombie; /* fd of disconnected device */ struct usbtmc_dev_capabilities capabilities; @@ -196,11 +191,10 @@ static int usbtmc_open(struct inode *inode, struct file *filp) atomic_set(&file_data->closing, 0); - /* copy default values from device settings */ file_data->timeout = USBTMC_TIMEOUT; - file_data->term_char = data->TermChar; - file_data->term_char_enabled = data->TermCharEnabled; - file_data->auto_abort = data->auto_abort; + file_data->term_char = '\n'; + file_data->term_char_enabled = 0; + file_data->auto_abort = 0; file_data->eom_val = 1; INIT_LIST_HEAD(&file_data->file_elem); @@ -1851,72 +1845,6 @@ static const struct attribute_group capability_attr_grp = { .attrs = capability_attrs, }; -static ssize_t TermChar_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct usb_interface *intf = to_usb_interface(dev); - struct usbtmc_device_data *data = usb_get_intfdata(intf); - - return sprintf(buf, "%c\n", data->TermChar); -} - -static ssize_t TermChar_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct usb_interface *intf = to_usb_interface(dev); - struct usbtmc_device_data *data = usb_get_intfdata(intf); - - if (count < 1) - return -EINVAL; - data->TermChar = buf[0]; - return count; -} -static DEVICE_ATTR_RW(TermChar); - -#define data_attribute(name) \ -static ssize_t name##_show(struct device *dev, \ - struct device_attribute *attr, char *buf) \ -{ \ - struct usb_interface *intf = to_usb_interface(dev); \ - struct usbtmc_device_data *data = usb_get_intfdata(intf); \ - \ - return sprintf(buf, "%d\n", data->name); \ -} \ -static ssize_t name##_store(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - struct usb_interface *intf = to_usb_interface(dev); \ - struct usbtmc_device_data *data = usb_get_intfdata(intf); \ - ssize_t result; \ - unsigned val; \ - \ - result = sscanf(buf, "%u\n", &val); \ - if (result != 1) \ - result = -EINVAL; \ - data->name = val; \ - if (result < 0) \ - return result; \ - else \ - return count; \ -} \ -static DEVICE_ATTR_RW(name) - -data_attribute(TermCharEnabled); -data_attribute(auto_abort); - -static struct attribute *data_attrs[] = { - &dev_attr_TermChar.attr, - &dev_attr_TermCharEnabled.attr, - &dev_attr_auto_abort.attr, - NULL, -}; - -static const struct attribute_group data_attr_grp = { - .attrs = data_attrs, -}; - static int usbtmc_ioctl_indicator_pulse(struct usbtmc_device_data *data) { struct device *dev; @@ -2420,8 +2348,6 @@ static int usbtmc_probe(struct usb_interface *intf, /* Initialize USBTMC bTag and other fields */ data->bTag = 1; - data->TermCharEnabled = 0; - data->TermChar = '\n'; /* 2 <= bTag <= 127 USBTMC-USB488 subclass specification 4.3.1 */ data->iin_bTag = 2; @@ -2495,8 +2421,6 @@ static int usbtmc_probe(struct usb_interface *intf, } } - retcode = sysfs_create_group(&intf->dev.kobj, &data_attr_grp); - retcode = usb_register_dev(intf, &usbtmc_class); if (retcode) { dev_err(&intf->dev, "Not able to get a minor (base %u, slice default): %d\n", @@ -2510,7 +2434,6 @@ static int usbtmc_probe(struct usb_interface *intf, error_register: sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp); - sysfs_remove_group(&intf->dev.kobj, &data_attr_grp); usbtmc_free_int(data); err_put: kref_put(&data->kref, usbtmc_delete); @@ -2524,7 +2447,6 @@ static void usbtmc_disconnect(struct usb_interface *intf) usb_deregister_dev(intf, &usbtmc_class); sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp); - sysfs_remove_group(&intf->dev.kobj, &data_attr_grp); mutex_lock(&data->io_mutex); data->zombie = 1; wake_up_interruptible_all(&data->waitq); -- cgit v1.2.3-58-ga151 From 0440fa3d1b4eb3a75b806b6538a3f9547dec4eef Mon Sep 17 00:00:00 2001 From: Lubomir Rintel <lkundrak@v3.sk> Date: Wed, 22 Aug 2018 22:42:56 +0200 Subject: USB: EHCI: make ehci-mv a separate driver This is done do that it could be enabled alongside other platform EHCI glue drivers on multiplatform kernels. Signed-off-by: Lubomir Rintel <lkundrak@v3.sk> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/Kconfig | 2 +- drivers/usb/host/Makefile | 1 + drivers/usb/host/ehci-hcd.c | 5 --- drivers/usb/host/ehci-mv.c | 98 +++++++++++++++++++-------------------------- 4 files changed, 44 insertions(+), 62 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 1a4ea98cac2a..16758b12a5e9 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -276,7 +276,7 @@ config USB_EHCI_EXYNOS Enable support for the Samsung Exynos SOC's on-chip EHCI controller. config USB_EHCI_MV - bool "EHCI support for Marvell PXA/MMP USB controller" + tristate "EHCI support for Marvell PXA/MMP USB controller" depends on (ARCH_PXA || ARCH_MMP) select USB_EHCI_ROOT_HUB_TT ---help--- diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index e6235269c151..84514f71ae44 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o obj-$(CONFIG_USB_FSL_USB2) += fsl-mph-dr-of.o obj-$(CONFIG_USB_EHCI_FSL) += fsl-mph-dr-of.o obj-$(CONFIG_USB_EHCI_FSL) += ehci-fsl.o +obj-$(CONFIG_USB_EHCI_MV) += ehci-mv.o obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o obj-$(CONFIG_USB_FOTG210_HCD) += fotg210-hcd.o diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 8608ac513fb7..e8d7667828eb 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1286,11 +1286,6 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ehci_grlib_driver #endif -#ifdef CONFIG_USB_EHCI_MV -#include "ehci-mv.c" -#define PLATFORM_DRIVER ehci_mv_driver -#endif - static int __init ehci_hcd_init(void) { int retval = 0; diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index de764459e05a..77a4ab1dcd07 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -12,12 +12,17 @@ #include <linux/err.h> #include <linux/usb/otg.h> #include <linux/platform_data/mv_usb.h> +#include <linux/io.h> + +#include <linux/usb/hcd.h> + +#include "ehci.h" #define CAPLENGTH_MASK (0xff) -struct ehci_hcd_mv { - struct usb_hcd *hcd; +#define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv) +struct ehci_hcd_mv { /* Which mode does this ehci running OTG/Host ? */ int mode; @@ -66,7 +71,7 @@ static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) static int mv_ehci_reset(struct usb_hcd *hcd) { struct device *dev = hcd->self.controller; - struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev); + struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); int retval; if (ehci_mv == NULL) { @@ -83,46 +88,11 @@ static int mv_ehci_reset(struct usb_hcd *hcd) return retval; } -static const struct hc_driver mv_ehci_hc_driver = { - .description = hcd_name, - .product_desc = "Marvell EHCI", - .hcd_priv_size = sizeof(struct ehci_hcd), - - /* - * generic hardware linkage - */ - .irq = ehci_irq, - .flags = HCD_MEMORY | HCD_USB2 | HCD_BH, - - /* - * basic lifecycle operations - */ - .reset = mv_ehci_reset, - .start = ehci_run, - .stop = ehci_stop, - .shutdown = ehci_shutdown, - - /* - * managing i/o requests and associated device resources - */ - .urb_enqueue = ehci_urb_enqueue, - .urb_dequeue = ehci_urb_dequeue, - .endpoint_disable = ehci_endpoint_disable, - .endpoint_reset = ehci_endpoint_reset, - .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, - - /* - * scheduling support - */ - .get_frame_number = ehci_get_frame, - - /* - * root hub support - */ - .hub_status_data = ehci_hub_status_data, - .hub_control = ehci_hub_control, - .bus_suspend = ehci_bus_suspend, - .bus_resume = ehci_bus_resume, +static struct hc_driver __read_mostly ehci_platform_hc_driver; + +static const struct ehci_driver_overrides platform_overrides __initconst = { + .reset = mv_ehci_reset, + .extra_priv_size = sizeof(struct ehci_hcd_mv), }; static int mv_ehci_probe(struct platform_device *pdev) @@ -143,19 +113,13 @@ static int mv_ehci_probe(struct platform_device *pdev) if (usb_disabled()) return -ENODEV; - hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci"); + hcd = usb_create_hcd(&ehci_platform_hc_driver, &pdev->dev, "mv ehci"); if (!hcd) return -ENOMEM; - ehci_mv = devm_kzalloc(&pdev->dev, sizeof(*ehci_mv), GFP_KERNEL); - if (ehci_mv == NULL) { - retval = -ENOMEM; - goto err_put_hcd; - } - - platform_set_drvdata(pdev, ehci_mv); + platform_set_drvdata(pdev, hcd); + ehci_mv = hcd_to_ehci_hcd_mv(hcd); ehci_mv->pdata = pdata; - ehci_mv->hcd = hcd; ehci_mv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(ehci_mv->clk)) { @@ -262,8 +226,8 @@ err_put_hcd: static int mv_ehci_remove(struct platform_device *pdev) { - struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); - struct usb_hcd *hcd = ehci_mv->hcd; + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); if (hcd->rh_registered) usb_remove_hcd(hcd); @@ -295,8 +259,8 @@ static const struct platform_device_id ehci_id_table[] = { static void mv_ehci_shutdown(struct platform_device *pdev) { - struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); - struct usb_hcd *hcd = ehci_mv->hcd; + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); if (!hcd->rh_registered) return; @@ -315,3 +279,25 @@ static struct platform_driver ehci_mv_driver = { }, .id_table = ehci_id_table, }; + +static int __init ehci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides); + return platform_driver_register(&ehci_mv_driver); +} +module_init(ehci_platform_init); + +static void __exit ehci_platform_cleanup(void) +{ + platform_driver_unregister(&ehci_mv_driver); +} +module_exit(ehci_platform_cleanup); + +MODULE_DESCRIPTION("Marvell EHCI driver"); +MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>"); +MODULE_AUTHOR("Neil Zhang <zhangwm@marvell.com>"); +MODULE_ALIAS("mv-ehci"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-58-ga151 From bd93227897007bac09c44fe67626035303905900 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel <lkundrak@v3.sk> Date: Wed, 22 Aug 2018 22:43:00 +0200 Subject: USB: EHCI: ehci-mv: remove private_init It's unused. Signed-off-by: Lubomir Rintel <lkundrak@v3.sk> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/ehci-mv.c | 4 ---- include/linux/platform_data/mv_usb.h | 1 - 2 files changed, 5 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index 77a4ab1dcd07..705d1b43b2dd 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -203,9 +203,6 @@ static int mv_ehci_probe(struct platform_device *pdev) device_wakeup_enable(hcd->self.controller); } - if (pdata->private_init) - pdata->private_init(ehci_mv->op_regs, ehci_mv->phy_regs); - dev_info(&pdev->dev, "successful find EHCI device with regs 0x%p irq %d" " working in %s mode\n", hcd->regs, hcd->irq, @@ -260,7 +257,6 @@ static const struct platform_device_id ehci_id_table[] = { static void mv_ehci_shutdown(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); - struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); if (!hcd->rh_registered) return; diff --git a/include/linux/platform_data/mv_usb.h b/include/linux/platform_data/mv_usb.h index 98b7925f1a2d..c0f624aca81c 100644 --- a/include/linux/platform_data/mv_usb.h +++ b/include/linux/platform_data/mv_usb.h @@ -48,6 +48,5 @@ struct mv_usb_platform_data { int (*phy_init)(void __iomem *regbase); void (*phy_deinit)(void __iomem *regbase); int (*set_vbus)(unsigned int vbus); - int (*private_init)(void __iomem *opregs, void __iomem *phyregs); }; #endif -- cgit v1.2.3-58-ga151 From a740f20d13b67fd4d91ede93980106f085111f10 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel <lkundrak@v3.sk> Date: Wed, 22 Aug 2018 22:43:01 +0200 Subject: USB: EHCI: ehci-mv: use phy-pxa-usb Use a proper PHY driver, instead of hooks to a board support package. Signed-off-by: Lubomir Rintel <lkundrak@v3.sk> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- arch/arm/mach-mmp/devices.c | 11 +---------- drivers/usb/host/ehci-mv.c | 44 ++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 32 deletions(-) (limited to 'drivers/usb') diff --git a/arch/arm/mach-mmp/devices.c b/arch/arm/mach-mmp/devices.c index 671c7a09ab3d..0fca63c80e1a 100644 --- a/arch/arm/mach-mmp/devices.c +++ b/arch/arm/mach-mmp/devices.c @@ -277,21 +277,12 @@ struct platform_device pxa168_device_u2o = { #if IS_ENABLED(CONFIG_USB_EHCI_MV_U2O) struct resource pxa168_u2oehci_resources[] = { - /* regbase */ [0] = { - .start = PXA168_U2O_REGBASE + U2x_CAPREGS_OFFSET, + .start = PXA168_U2O_REGBASE, .end = PXA168_U2O_REGBASE + USB_REG_RANGE, .flags = IORESOURCE_MEM, - .name = "capregs", }, - /* phybase */ [1] = { - .start = PXA168_U2O_PHYBASE, - .end = PXA168_U2O_PHYBASE + USB_PHY_RANGE, - .flags = IORESOURCE_MEM, - .name = "phyregs", - }, - [2] = { .start = IRQ_PXA168_USB1, .end = IRQ_PXA168_USB1, .flags = IORESOURCE_IRQ, diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index 705d1b43b2dd..43b300c90875 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -18,6 +18,9 @@ #include "ehci.h" +/* registers */ +#define U2x_CAPREGS_OFFSET 0x100 + #define CAPLENGTH_MASK (0xff) #define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv) @@ -26,13 +29,14 @@ struct ehci_hcd_mv { /* Which mode does this ehci running OTG/Host ? */ int mode; - void __iomem *phy_regs; + void __iomem *base; void __iomem *cap_regs; void __iomem *op_regs; struct usb_phy *otg; struct mv_usb_platform_data *pdata; + struct phy *phy; struct clk *clk; }; @@ -49,22 +53,13 @@ static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) { - int retval; - ehci_clock_enable(ehci_mv); - if (ehci_mv->pdata->phy_init) { - retval = ehci_mv->pdata->phy_init(ehci_mv->phy_regs); - if (retval) - return retval; - } - - return 0; + return phy_init(ehci_mv->phy); } static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) { - if (ehci_mv->pdata->phy_deinit) - ehci_mv->pdata->phy_deinit(ehci_mv->phy_regs); + phy_exit(ehci_mv->phy); ehci_clock_disable(ehci_mv); } @@ -121,6 +116,14 @@ static int mv_ehci_probe(struct platform_device *pdev) ehci_mv = hcd_to_ehci_hcd_mv(hcd); ehci_mv->pdata = pdata; + ehci_mv->phy = devm_phy_get(&pdev->dev, "usb"); + if (IS_ERR(ehci_mv->phy)) { + retval = PTR_ERR(ehci_mv->phy); + if (retval != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get phy.\n"); + goto err_put_hcd; + } + ehci_mv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(ehci_mv->clk)) { dev_err(&pdev->dev, "error getting clock\n"); @@ -128,17 +131,12 @@ static int mv_ehci_probe(struct platform_device *pdev) goto err_put_hcd; } - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); - ehci_mv->phy_regs = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(ehci_mv->phy_regs)) { - retval = PTR_ERR(ehci_mv->phy_regs); - goto err_put_hcd; - } - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); - ehci_mv->cap_regs = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(ehci_mv->cap_regs)) { - retval = PTR_ERR(ehci_mv->cap_regs); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ehci_mv->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(ehci_mv->base)) { + retval = PTR_ERR(ehci_mv->base); goto err_put_hcd; } @@ -148,6 +146,8 @@ static int mv_ehci_probe(struct platform_device *pdev) goto err_put_hcd; } + ehci_mv->cap_regs = + (void __iomem *) ((unsigned long) ehci_mv->base + U2x_CAPREGS_OFFSET); offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; ehci_mv->op_regs = (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); -- cgit v1.2.3-58-ga151 From 813e18b18a87f31c5b216ea7546127deac3ae1ae Mon Sep 17 00:00:00 2001 From: Lubomir Rintel <lkundrak@v3.sk> Date: Wed, 22 Aug 2018 22:43:04 +0200 Subject: USB: EHCI: ehci-mv: add DT support Add Device tree support. Signed-off-by: Lubomir Rintel <lkundrak@v3.sk> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/ehci-mv.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index 43b300c90875..f26109eafdbf 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -34,11 +34,11 @@ struct ehci_hcd_mv { void __iomem *op_regs; struct usb_phy *otg; + struct clk *clk; - struct mv_usb_platform_data *pdata; struct phy *phy; - struct clk *clk; + int (*set_vbus)(unsigned int vbus); }; static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) @@ -100,11 +100,6 @@ static int mv_ehci_probe(struct platform_device *pdev) int retval = -ENODEV; u32 offset; - if (!pdata) { - dev_err(&pdev->dev, "missing platform_data\n"); - return -ENODEV; - } - if (usb_disabled()) return -ENODEV; @@ -114,7 +109,12 @@ static int mv_ehci_probe(struct platform_device *pdev) platform_set_drvdata(pdev, hcd); ehci_mv = hcd_to_ehci_hcd_mv(hcd); - ehci_mv->pdata = pdata; + + ehci_mv->mode = MV_USB_MODE_HOST; + if (pdata) { + ehci_mv->mode = pdata->mode; + ehci_mv->set_vbus = pdata->set_vbus; + } ehci_mv->phy = devm_phy_get(&pdev->dev, "usb"); if (IS_ERR(ehci_mv->phy)) { @@ -166,7 +166,6 @@ static int mv_ehci_probe(struct platform_device *pdev) ehci = hcd_to_ehci(hcd); ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs; - ehci_mv->mode = pdata->mode; if (ehci_mv->mode == MV_USB_MODE_OTG) { ehci_mv->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); if (IS_ERR(ehci_mv->otg)) { @@ -191,8 +190,8 @@ static int mv_ehci_probe(struct platform_device *pdev) /* otg will enable clock before use as host */ mv_ehci_disable(ehci_mv); } else { - if (pdata->set_vbus) - pdata->set_vbus(1); + if (ehci_mv->set_vbus) + ehci_mv->set_vbus(1); retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); if (retval) { @@ -211,8 +210,8 @@ static int mv_ehci_probe(struct platform_device *pdev) return 0; err_set_vbus: - if (pdata->set_vbus) - pdata->set_vbus(0); + if (ehci_mv->set_vbus) + ehci_mv->set_vbus(0); err_disable_clk: mv_ehci_disable(ehci_mv); err_put_hcd: @@ -233,8 +232,8 @@ static int mv_ehci_remove(struct platform_device *pdev) otg_set_host(ehci_mv->otg->otg, NULL); if (ehci_mv->mode == MV_USB_MODE_HOST) { - if (ehci_mv->pdata->set_vbus) - ehci_mv->pdata->set_vbus(0); + if (ehci_mv->set_vbus) + ehci_mv->set_vbus(0); mv_ehci_disable(ehci_mv); } @@ -265,14 +264,20 @@ static void mv_ehci_shutdown(struct platform_device *pdev) hcd->driver->shutdown(hcd); } +static const struct of_device_id ehci_mv_dt_ids[] = { + { .compatible = "marvell,pxau2o-ehci", }, + {}, +}; + static struct platform_driver ehci_mv_driver = { .probe = mv_ehci_probe, .remove = mv_ehci_remove, .shutdown = mv_ehci_shutdown, .driver = { - .name = "mv-ehci", - .bus = &platform_bus_type, - }, + .name = "mv-ehci", + .bus = &platform_bus_type, + .of_match_table = ehci_mv_dt_ids, + }, .id_table = ehci_id_table, }; -- cgit v1.2.3-58-ga151 From f13912d3f014a7f2fa5c35d25ee8c3f96bda6272 Mon Sep 17 00:00:00 2001 From: Saranya Gopal <saranya.gopal@intel.com> Date: Wed, 12 Sep 2018 08:46:26 +0530 Subject: usbcore: Select UAC3 configuration for audio if present USB audio class 3.0 specification introduced many significant changes like - new power domains, support for LPM/L1 - new cluster descriptor - new high capability and class-specific string descriptors - BADD profiles - ... and many other things (check spec from link below: http://www.usb.org/developers/docs/devclass_docs/USB_Audio_v3.0.zip) Now that UAC3 is supported in linux, choose UAC3 configuration for audio if the device supports it. Selecting this configuration will enable the system to save power by leveraging the new power domains and LPM L1 capability and also support new codec types and data formats for consumer audio applications. Signed-off-by: Saranya Gopal <saranya.gopal@intel.com> Reviewed-by: Felipe Balbi <felipe.balbi@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/core/generic.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index bc8242bc4564..356b05c82dbc 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -21,6 +21,7 @@ #include <linux/usb.h> #include <linux/usb/hcd.h> +#include <uapi/linux/usb/audio.h> #include "usb.h" static inline const char *plural(int n) @@ -42,6 +43,16 @@ static int is_activesync(struct usb_interface_descriptor *desc) && desc->bInterfaceProtocol == 1; } +static bool is_audio(struct usb_interface_descriptor *desc) +{ + return desc->bInterfaceClass == USB_CLASS_AUDIO; +} + +static bool is_uac3_config(struct usb_interface_descriptor *desc) +{ + return desc->bInterfaceProtocol == UAC_VERSION_3; +} + int usb_choose_configuration(struct usb_device *udev) { int i; @@ -121,6 +132,22 @@ int usb_choose_configuration(struct usb_device *udev) #endif } + /* + * Select first configuration as default for audio so that + * devices that don't comply with UAC3 protocol are supported. + * But, still iterate through other configurations and + * select UAC3 compliant config if present. + */ + if (i == 0 && num_configs > 1 && desc && is_audio(desc)) { + best = c; + continue; + } + + if (i > 0 && desc && is_audio(desc) && is_uac3_config(desc)) { + best = c; + break; + } + /* From the remaining configs, choose the first one whose * first interface is for a non-vendor-specific class. * Reason: Linux is more likely to have a class driver -- cgit v1.2.3-58-ga151 From f181dbb4824130e84f46e5be5b49cf6456f96683 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Tue, 11 Sep 2018 17:47:03 +0900 Subject: usb: renesas_usbhs: Add reset_control R-Car Gen3 needs to deassert resets of both host and peripheral. Since [eo]hci-platform is possible to assert the reset(s) when the probing failed, renesas_usbhs driver doesn't work correctly regardless of finished probing. To fix this issue, this patch adds reset_control on this renesas_usbhs driver. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/renesas_usbhs/common.c | 12 ++++++++++++ drivers/usb/renesas_usbhs/common.h | 2 ++ 2 files changed, 14 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 4310df46639d..1d355d59b5e4 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -12,6 +12,7 @@ #include <linux/of_device.h> #include <linux/of_gpio.h> #include <linux/pm_runtime.h> +#include <linux/reset.h> #include <linux/slab.h> #include <linux/sysfs.h> #include "common.h" @@ -574,6 +575,10 @@ static int usbhs_probe(struct platform_device *pdev) return PTR_ERR(priv->edev); } + priv->rsts = devm_reset_control_array_get_optional_shared(&pdev->dev); + if (IS_ERR(priv->rsts)) + return PTR_ERR(priv->rsts); + /* * care platform info */ @@ -658,6 +663,10 @@ static int usbhs_probe(struct platform_device *pdev) /* dev_set_drvdata should be called after usbhs_mod_init */ platform_set_drvdata(pdev, priv); + ret = reset_control_deassert(priv->rsts); + if (ret) + goto probe_fail_rst; + /* * deviece reset here because * USB device might be used in boot loader. @@ -711,6 +720,8 @@ static int usbhs_probe(struct platform_device *pdev) return ret; probe_end_mod_exit: + reset_control_assert(priv->rsts); +probe_fail_rst: usbhs_mod_remove(priv); probe_end_fifo_exit: usbhs_fifo_remove(priv); @@ -739,6 +750,7 @@ static int usbhs_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); usbhs_platform_call(priv, hardware_exit, pdev); + reset_control_assert(priv->rsts); usbhs_mod_remove(priv); usbhs_fifo_remove(priv); usbhs_pipe_remove(priv); diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index 6137f7942c05..bce7d35fed80 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -10,6 +10,7 @@ #include <linux/extcon.h> #include <linux/platform_device.h> +#include <linux/reset.h> #include <linux/usb/renesas_usbhs.h> struct usbhs_priv; @@ -277,6 +278,7 @@ struct usbhs_priv { struct usbhs_fifo_info fifo_info; struct phy *phy; + struct reset_control *rsts; }; /* -- cgit v1.2.3-58-ga151 From 3df0e240caba641e0d70640e3baf34d34c105176 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Tue, 11 Sep 2018 17:47:05 +0900 Subject: usb: renesas_usbhs: Add multiple clocks management R-Car Gen3 needs to enable clocks of both host and peripheral. Since [eo]hci-platform disables the reset(s) when the drivers are removed, renesas_usbhs driver doesn't work correctly. To fix this issue, this patch adds multiple clocks management on this renesas_usbhs driver. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/renesas_usbhs/common.c | 88 ++++++++++++++++++++++++++++++++++++++ drivers/usb/renesas_usbhs/common.h | 2 + 2 files changed, 90 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 1d355d59b5e4..d6c39ba73190 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -5,6 +5,7 @@ * Copyright (C) 2011 Renesas Solutions Corp. * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> */ +#include <linux/clk.h> #include <linux/err.h> #include <linux/gpio.h> #include <linux/io.h> @@ -291,6 +292,79 @@ static void usbhsc_set_buswait(struct usbhs_priv *priv) usbhs_bset(priv, BUSWAIT, 0x000F, wait); } +static bool usbhsc_is_multi_clks(struct usbhs_priv *priv) +{ + if (priv->dparam.type == USBHS_TYPE_RCAR_GEN3 || + priv->dparam.type == USBHS_TYPE_RCAR_GEN3_WITH_PLL) + return true; + + return false; +} + +static int usbhsc_clk_get(struct device *dev, struct usbhs_priv *priv) +{ + if (!usbhsc_is_multi_clks(priv)) + return 0; + + /* The first clock should exist */ + priv->clks[0] = of_clk_get(dev->of_node, 0); + if (IS_ERR(priv->clks[0])) + return PTR_ERR(priv->clks[0]); + + /* + * To backward compatibility with old DT, this driver checks the return + * value if it's -ENOENT or not. + */ + priv->clks[1] = of_clk_get(dev->of_node, 1); + if (PTR_ERR(priv->clks[1]) == -ENOENT) + priv->clks[1] = NULL; + else if (IS_ERR(priv->clks[1])) + return PTR_ERR(priv->clks[1]); + + return 0; +} + +static void usbhsc_clk_put(struct usbhs_priv *priv) +{ + int i; + + if (!usbhsc_is_multi_clks(priv)) + return; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) + clk_put(priv->clks[i]); +} + +static int usbhsc_clk_prepare_enable(struct usbhs_priv *priv) +{ + int i, ret; + + if (!usbhsc_is_multi_clks(priv)) + return 0; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) { + ret = clk_prepare_enable(priv->clks[i]); + if (ret) { + while (--i >= 0) + clk_disable_unprepare(priv->clks[i]); + return ret; + } + } + + return ret; +} + +static void usbhsc_clk_disable_unprepare(struct usbhs_priv *priv) +{ + int i; + + if (!usbhsc_is_multi_clks(priv)) + return; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) + clk_disable_unprepare(priv->clks[i]); +} + /* * platform default param */ @@ -341,6 +415,10 @@ static void usbhsc_power_ctrl(struct usbhs_priv *priv, int enable) /* enable PM */ pm_runtime_get_sync(dev); + /* enable clks */ + if (usbhsc_clk_prepare_enable(priv)) + return; + /* enable platform power */ usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); @@ -353,6 +431,9 @@ static void usbhsc_power_ctrl(struct usbhs_priv *priv, int enable) /* disable platform power */ usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); + /* disable clks */ + usbhsc_clk_disable_unprepare(priv); + /* disable PM */ pm_runtime_put_sync(dev); } @@ -667,6 +748,10 @@ static int usbhs_probe(struct platform_device *pdev) if (ret) goto probe_fail_rst; + ret = usbhsc_clk_get(&pdev->dev, priv); + if (ret) + goto probe_fail_clks; + /* * deviece reset here because * USB device might be used in boot loader. @@ -720,6 +805,8 @@ static int usbhs_probe(struct platform_device *pdev) return ret; probe_end_mod_exit: + usbhsc_clk_put(priv); +probe_fail_clks: reset_control_assert(priv->rsts); probe_fail_rst: usbhs_mod_remove(priv); @@ -750,6 +837,7 @@ static int usbhs_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); usbhs_platform_call(priv, hardware_exit, pdev); + usbhsc_clk_put(priv); reset_control_assert(priv->rsts); usbhs_mod_remove(priv); usbhs_fifo_remove(priv); diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index bce7d35fed80..555b3e788c6d 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -8,6 +8,7 @@ #ifndef RENESAS_USB_DRIVER_H #define RENESAS_USB_DRIVER_H +#include <linux/clk.h> #include <linux/extcon.h> #include <linux/platform_device.h> #include <linux/reset.h> @@ -279,6 +280,7 @@ struct usbhs_priv { struct phy *phy; struct reset_control *rsts; + struct clk *clks[2]; }; /* -- cgit v1.2.3-58-ga151 From 46216506ceacc36eea7535c4a72b77b38fe4b664 Mon Sep 17 00:00:00 2001 From: Linus Walleij <linus.walleij@linaro.org> Date: Tue, 18 Sep 2018 16:25:06 -0700 Subject: usb: host: fotg2: Fix potential NULL dereference There is code in the .remove() hook to handle the drvdata being NULL, for good reasons: it is never set, so it will always be NULL. As I moved code around, static checkers start complaining. Instead of this, make sure to always set it on successful probe so we can always dereference it on the remove path. Use the platform_device_[set|get]_drvdata() since this is a platform device. Fixes: ffa8a31b5b3b ("usb: host: fotg2: add silicon clock handling") Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Cc: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/fotg210-hcd.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index 058ff82ea789..bbcc68179bfc 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -5636,6 +5636,7 @@ static int fotg210_hcd_probe(struct platform_device *pdev) goto failed_dis_clk; } device_wakeup_enable(hcd->self.controller); + platform_set_drvdata(pdev, hcd); return retval; @@ -5656,16 +5657,12 @@ fail_create_hcd: */ static int fotg210_hcd_remove(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - struct usb_hcd *hcd = dev_get_drvdata(dev); + struct usb_hcd *hcd = platform_get_drvdata(pdev); struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); if (!IS_ERR(fotg210->pclk)) clk_disable_unprepare(fotg210->pclk); - if (!hcd) - return 0; - usb_remove_hcd(hcd); usb_put_hcd(hcd); -- cgit v1.2.3-58-ga151 From 818eecfd56409ca78fc3e4d841f30833699f70b6 Mon Sep 17 00:00:00 2001 From: Colin Ian King <colin.king@canonical.com> Date: Tue, 18 Sep 2018 08:54:11 +0200 Subject: usb: phy: mxs: fix spelling mistake "stardard" -> "standard" Trivial fix to spelling mistake in dev_dbg message Signed-off-by: Colin Ian King <colin.king@canonical.com> Acked-by: Peter Chen <peter.chen@nxp.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/phy/phy-mxs-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index e5aa24c1e4fd..1b1bb0ad40c3 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -563,7 +563,7 @@ static enum usb_charger_type mxs_charger_primary_detection(struct mxs_phy *x) regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) { chgr_type = SDP_TYPE; - dev_dbg(x->phy.dev, "It is a stardard downstream port\n"); + dev_dbg(x->phy.dev, "It is a standard downstream port\n"); } /* Disable charger detector */ -- cgit v1.2.3-58-ga151 From 23481121c81d984193edf1532f5e123637e50903 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus <heikki.krogerus@linux.intel.com> Date: Thu, 20 Sep 2018 14:23:44 +0300 Subject: usb: typec: class: Don't use port parent for getting mux handles It is not possible to use the parent of the port device when requesting mux handles as the parent may be a multiport USB Type-C or PD controller. The muxes must be assigned to the ports, not the controllers. This will also move the requesting of the muxes after the port device is initialized. Acked-by: Hans de Goede <hdegoede@redhat.com> Tested-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/class.c | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index e61dffb27a0c..00141e05bc72 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1500,7 +1500,7 @@ typec_port_register_altmode(struct typec_port *port, sprintf(id, "id%04xm%02x", desc->svid, desc->mode); - mux = typec_mux_get(port->dev.parent, id); + mux = typec_mux_get(&port->dev, id); if (IS_ERR(mux)) return ERR_CAST(mux); @@ -1540,18 +1540,6 @@ struct typec_port *typec_register_port(struct device *parent, return ERR_PTR(id); } - port->sw = typec_switch_get(cap->fwnode ? &port->dev : parent); - if (IS_ERR(port->sw)) { - ret = PTR_ERR(port->sw); - goto err_switch; - } - - port->mux = typec_mux_get(parent, "typec-mux"); - if (IS_ERR(port->mux)) { - ret = PTR_ERR(port->mux); - goto err_mux; - } - switch (cap->type) { case TYPEC_PORT_SRC: port->pwr_role = TYPEC_SOURCE; @@ -1592,13 +1580,26 @@ struct typec_port *typec_register_port(struct device *parent, port->port_type = cap->type; port->prefer_role = cap->prefer_role; + device_initialize(&port->dev); port->dev.class = typec_class; port->dev.parent = parent; port->dev.fwnode = cap->fwnode; port->dev.type = &typec_port_dev_type; dev_set_name(&port->dev, "port%d", id); - ret = device_register(&port->dev); + port->sw = typec_switch_get(&port->dev); + if (IS_ERR(port->sw)) { + put_device(&port->dev); + return ERR_CAST(port->sw); + } + + port->mux = typec_mux_get(&port->dev, "typec-mux"); + if (IS_ERR(port->mux)) { + put_device(&port->dev); + return ERR_CAST(port->mux); + } + + ret = device_add(&port->dev); if (ret) { dev_err(parent, "failed to register port (%d)\n", ret); put_device(&port->dev); @@ -1606,15 +1607,6 @@ struct typec_port *typec_register_port(struct device *parent, } return port; - -err_mux: - typec_switch_put(port->sw); - -err_switch: - ida_simple_remove(&typec_index_ida, port->id); - kfree(port); - - return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(typec_register_port); -- cgit v1.2.3-58-ga151 From c800c51f586b3c3dda59f5f08d3df9bd1ae09e3d Mon Sep 17 00:00:00 2001 From: Heikki Krogerus <heikki.krogerus@linux.intel.com> Date: Thu, 20 Sep 2018 14:23:46 +0300 Subject: usb: typec: fusb302: reorganizing the probe function a little The debugfs needs to be initialized as the last step in probe in this case. The struct dentry *rootdir can't be pointing to anything unless driver probe really finishes successfully. It is also not necessary to clear the i2c clientdata if the probe fails, so removing the extra label used for that. Acked-by: Hans de Goede <hdegoede@redhat.com> Tested-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/fusb302/fusb302.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c index 1cb0cd2a4e63..6e9370a813f7 100644 --- a/drivers/usb/typec/fusb302/fusb302.c +++ b/drivers/usb/typec/fusb302/fusb302.c @@ -1719,7 +1719,6 @@ static int fusb302_probe(struct i2c_client *client, return -ENOMEM; chip->i2c_client = client; - i2c_set_clientdata(client, chip); chip->dev = &client->dev; chip->tcpc_config = fusb302_tcpc_config; chip->tcpc_dev.config = &chip->tcpc_config; @@ -1748,22 +1747,17 @@ static int fusb302_probe(struct i2c_client *client, return -EPROBE_DEFER; } - fusb302_debugfs_init(chip); + chip->vbus = devm_regulator_get(chip->dev, "vbus"); + if (IS_ERR(chip->vbus)) + return PTR_ERR(chip->vbus); chip->wq = create_singlethread_workqueue(dev_name(chip->dev)); - if (!chip->wq) { - ret = -ENOMEM; - goto clear_client_data; - } + if (!chip->wq) + return -ENOMEM; + INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); init_tcpc_dev(&chip->tcpc_dev); - chip->vbus = devm_regulator_get(chip->dev, "vbus"); - if (IS_ERR(chip->vbus)) { - ret = PTR_ERR(chip->vbus); - goto destroy_workqueue; - } - if (client->irq) { chip->gpio_int_n_irq = client->irq; } else { @@ -1789,15 +1783,15 @@ static int fusb302_probe(struct i2c_client *client, goto tcpm_unregister_port; } enable_irq_wake(chip->gpio_int_n_irq); + fusb302_debugfs_init(chip); + i2c_set_clientdata(client, chip); + return ret; tcpm_unregister_port: tcpm_unregister_port(chip->tcpm_port); destroy_workqueue: destroy_workqueue(chip->wq); -clear_client_data: - i2c_set_clientdata(client, NULL); - fusb302_debugfs_exit(chip); return ret; } @@ -1808,7 +1802,6 @@ static int fusb302_remove(struct i2c_client *client) tcpm_unregister_port(chip->tcpm_port); destroy_workqueue(chip->wq); - i2c_set_clientdata(client, NULL); fusb302_debugfs_exit(chip); return 0; -- cgit v1.2.3-58-ga151 From ae8a2ca8a2215c7e31e6d874f7303801bb15fbbc Mon Sep 17 00:00:00 2001 From: Heikki Krogerus <heikki.krogerus@linux.intel.com> Date: Thu, 20 Sep 2018 14:23:47 +0300 Subject: usb: typec: Group all TCPCI/TCPM code together Moving all the drivers that depend on the Port Controller Manager under a new directory drivers/usb/typec/tcpm/ and making Guenter Roeck the designated reviewer of that code. Acked-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- MAINTAINERS | 6 + drivers/usb/typec/Kconfig | 45 +- drivers/usb/typec/Makefile | 6 +- drivers/usb/typec/fusb302/Kconfig | 7 - drivers/usb/typec/fusb302/Makefile | 2 - drivers/usb/typec/fusb302/fusb302.c | 1861 ------------ drivers/usb/typec/fusb302/fusb302_reg.h | 177 -- drivers/usb/typec/tcpci.c | 612 ---- drivers/usb/typec/tcpci.h | 139 - drivers/usb/typec/tcpci_rt1711h.c | 312 -- drivers/usb/typec/tcpm.c | 4851 ------------------------------- drivers/usb/typec/tcpm/Kconfig | 52 + drivers/usb/typec/tcpm/Makefile | 7 + drivers/usb/typec/tcpm/fusb302.c | 1861 ++++++++++++ drivers/usb/typec/tcpm/fusb302_reg.h | 177 ++ drivers/usb/typec/tcpm/tcpci.c | 612 ++++ drivers/usb/typec/tcpm/tcpci.h | 139 + drivers/usb/typec/tcpm/tcpci_rt1711h.c | 312 ++ drivers/usb/typec/tcpm/tcpm.c | 4851 +++++++++++++++++++++++++++++++ drivers/usb/typec/tcpm/wcove.c | 693 +++++ drivers/usb/typec/typec_wcove.c | 693 ----- 21 files changed, 8712 insertions(+), 8703 deletions(-) delete mode 100644 drivers/usb/typec/fusb302/Kconfig delete mode 100644 drivers/usb/typec/fusb302/Makefile delete mode 100644 drivers/usb/typec/fusb302/fusb302.c delete mode 100644 drivers/usb/typec/fusb302/fusb302_reg.h delete mode 100644 drivers/usb/typec/tcpci.c delete mode 100644 drivers/usb/typec/tcpci.h delete mode 100644 drivers/usb/typec/tcpci_rt1711h.c delete mode 100644 drivers/usb/typec/tcpm.c create mode 100644 drivers/usb/typec/tcpm/Kconfig create mode 100644 drivers/usb/typec/tcpm/Makefile create mode 100644 drivers/usb/typec/tcpm/fusb302.c create mode 100644 drivers/usb/typec/tcpm/fusb302_reg.h create mode 100644 drivers/usb/typec/tcpm/tcpci.c create mode 100644 drivers/usb/typec/tcpm/tcpci.h create mode 100644 drivers/usb/typec/tcpm/tcpci_rt1711h.c create mode 100644 drivers/usb/typec/tcpm/tcpm.c create mode 100644 drivers/usb/typec/tcpm/wcove.c delete mode 100644 drivers/usb/typec/typec_wcove.c (limited to 'drivers/usb') diff --git a/MAINTAINERS b/MAINTAINERS index 4ece30f15777..9dff31e38fac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15286,6 +15286,12 @@ F: Documentation/driver-api/usb/typec_bus.rst F: drivers/usb/typec/altmodes/ F: include/linux/usb/typec_altmode.h +USB TYPEC PORT CONTROLLER DRIVERS +M: Guenter Roeck <linux@roeck-us.net> +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/typec/tcpm/ + USB UHCI DRIVER M: Alan Stern <stern@rowland.harvard.edu> L: linux-usb@vger.kernel.org diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 00878c386dd0..30a847c2089d 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -45,50 +45,7 @@ menuconfig TYPEC if TYPEC -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. - -if TYPEC_TCPM - -config TYPEC_TCPCI - tristate "Type-C Port Controller Interface driver" - depends on I2C - select REGMAP_I2C - help - Type-C Port Controller driver for TCPCI-compliant controller. - -config TYPEC_RT1711H - tristate "Richtek RT1711H Type-C chip driver" - depends on I2C - select TYPEC_TCPCI - help - Richtek RT1711H Type-C chip driver that works with - Type-C Port Controller Manager to provide USB PD and USB - Type-C functionalities. - -source "drivers/usb/typec/fusb302/Kconfig" - -config TYPEC_WCOVE - tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver" - depends on ACPI - depends on INTEL_SOC_PMIC - depends on INTEL_PMC_IPC - depends on BXT_WC_PMIC_OPREGION - help - This driver adds support for USB Type-C detection on Intel Broxton - platforms that have Intel Whiskey Cove PMIC. The driver can detect the - role and cable orientation. - - To compile this driver as module, choose M here: the module will be - called typec_wcove - -endif # TYPEC_TCPM +source "drivers/usb/typec/tcpm/Kconfig" source "drivers/usb/typec/ucsi/Kconfig" diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 45b0aef428a8..6696b7263d61 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -2,11 +2,7 @@ obj-$(CONFIG_TYPEC) += typec.o typec-y := class.o mux.o bus.o obj-$(CONFIG_TYPEC) += altmodes/ -obj-$(CONFIG_TYPEC_TCPM) += tcpm.o -obj-y += fusb302/ -obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o +obj-$(CONFIG_TYPEC_TCPM) += tcpm/ obj-$(CONFIG_TYPEC_UCSI) += ucsi/ obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o obj-$(CONFIG_TYPEC) += mux/ -obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o -obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o diff --git a/drivers/usb/typec/fusb302/Kconfig b/drivers/usb/typec/fusb302/Kconfig deleted file mode 100644 index fce099ff39fe..000000000000 --- a/drivers/usb/typec/fusb302/Kconfig +++ /dev/null @@ -1,7 +0,0 @@ -config TYPEC_FUSB302 - tristate "Fairchild FUSB302 Type-C chip driver" - 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 - Type-C functionalities. diff --git a/drivers/usb/typec/fusb302/Makefile b/drivers/usb/typec/fusb302/Makefile deleted file mode 100644 index 3b51b33631a0..000000000000 --- a/drivers/usb/typec/fusb302/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c deleted file mode 100644 index 6e9370a813f7..000000000000 --- a/drivers/usb/typec/fusb302/fusb302.c +++ /dev/null @@ -1,1861 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright 2016-2017 Google, Inc - * - * Fairchild FUSB302 Type-C Chip Driver - */ - -#include <linux/debugfs.h> -#include <linux/delay.h> -#include <linux/errno.h> -#include <linux/extcon.h> -#include <linux/gpio.h> -#include <linux/i2c.h> -#include <linux/interrupt.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/of_device.h> -#include <linux/of_gpio.h> -#include <linux/pinctrl/consumer.h> -#include <linux/proc_fs.h> -#include <linux/regulator/consumer.h> -#include <linux/sched/clock.h> -#include <linux/seq_file.h> -#include <linux/slab.h> -#include <linux/string.h> -#include <linux/types.h> -#include <linux/usb/typec.h> -#include <linux/usb/tcpm.h> -#include <linux/usb/pd.h> -#include <linux/workqueue.h> - -#include "fusb302_reg.h" - -/* - * When the device is SNK, BC_LVL interrupt is used to monitor cc pins - * for the current capability offered by the SRC. As FUSB302 chip fires - * the BC_LVL interrupt on PD signalings, cc lvl should be handled after - * a delay to avoid measuring on PD activities. The delay is slightly - * longer than PD_T_PD_DEBPUNCE (10-20ms). - */ -#define T_BC_LVL_DEBOUNCE_DELAY_MS 30 - -enum toggling_mode { - TOGGLINE_MODE_OFF, - TOGGLING_MODE_DRP, - TOGGLING_MODE_SNK, - TOGGLING_MODE_SRC, -}; - -enum src_current_status { - SRC_CURRENT_DEFAULT, - SRC_CURRENT_MEDIUM, - SRC_CURRENT_HIGH, -}; - -static const u8 ra_mda_value[] = { - [SRC_CURRENT_DEFAULT] = 4, /* 210mV */ - [SRC_CURRENT_MEDIUM] = 9, /* 420mV */ - [SRC_CURRENT_HIGH] = 18, /* 798mV */ -}; - -static const u8 rd_mda_value[] = { - [SRC_CURRENT_DEFAULT] = 38, /* 1638mV */ - [SRC_CURRENT_MEDIUM] = 38, /* 1638mV */ - [SRC_CURRENT_HIGH] = 61, /* 2604mV */ -}; - -#define LOG_BUFFER_ENTRIES 1024 -#define LOG_BUFFER_ENTRY_SIZE 128 - -struct fusb302_chip { - struct device *dev; - struct i2c_client *i2c_client; - struct tcpm_port *tcpm_port; - struct tcpc_dev tcpc_dev; - struct tcpc_config tcpc_config; - - struct regulator *vbus; - - int gpio_int_n; - int gpio_int_n_irq; - struct extcon_dev *extcon; - - struct workqueue_struct *wq; - struct delayed_work bc_lvl_handler; - - atomic_t pm_suspend; - atomic_t i2c_busy; - - /* lock for sharing chip states */ - struct mutex lock; - - /* chip status */ - enum toggling_mode toggling_mode; - enum src_current_status src_current_status; - bool intr_togdone; - bool intr_bc_lvl; - bool intr_comp_chng; - - /* port status */ - bool pull_up; - bool vconn_on; - bool vbus_on; - bool charge_on; - bool vbus_present; - 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; - /* lock for log buffer access */ - struct mutex logbuffer_lock; - int logbuffer_head; - int logbuffer_tail; - u8 *logbuffer[LOG_BUFFER_ENTRIES]; -#endif -}; - -/* - * Logging - */ - -#ifdef CONFIG_DEBUG_FS - -static bool fusb302_log_full(struct fusb302_chip *chip) -{ - return chip->logbuffer_tail == - (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; -} - -static void _fusb302_log(struct fusb302_chip *chip, const char *fmt, - va_list args) -{ - char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; - u64 ts_nsec = local_clock(); - unsigned long rem_nsec; - - if (!chip->logbuffer[chip->logbuffer_head]) { - chip->logbuffer[chip->logbuffer_head] = - kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); - if (!chip->logbuffer[chip->logbuffer_head]) - return; - } - - vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); - - mutex_lock(&chip->logbuffer_lock); - - if (fusb302_log_full(chip)) { - chip->logbuffer_head = max(chip->logbuffer_head - 1, 0); - strlcpy(tmpbuffer, "overflow", sizeof(tmpbuffer)); - } - - if (chip->logbuffer_head < 0 || - chip->logbuffer_head >= LOG_BUFFER_ENTRIES) { - dev_warn(chip->dev, - "Bad log buffer index %d\n", chip->logbuffer_head); - goto abort; - } - - if (!chip->logbuffer[chip->logbuffer_head]) { - dev_warn(chip->dev, - "Log buffer index %d is NULL\n", chip->logbuffer_head); - goto abort; - } - - rem_nsec = do_div(ts_nsec, 1000000000); - scnprintf(chip->logbuffer[chip->logbuffer_head], - LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", - (unsigned long)ts_nsec, rem_nsec / 1000, - tmpbuffer); - chip->logbuffer_head = (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; - -abort: - mutex_unlock(&chip->logbuffer_lock); -} - -static void fusb302_log(struct fusb302_chip *chip, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - _fusb302_log(chip, fmt, args); - va_end(args); -} - -static int fusb302_debug_show(struct seq_file *s, void *v) -{ - struct fusb302_chip *chip = (struct fusb302_chip *)s->private; - int tail; - - mutex_lock(&chip->logbuffer_lock); - tail = chip->logbuffer_tail; - while (tail != chip->logbuffer_head) { - seq_printf(s, "%s\n", chip->logbuffer[tail]); - tail = (tail + 1) % LOG_BUFFER_ENTRIES; - } - if (!seq_has_overflowed(s)) - chip->logbuffer_tail = tail; - mutex_unlock(&chip->logbuffer_lock); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(fusb302_debug); - -static struct dentry *rootdir; - -static void fusb302_debugfs_init(struct fusb302_chip *chip) -{ - mutex_init(&chip->logbuffer_lock); - if (!rootdir) - rootdir = debugfs_create_dir("fusb302", NULL); - - chip->dentry = debugfs_create_file(dev_name(chip->dev), - S_IFREG | 0444, rootdir, - chip, &fusb302_debug_fops); -} - -static void fusb302_debugfs_exit(struct fusb302_chip *chip) -{ - debugfs_remove(chip->dentry); - debugfs_remove(rootdir); -} - -#else - -static void fusb302_log(const struct fusb302_chip *chip, - const char *fmt, ...) { } -static void fusb302_debugfs_init(const struct fusb302_chip *chip) { } -static void fusb302_debugfs_exit(const struct fusb302_chip *chip) { } - -#endif - -#define FUSB302_RESUME_RETRY 10 -#define FUSB302_RESUME_RETRY_SLEEP 50 - -static bool fusb302_is_suspended(struct fusb302_chip *chip) -{ - int retry_cnt; - - for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) { - if (atomic_read(&chip->pm_suspend)) { - dev_err(chip->dev, "i2c: pm suspend, retry %d/%d\n", - retry_cnt + 1, FUSB302_RESUME_RETRY); - msleep(FUSB302_RESUME_RETRY_SLEEP); - } else { - return false; - } - } - - return true; -} - -static int fusb302_i2c_write(struct fusb302_chip *chip, - u8 address, u8 data) -{ - int ret = 0; - - atomic_set(&chip->i2c_busy, 1); - - if (fusb302_is_suspended(chip)) { - atomic_set(&chip->i2c_busy, 0); - return -ETIMEDOUT; - } - - ret = i2c_smbus_write_byte_data(chip->i2c_client, address, data); - if (ret < 0) - fusb302_log(chip, "cannot write 0x%02x to 0x%02x, ret=%d", - data, address, ret); - atomic_set(&chip->i2c_busy, 0); - - return ret; -} - -static int fusb302_i2c_block_write(struct fusb302_chip *chip, u8 address, - u8 length, const u8 *data) -{ - int ret = 0; - - if (length <= 0) - return ret; - atomic_set(&chip->i2c_busy, 1); - - if (fusb302_is_suspended(chip)) { - atomic_set(&chip->i2c_busy, 0); - return -ETIMEDOUT; - } - - ret = i2c_smbus_write_i2c_block_data(chip->i2c_client, address, - length, data); - if (ret < 0) - fusb302_log(chip, "cannot block write 0x%02x, len=%d, ret=%d", - address, length, ret); - atomic_set(&chip->i2c_busy, 0); - - return ret; -} - -static int fusb302_i2c_read(struct fusb302_chip *chip, - u8 address, u8 *data) -{ - int ret = 0; - - atomic_set(&chip->i2c_busy, 1); - - if (fusb302_is_suspended(chip)) { - atomic_set(&chip->i2c_busy, 0); - return -ETIMEDOUT; - } - - ret = i2c_smbus_read_byte_data(chip->i2c_client, address); - *data = (u8)ret; - if (ret < 0) - fusb302_log(chip, "cannot read %02x, ret=%d", address, ret); - atomic_set(&chip->i2c_busy, 0); - - return ret; -} - -static int fusb302_i2c_block_read(struct fusb302_chip *chip, u8 address, - u8 length, u8 *data) -{ - int ret = 0; - - if (length <= 0) - return ret; - atomic_set(&chip->i2c_busy, 1); - - if (fusb302_is_suspended(chip)) { - atomic_set(&chip->i2c_busy, 0); - return -ETIMEDOUT; - } - - ret = i2c_smbus_read_i2c_block_data(chip->i2c_client, address, - length, data); - if (ret < 0) { - fusb302_log(chip, "cannot block read 0x%02x, len=%d, ret=%d", - address, length, ret); - goto done; - } - if (ret != length) { - fusb302_log(chip, "only read %d/%d bytes from 0x%02x", - ret, length, address); - ret = -EIO; - } - -done: - atomic_set(&chip->i2c_busy, 0); - - return ret; -} - -static int fusb302_i2c_mask_write(struct fusb302_chip *chip, u8 address, - u8 mask, u8 value) -{ - int ret = 0; - u8 data; - - ret = fusb302_i2c_read(chip, address, &data); - if (ret < 0) - return ret; - data &= ~mask; - data |= value; - ret = fusb302_i2c_write(chip, address, data); - if (ret < 0) - return ret; - - return ret; -} - -static int fusb302_i2c_set_bits(struct fusb302_chip *chip, u8 address, - u8 set_bits) -{ - return fusb302_i2c_mask_write(chip, address, 0x00, set_bits); -} - -static int fusb302_i2c_clear_bits(struct fusb302_chip *chip, u8 address, - u8 clear_bits) -{ - return fusb302_i2c_mask_write(chip, address, clear_bits, 0x00); -} - -static int fusb302_sw_reset(struct fusb302_chip *chip) -{ - int ret = 0; - - ret = fusb302_i2c_write(chip, FUSB_REG_RESET, - FUSB_REG_RESET_SW_RESET); - if (ret < 0) - fusb302_log(chip, "cannot sw reset the chip, ret=%d", ret); - else - fusb302_log(chip, "sw reset"); - - return ret; -} - -static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip) -{ - int ret = 0; - - ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, - FUSB_REG_CONTROL3_N_RETRIES_3 | - FUSB_REG_CONTROL3_AUTO_RETRY); - - return ret; -} - -/* - * initialize interrupt on the chip - * - unmasked interrupt: VBUS_OK - */ -static int fusb302_init_interrupt(struct fusb302_chip *chip) -{ - int ret = 0; - - ret = fusb302_i2c_write(chip, FUSB_REG_MASK, - 0xFF & ~FUSB_REG_MASK_VBUSOK); - if (ret < 0) - return ret; - ret = fusb302_i2c_write(chip, FUSB_REG_MASKA, 0xFF); - if (ret < 0) - return ret; - ret = fusb302_i2c_write(chip, FUSB_REG_MASKB, 0xFF); - if (ret < 0) - return ret; - ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL0, - FUSB_REG_CONTROL0_INT_MASK); - if (ret < 0) - return ret; - - return ret; -} - -static int fusb302_set_power_mode(struct fusb302_chip *chip, u8 power_mode) -{ - int ret = 0; - - ret = fusb302_i2c_write(chip, FUSB_REG_POWER, power_mode); - - return ret; -} - -static int tcpm_init(struct tcpc_dev *dev) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - u8 data; - - ret = fusb302_sw_reset(chip); - if (ret < 0) - return ret; - ret = fusb302_enable_tx_auto_retries(chip); - if (ret < 0) - return ret; - ret = fusb302_init_interrupt(chip); - if (ret < 0) - return ret; - ret = fusb302_set_power_mode(chip, FUSB_REG_POWER_PWR_ALL); - if (ret < 0) - return ret; - ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &data); - if (ret < 0) - return ret; - chip->vbus_present = !!(data & FUSB_REG_STATUS0_VBUSOK); - ret = fusb302_i2c_read(chip, FUSB_REG_DEVICE_ID, &data); - if (ret < 0) - return ret; - fusb302_log(chip, "fusb302 device ID: 0x%02x", data); - - return ret; -} - -static int tcpm_get_vbus(struct tcpc_dev *dev) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - - mutex_lock(&chip->lock); - ret = chip->vbus_present ? 1 : 0; - mutex_unlock(&chip->lock); - - return ret; -} - -static int tcpm_get_current_limit(struct tcpc_dev *dev) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int current_limit = 0; - unsigned long timeout; - - if (!chip->extcon) - return 0; - - /* - * USB2 Charger detection may still be in progress when we get here, - * this can take upto 600ms, wait 800ms max. - */ - timeout = jiffies + msecs_to_jiffies(800); - do { - if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_SDP) == 1) - current_limit = 500; - - if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_CDP) == 1 || - extcon_get_state(chip->extcon, EXTCON_CHG_USB_ACA) == 1) - current_limit = 1500; - - if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_DCP) == 1) - current_limit = 2000; - - msleep(50); - } while (current_limit == 0 && time_before(jiffies, timeout)); - - return current_limit; -} - -static int fusb302_set_cc_pull(struct fusb302_chip *chip, - bool pull_up, bool pull_down) -{ - int ret = 0; - u8 data = 0x00; - u8 mask = FUSB_REG_SWITCHES0_CC1_PU_EN | - FUSB_REG_SWITCHES0_CC2_PU_EN | - FUSB_REG_SWITCHES0_CC1_PD_EN | - FUSB_REG_SWITCHES0_CC2_PD_EN; - - if (pull_up) - data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ? - FUSB_REG_SWITCHES0_CC1_PU_EN : - FUSB_REG_SWITCHES0_CC2_PU_EN; - if (pull_down) - data |= FUSB_REG_SWITCHES0_CC1_PD_EN | - FUSB_REG_SWITCHES0_CC2_PD_EN; - ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, - mask, data); - if (ret < 0) - return ret; - chip->pull_up = pull_up; - - return ret; -} - -static int fusb302_set_src_current(struct fusb302_chip *chip, - enum src_current_status status) -{ - int ret = 0; - - chip->src_current_status = status; - switch (status) { - case SRC_CURRENT_DEFAULT: - ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, - FUSB_REG_CONTROL0_HOST_CUR_MASK, - FUSB_REG_CONTROL0_HOST_CUR_DEF); - break; - case SRC_CURRENT_MEDIUM: - ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, - FUSB_REG_CONTROL0_HOST_CUR_MASK, - FUSB_REG_CONTROL0_HOST_CUR_MED); - break; - case SRC_CURRENT_HIGH: - ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, - FUSB_REG_CONTROL0_HOST_CUR_MASK, - FUSB_REG_CONTROL0_HOST_CUR_HIGH); - break; - default: - break; - } - - return ret; -} - -static int fusb302_set_toggling(struct fusb302_chip *chip, - enum toggling_mode mode) -{ - int ret = 0; - - /* first disable toggling */ - ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL2, - FUSB_REG_CONTROL2_TOGGLE); - if (ret < 0) - return ret; - /* mask interrupts for SRC or SNK */ - ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASK, - FUSB_REG_MASK_BC_LVL | - FUSB_REG_MASK_COMP_CHNG); - if (ret < 0) - return ret; - chip->intr_bc_lvl = false; - chip->intr_comp_chng = false; - /* configure toggling mode: none/snk/src/drp */ - switch (mode) { - case TOGGLINE_MODE_OFF: - ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, - FUSB_REG_CONTROL2_MODE_MASK, - FUSB_REG_CONTROL2_MODE_NONE); - if (ret < 0) - return ret; - break; - case TOGGLING_MODE_SNK: - ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, - FUSB_REG_CONTROL2_MODE_MASK, - FUSB_REG_CONTROL2_MODE_UFP); - if (ret < 0) - return ret; - break; - case TOGGLING_MODE_SRC: - ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, - FUSB_REG_CONTROL2_MODE_MASK, - FUSB_REG_CONTROL2_MODE_DFP); - if (ret < 0) - return ret; - break; - case TOGGLING_MODE_DRP: - ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, - FUSB_REG_CONTROL2_MODE_MASK, - FUSB_REG_CONTROL2_MODE_DRP); - if (ret < 0) - return ret; - break; - default: - break; - } - - if (mode == TOGGLINE_MODE_OFF) { - /* mask TOGDONE interrupt */ - ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, - FUSB_REG_MASKA_TOGDONE); - if (ret < 0) - return ret; - chip->intr_togdone = false; - } else { - /* unmask TOGDONE interrupt */ - ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, - FUSB_REG_MASKA_TOGDONE); - if (ret < 0) - return ret; - chip->intr_togdone = true; - /* start toggling */ - ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL2, - FUSB_REG_CONTROL2_TOGGLE); - if (ret < 0) - return ret; - /* during toggling, consider cc as Open */ - chip->cc1 = TYPEC_CC_OPEN; - chip->cc2 = TYPEC_CC_OPEN; - } - chip->toggling_mode = mode; - - return ret; -} - -static const char * const typec_cc_status_name[] = { - [TYPEC_CC_OPEN] = "Open", - [TYPEC_CC_RA] = "Ra", - [TYPEC_CC_RD] = "Rd", - [TYPEC_CC_RP_DEF] = "Rp-def", - [TYPEC_CC_RP_1_5] = "Rp-1.5", - [TYPEC_CC_RP_3_0] = "Rp-3.0", -}; - -static const enum src_current_status cc_src_current[] = { - [TYPEC_CC_OPEN] = SRC_CURRENT_DEFAULT, - [TYPEC_CC_RA] = SRC_CURRENT_DEFAULT, - [TYPEC_CC_RD] = SRC_CURRENT_DEFAULT, - [TYPEC_CC_RP_DEF] = SRC_CURRENT_DEFAULT, - [TYPEC_CC_RP_1_5] = SRC_CURRENT_MEDIUM, - [TYPEC_CC_RP_3_0] = SRC_CURRENT_HIGH, -}; - -static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - bool pull_up, pull_down; - u8 rd_mda; - - mutex_lock(&chip->lock); - switch (cc) { - case TYPEC_CC_OPEN: - pull_up = false; - pull_down = false; - break; - case TYPEC_CC_RD: - pull_up = false; - pull_down = true; - break; - case TYPEC_CC_RP_DEF: - case TYPEC_CC_RP_1_5: - case TYPEC_CC_RP_3_0: - pull_up = true; - pull_down = false; - break; - default: - fusb302_log(chip, "unsupported cc value %s", - typec_cc_status_name[cc]); - ret = -EINVAL; - goto done; - } - ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); - if (ret < 0) { - fusb302_log(chip, "cannot stop toggling, ret=%d", ret); - goto done; - } - ret = fusb302_set_cc_pull(chip, pull_up, pull_down); - if (ret < 0) { - fusb302_log(chip, - "cannot set cc pulling up %s, down %s, ret = %d", - pull_up ? "True" : "False", - pull_down ? "True" : "False", - ret); - goto done; - } - /* reset the cc status */ - chip->cc1 = TYPEC_CC_OPEN; - chip->cc2 = TYPEC_CC_OPEN; - /* adjust current for SRC */ - if (pull_up) { - ret = fusb302_set_src_current(chip, cc_src_current[cc]); - if (ret < 0) { - fusb302_log(chip, "cannot set src current %s, ret=%d", - typec_cc_status_name[cc], ret); - goto done; - } - } - /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */ - if (pull_up) { - rd_mda = rd_mda_value[cc_src_current[cc]]; - ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); - if (ret < 0) { - fusb302_log(chip, - "cannot set SRC measure value, ret=%d", - ret); - goto done; - } - ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, - FUSB_REG_MASK_BC_LVL | - FUSB_REG_MASK_COMP_CHNG, - FUSB_REG_MASK_COMP_CHNG); - if (ret < 0) { - fusb302_log(chip, "cannot set SRC interrupt, ret=%d", - ret); - goto done; - } - chip->intr_bc_lvl = false; - chip->intr_comp_chng = true; - } - if (pull_down) { - ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, - FUSB_REG_MASK_BC_LVL | - FUSB_REG_MASK_COMP_CHNG, - FUSB_REG_MASK_BC_LVL); - if (ret < 0) { - fusb302_log(chip, "cannot set SRC interrupt, ret=%d", - ret); - goto done; - } - chip->intr_bc_lvl = true; - chip->intr_comp_chng = false; - } - fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]); -done: - mutex_unlock(&chip->lock); - - return ret; -} - -static int tcpm_get_cc(struct tcpc_dev *dev, enum typec_cc_status *cc1, - enum typec_cc_status *cc2) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - - mutex_lock(&chip->lock); - *cc1 = chip->cc1; - *cc2 = chip->cc2; - fusb302_log(chip, "cc1=%s, cc2=%s", typec_cc_status_name[*cc1], - typec_cc_status_name[*cc2]); - mutex_unlock(&chip->lock); - - return 0; -} - -static int tcpm_set_polarity(struct tcpc_dev *dev, - enum typec_cc_polarity polarity) -{ - return 0; -} - -static int tcpm_set_vconn(struct tcpc_dev *dev, bool on) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - u8 switches0_data = 0x00; - u8 switches0_mask = FUSB_REG_SWITCHES0_VCONN_CC1 | - FUSB_REG_SWITCHES0_VCONN_CC2; - - mutex_lock(&chip->lock); - if (chip->vconn_on == on) { - fusb302_log(chip, "vconn is already %s", on ? "On" : "Off"); - goto done; - } - if (on) { - switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ? - FUSB_REG_SWITCHES0_VCONN_CC2 : - FUSB_REG_SWITCHES0_VCONN_CC1; - } - ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, - switches0_mask, switches0_data); - if (ret < 0) - goto done; - chip->vconn_on = on; - fusb302_log(chip, "vconn := %s", on ? "On" : "Off"); -done: - mutex_unlock(&chip->lock); - - return ret; -} - -static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - - mutex_lock(&chip->lock); - if (chip->vbus_on == on) { - fusb302_log(chip, "vbus is already %s", on ? "On" : "Off"); - } else { - if (on) - ret = regulator_enable(chip->vbus); - else - ret = regulator_disable(chip->vbus); - if (ret < 0) { - fusb302_log(chip, "cannot %s vbus regulator, ret=%d", - on ? "enable" : "disable", ret); - goto done; - } - chip->vbus_on = on; - fusb302_log(chip, "vbus := %s", on ? "On" : "Off"); - } - if (chip->charge_on == charge) - fusb302_log(chip, "charge is already %s", - charge ? "On" : "Off"); - else - chip->charge_on = charge; - -done: - mutex_unlock(&chip->lock); - - return ret; -} - -static int fusb302_pd_tx_flush(struct fusb302_chip *chip) -{ - return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0, - FUSB_REG_CONTROL0_TX_FLUSH); -} - -static int fusb302_pd_rx_flush(struct fusb302_chip *chip) -{ - return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL1, - FUSB_REG_CONTROL1_RX_FLUSH); -} - -static int fusb302_pd_set_auto_goodcrc(struct fusb302_chip *chip, bool on) -{ - if (on) - return fusb302_i2c_set_bits(chip, FUSB_REG_SWITCHES1, - FUSB_REG_SWITCHES1_AUTO_GCRC); - return fusb302_i2c_clear_bits(chip, FUSB_REG_SWITCHES1, - FUSB_REG_SWITCHES1_AUTO_GCRC); -} - -static int fusb302_pd_set_interrupts(struct fusb302_chip *chip, bool on) -{ - int ret = 0; - u8 mask_interrupts = FUSB_REG_MASK_COLLISION; - u8 maska_interrupts = FUSB_REG_MASKA_RETRYFAIL | - FUSB_REG_MASKA_HARDSENT | - FUSB_REG_MASKA_TX_SUCCESS | - FUSB_REG_MASKA_HARDRESET; - u8 maskb_interrupts = FUSB_REG_MASKB_GCRCSENT; - - ret = on ? - fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, mask_interrupts) : - fusb302_i2c_set_bits(chip, FUSB_REG_MASK, mask_interrupts); - if (ret < 0) - return ret; - ret = on ? - fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, maska_interrupts) : - fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, maska_interrupts); - if (ret < 0) - return ret; - ret = on ? - fusb302_i2c_clear_bits(chip, FUSB_REG_MASKB, maskb_interrupts) : - fusb302_i2c_set_bits(chip, FUSB_REG_MASKB, maskb_interrupts); - return ret; -} - -static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - - mutex_lock(&chip->lock); - ret = fusb302_pd_rx_flush(chip); - if (ret < 0) { - fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret); - goto done; - } - ret = fusb302_pd_tx_flush(chip); - if (ret < 0) { - fusb302_log(chip, "cannot flush pd tx buffer, ret=%d", ret); - goto done; - } - ret = fusb302_pd_set_auto_goodcrc(chip, on); - if (ret < 0) { - fusb302_log(chip, "cannot turn %s auto GCRC, ret=%d", - on ? "on" : "off", ret); - goto done; - } - ret = fusb302_pd_set_interrupts(chip, on); - if (ret < 0) { - fusb302_log(chip, "cannot turn %s pd interrupts, ret=%d", - on ? "on" : "off", ret); - goto done; - } - fusb302_log(chip, "pd := %s", on ? "on" : "off"); -done: - mutex_unlock(&chip->lock); - - return ret; -} - -static const char * const typec_role_name[] = { - [TYPEC_SINK] = "Sink", - [TYPEC_SOURCE] = "Source", -}; - -static const char * const typec_data_role_name[] = { - [TYPEC_DEVICE] = "Device", - [TYPEC_HOST] = "Host", -}; - -static int tcpm_set_roles(struct tcpc_dev *dev, bool attached, - enum typec_role pwr, enum typec_data_role data) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - u8 switches1_mask = FUSB_REG_SWITCHES1_POWERROLE | - FUSB_REG_SWITCHES1_DATAROLE; - u8 switches1_data = 0x00; - - mutex_lock(&chip->lock); - if (pwr == TYPEC_SOURCE) - switches1_data |= FUSB_REG_SWITCHES1_POWERROLE; - if (data == TYPEC_HOST) - switches1_data |= FUSB_REG_SWITCHES1_DATAROLE; - ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, - switches1_mask, switches1_data); - if (ret < 0) { - fusb302_log(chip, "unable to set pd header %s, %s, ret=%d", - typec_role_name[pwr], typec_data_role_name[data], - ret); - goto done; - } - fusb302_log(chip, "pd header := %s, %s", typec_role_name[pwr], - typec_data_role_name[data]); -done: - mutex_unlock(&chip->lock); - - return ret; -} - -static int tcpm_start_drp_toggling(struct tcpc_dev *dev, - enum typec_cc_status cc) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - - mutex_lock(&chip->lock); - ret = fusb302_set_src_current(chip, cc_src_current[cc]); - if (ret < 0) { - fusb302_log(chip, "unable to set src current %s, ret=%d", - typec_cc_status_name[cc], ret); - goto done; - } - ret = fusb302_set_toggling(chip, TOGGLING_MODE_DRP); - if (ret < 0) { - fusb302_log(chip, - "unable to start drp toggling, ret=%d", ret); - goto done; - } - fusb302_log(chip, "start drp toggling"); -done: - mutex_unlock(&chip->lock); - - return ret; -} - -static int fusb302_pd_send_message(struct fusb302_chip *chip, - const struct pd_message *msg) -{ - int ret = 0; - u8 buf[40]; - u8 pos = 0; - int len; - - /* SOP tokens */ - buf[pos++] = FUSB302_TKN_SYNC1; - buf[pos++] = FUSB302_TKN_SYNC1; - buf[pos++] = FUSB302_TKN_SYNC1; - buf[pos++] = FUSB302_TKN_SYNC2; - - len = pd_header_cnt_le(msg->header) * 4; - /* plug 2 for header */ - len += 2; - if (len > 0x1F) { - fusb302_log(chip, - "PD message too long %d (incl. header)", len); - return -EINVAL; - } - /* packsym tells the FUSB302 chip that the next X bytes are payload */ - buf[pos++] = FUSB302_TKN_PACKSYM | (len & 0x1F); - memcpy(&buf[pos], &msg->header, sizeof(msg->header)); - pos += sizeof(msg->header); - - len -= 2; - memcpy(&buf[pos], msg->payload, len); - pos += len; - - /* CRC */ - buf[pos++] = FUSB302_TKN_JAMCRC; - /* EOP */ - buf[pos++] = FUSB302_TKN_EOP; - /* turn tx off after sending message */ - buf[pos++] = FUSB302_TKN_TXOFF; - /* start transmission */ - buf[pos++] = FUSB302_TKN_TXON; - - ret = fusb302_i2c_block_write(chip, FUSB_REG_FIFOS, pos, buf); - if (ret < 0) - return ret; - fusb302_log(chip, "sending PD message header: %x", msg->header); - fusb302_log(chip, "sending PD message len: %d", len); - - return ret; -} - -static int fusb302_pd_send_hardreset(struct fusb302_chip *chip) -{ - return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, - FUSB_REG_CONTROL3_SEND_HARDRESET); -} - -static const char * const transmit_type_name[] = { - [TCPC_TX_SOP] = "SOP", - [TCPC_TX_SOP_PRIME] = "SOP'", - [TCPC_TX_SOP_PRIME_PRIME] = "SOP''", - [TCPC_TX_SOP_DEBUG_PRIME] = "DEBUG'", - [TCPC_TX_SOP_DEBUG_PRIME_PRIME] = "DEBUG''", - [TCPC_TX_HARD_RESET] = "HARD_RESET", - [TCPC_TX_CABLE_RESET] = "CABLE_RESET", - [TCPC_TX_BIST_MODE_2] = "BIST_MODE_2", -}; - -static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, - const struct pd_message *msg) -{ - struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, - tcpc_dev); - int ret = 0; - - mutex_lock(&chip->lock); - switch (type) { - case TCPC_TX_SOP: - ret = fusb302_pd_send_message(chip, msg); - if (ret < 0) - fusb302_log(chip, - "cannot send PD message, ret=%d", ret); - break; - case TCPC_TX_HARD_RESET: - ret = fusb302_pd_send_hardreset(chip); - if (ret < 0) - fusb302_log(chip, - "cannot send hardreset, ret=%d", ret); - break; - default: - fusb302_log(chip, "type %s not supported", - transmit_type_name[type]); - ret = -EINVAL; - } - mutex_unlock(&chip->lock); - - return ret; -} - -static enum typec_cc_status fusb302_bc_lvl_to_cc(u8 bc_lvl) -{ - if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_1230_MAX) - return TYPEC_CC_RP_3_0; - if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_600_1230) - return TYPEC_CC_RP_1_5; - if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_200_600) - return TYPEC_CC_RP_DEF; - return TYPEC_CC_OPEN; -} - -static void fusb302_bc_lvl_handler_work(struct work_struct *work) -{ - struct fusb302_chip *chip = container_of(work, struct fusb302_chip, - bc_lvl_handler.work); - int ret = 0; - u8 status0; - u8 bc_lvl; - enum typec_cc_status cc_status; - - mutex_lock(&chip->lock); - if (!chip->intr_bc_lvl) { - fusb302_log(chip, "BC_LVL interrupt is turned off, abort"); - goto done; - } - ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); - if (ret < 0) - goto done; - fusb302_log(chip, "BC_LVL handler, status0=0x%02x", status0); - if (status0 & FUSB_REG_STATUS0_ACTIVITY) { - fusb302_log(chip, "CC activities detected, delay handling"); - mod_delayed_work(chip->wq, &chip->bc_lvl_handler, - msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); - goto done; - } - bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; - cc_status = fusb302_bc_lvl_to_cc(bc_lvl); - if (chip->cc_polarity == TYPEC_POLARITY_CC1) { - if (chip->cc1 != cc_status) { - fusb302_log(chip, "cc1: %s -> %s", - typec_cc_status_name[chip->cc1], - typec_cc_status_name[cc_status]); - chip->cc1 = cc_status; - tcpm_cc_change(chip->tcpm_port); - } - } else { - if (chip->cc2 != cc_status) { - fusb302_log(chip, "cc2: %s -> %s", - typec_cc_status_name[chip->cc2], - typec_cc_status_name[cc_status]); - chip->cc2 = cc_status; - tcpm_cc_change(chip->tcpm_port); - } - } - -done: - mutex_unlock(&chip->lock); -} - -#define PDO_FIXED_FLAGS \ - (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) - -static const u32 src_pdo[] = { - PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), -}; - -static const struct tcpc_config fusb302_tcpc_config = { - .src_pdo = src_pdo, - .nr_src_pdo = ARRAY_SIZE(src_pdo), - .operating_snk_mw = 2500, - .type = TYPEC_PORT_DRP, - .data = TYPEC_PORT_DRD, - .default_role = TYPEC_SINK, - .alt_modes = NULL, -}; - -static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) -{ - fusb302_tcpc_dev->init = tcpm_init; - fusb302_tcpc_dev->get_vbus = tcpm_get_vbus; - fusb302_tcpc_dev->get_current_limit = tcpm_get_current_limit; - fusb302_tcpc_dev->set_cc = tcpm_set_cc; - fusb302_tcpc_dev->get_cc = tcpm_get_cc; - fusb302_tcpc_dev->set_polarity = tcpm_set_polarity; - fusb302_tcpc_dev->set_vconn = tcpm_set_vconn; - fusb302_tcpc_dev->set_vbus = tcpm_set_vbus; - fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx; - fusb302_tcpc_dev->set_roles = tcpm_set_roles; - fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling; - fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit; -} - -static const char * const cc_polarity_name[] = { - [TYPEC_POLARITY_CC1] = "Polarity_CC1", - [TYPEC_POLARITY_CC2] = "Polarity_CC2", -}; - -static int fusb302_set_cc_polarity(struct fusb302_chip *chip, - enum typec_cc_polarity cc_polarity) -{ - int ret = 0; - u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN | - FUSB_REG_SWITCHES0_CC2_PU_EN | - FUSB_REG_SWITCHES0_VCONN_CC1 | - FUSB_REG_SWITCHES0_VCONN_CC2 | - FUSB_REG_SWITCHES0_MEAS_CC1 | - FUSB_REG_SWITCHES0_MEAS_CC2; - u8 switches0_data = 0x00; - u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN | - FUSB_REG_SWITCHES1_TXCC2_EN; - u8 switches1_data = 0x00; - - if (cc_polarity == TYPEC_POLARITY_CC1) { - switches0_data = FUSB_REG_SWITCHES0_MEAS_CC1; - if (chip->vconn_on) - switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2; - if (chip->pull_up) - switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN; - switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN; - } else { - switches0_data = FUSB_REG_SWITCHES0_MEAS_CC2; - if (chip->vconn_on) - switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1; - if (chip->pull_up) - switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN; - switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN; - } - ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, - switches0_mask, switches0_data); - if (ret < 0) - return ret; - ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, - switches1_mask, switches1_data); - if (ret < 0) - return ret; - chip->cc_polarity = cc_polarity; - - return ret; -} - -static int fusb302_handle_togdone_snk(struct fusb302_chip *chip, - u8 togdone_result) -{ - int ret = 0; - u8 status0; - u8 bc_lvl; - enum typec_cc_polarity cc_polarity; - enum typec_cc_status cc_status_active, cc1, cc2; - - /* set pull_up, pull_down */ - ret = fusb302_set_cc_pull(chip, false, true); - if (ret < 0) { - fusb302_log(chip, "cannot set cc to pull down, ret=%d", ret); - return ret; - } - /* set polarity */ - cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ? - TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; - ret = fusb302_set_cc_polarity(chip, cc_polarity); - if (ret < 0) { - fusb302_log(chip, "cannot set cc polarity %s, ret=%d", - cc_polarity_name[cc_polarity], ret); - return ret; - } - /* fusb302_set_cc_polarity() has set the correct measure block */ - ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); - if (ret < 0) - return ret; - bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; - cc_status_active = fusb302_bc_lvl_to_cc(bc_lvl); - /* restart toggling if the cc status on the active line is OPEN */ - if (cc_status_active == TYPEC_CC_OPEN) { - fusb302_log(chip, "restart toggling as CC_OPEN detected"); - ret = fusb302_set_toggling(chip, chip->toggling_mode); - return ret; - } - /* update tcpm with the new cc value */ - cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? - cc_status_active : TYPEC_CC_OPEN; - cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? - cc_status_active : TYPEC_CC_OPEN; - if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { - chip->cc1 = cc1; - chip->cc2 = cc2; - tcpm_cc_change(chip->tcpm_port); - } - /* turn off toggling */ - ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); - if (ret < 0) { - fusb302_log(chip, - "cannot set toggling mode off, ret=%d", ret); - return ret; - } - /* unmask bc_lvl interrupt */ - ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, FUSB_REG_MASK_BC_LVL); - if (ret < 0) { - fusb302_log(chip, - "cannot unmask bc_lcl interrupt, ret=%d", ret); - return ret; - } - chip->intr_bc_lvl = true; - fusb302_log(chip, "detected cc1=%s, cc2=%s", - typec_cc_status_name[cc1], - typec_cc_status_name[cc2]); - - return ret; -} - -static int fusb302_handle_togdone_src(struct fusb302_chip *chip, - u8 togdone_result) -{ - /* - * - set polarity (measure cc, vconn, tx) - * - set pull_up, pull_down - * - set cc1, cc2, and update to tcpm_port - * - set I_COMP interrupt on - */ - int ret = 0; - u8 status0; - u8 ra_mda = ra_mda_value[chip->src_current_status]; - u8 rd_mda = rd_mda_value[chip->src_current_status]; - bool ra_comp, rd_comp; - enum typec_cc_polarity cc_polarity; - enum typec_cc_status cc_status_active, cc1, cc2; - - /* set pull_up, pull_down */ - ret = fusb302_set_cc_pull(chip, true, false); - if (ret < 0) { - fusb302_log(chip, "cannot set cc to pull up, ret=%d", ret); - return ret; - } - /* set polarity */ - cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) ? - TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; - ret = fusb302_set_cc_polarity(chip, cc_polarity); - if (ret < 0) { - fusb302_log(chip, "cannot set cc polarity %s, ret=%d", - cc_polarity_name[cc_polarity], ret); - return ret; - } - /* fusb302_set_cc_polarity() has set the correct measure block */ - ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); - if (ret < 0) - return ret; - usleep_range(50, 100); - ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); - if (ret < 0) - return ret; - rd_comp = !!(status0 & FUSB_REG_STATUS0_COMP); - if (!rd_comp) { - ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda); - if (ret < 0) - return ret; - usleep_range(50, 100); - ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); - if (ret < 0) - return ret; - ra_comp = !!(status0 & FUSB_REG_STATUS0_COMP); - } - if (rd_comp) - cc_status_active = TYPEC_CC_OPEN; - else if (ra_comp) - cc_status_active = TYPEC_CC_RD; - else - /* Ra is not supported, report as Open */ - cc_status_active = TYPEC_CC_OPEN; - /* restart toggling if the cc status on the active line is OPEN */ - if (cc_status_active == TYPEC_CC_OPEN) { - fusb302_log(chip, "restart toggling as CC_OPEN detected"); - ret = fusb302_set_toggling(chip, chip->toggling_mode); - return ret; - } - /* update tcpm with the new cc value */ - cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? - cc_status_active : TYPEC_CC_OPEN; - cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? - cc_status_active : TYPEC_CC_OPEN; - if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { - chip->cc1 = cc1; - chip->cc2 = cc2; - tcpm_cc_change(chip->tcpm_port); - } - /* turn off toggling */ - ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); - if (ret < 0) { - fusb302_log(chip, - "cannot set toggling mode off, ret=%d", ret); - return ret; - } - /* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */ - ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); - if (ret < 0) - return ret; - /* unmask comp_chng interrupt */ - ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, - FUSB_REG_MASK_COMP_CHNG); - if (ret < 0) { - fusb302_log(chip, - "cannot unmask bc_lcl interrupt, ret=%d", ret); - return ret; - } - chip->intr_comp_chng = true; - fusb302_log(chip, "detected cc1=%s, cc2=%s", - typec_cc_status_name[cc1], - typec_cc_status_name[cc2]); - - return ret; -} - -static int fusb302_handle_togdone(struct fusb302_chip *chip) -{ - int ret = 0; - u8 status1a; - u8 togdone_result; - - ret = fusb302_i2c_read(chip, FUSB_REG_STATUS1A, &status1a); - if (ret < 0) - return ret; - togdone_result = (status1a >> FUSB_REG_STATUS1A_TOGSS_POS) & - FUSB_REG_STATUS1A_TOGSS_MASK; - switch (togdone_result) { - case FUSB_REG_STATUS1A_TOGSS_SNK1: - case FUSB_REG_STATUS1A_TOGSS_SNK2: - return fusb302_handle_togdone_snk(chip, togdone_result); - case FUSB_REG_STATUS1A_TOGSS_SRC1: - case FUSB_REG_STATUS1A_TOGSS_SRC2: - return fusb302_handle_togdone_src(chip, togdone_result); - case FUSB_REG_STATUS1A_TOGSS_AA: - /* doesn't support */ - fusb302_log(chip, "AudioAccessory not supported"); - fusb302_set_toggling(chip, chip->toggling_mode); - break; - default: - fusb302_log(chip, "TOGDONE with an invalid state: %d", - togdone_result); - fusb302_set_toggling(chip, chip->toggling_mode); - break; - } - return ret; -} - -static int fusb302_pd_reset(struct fusb302_chip *chip) -{ - return fusb302_i2c_set_bits(chip, FUSB_REG_RESET, - FUSB_REG_RESET_PD_RESET); -} - -static int fusb302_pd_read_message(struct fusb302_chip *chip, - struct pd_message *msg) -{ - int ret = 0; - u8 token; - u8 crc[4]; - int len; - - /* first SOP token */ - ret = fusb302_i2c_read(chip, FUSB_REG_FIFOS, &token); - if (ret < 0) - return ret; - ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 2, - (u8 *)&msg->header); - if (ret < 0) - return ret; - len = pd_header_cnt_le(msg->header) * 4; - /* add 4 to length to include the CRC */ - if (len > PD_MAX_PAYLOAD * 4) { - fusb302_log(chip, "PD message too long %d", len); - return -EINVAL; - } - if (len > 0) { - ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, len, - (u8 *)msg->payload); - if (ret < 0) - return ret; - } - /* another 4 bytes to read CRC out */ - ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 4, crc); - if (ret < 0) - return ret; - fusb302_log(chip, "PD message header: %x", msg->header); - fusb302_log(chip, "PD message len: %d", len); - - /* - * Check if we've read off a GoodCRC message. If so then indicate to - * TCPM that the previous transmission has completed. Otherwise we pass - * the received message over to TCPM for processing. - * - * We make this check here instead of basing the reporting decision on - * the IRQ event type, as it's possible for the chip to report the - * TX_SUCCESS and GCRCSENT events out of order on occasion, so we need - * to check the message type to ensure correct reporting to TCPM. - */ - if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC)) - tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); - else - tcpm_pd_receive(chip->tcpm_port, msg); - - return ret; -} - -static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) -{ - struct fusb302_chip *chip = dev_id; - int ret = 0; - u8 interrupt; - u8 interrupta; - u8 interruptb; - u8 status0; - bool vbus_present; - bool comp_result; - bool intr_togdone; - bool intr_bc_lvl; - bool intr_comp_chng; - struct pd_message pd_msg; - - mutex_lock(&chip->lock); - /* grab a snapshot of intr flags */ - intr_togdone = chip->intr_togdone; - intr_bc_lvl = chip->intr_bc_lvl; - intr_comp_chng = chip->intr_comp_chng; - - ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPT, &interrupt); - if (ret < 0) - goto done; - ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTA, &interrupta); - if (ret < 0) - goto done; - ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTB, &interruptb); - if (ret < 0) - goto done; - ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); - if (ret < 0) - goto done; - fusb302_log(chip, - "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x", - interrupt, interrupta, interruptb, status0); - - if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { - vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); - fusb302_log(chip, "IRQ: VBUS_OK, vbus=%s", - vbus_present ? "On" : "Off"); - if (vbus_present != chip->vbus_present) { - chip->vbus_present = vbus_present; - tcpm_vbus_change(chip->tcpm_port); - } - } - - if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) { - fusb302_log(chip, "IRQ: TOGDONE"); - ret = fusb302_handle_togdone(chip); - if (ret < 0) { - fusb302_log(chip, - "handle togdone error, ret=%d", ret); - goto done; - } - } - - if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) { - fusb302_log(chip, "IRQ: BC_LVL, handler pending"); - /* - * as BC_LVL interrupt can be affected by PD activity, - * apply delay to for the handler to wait for the PD - * signaling to finish. - */ - mod_delayed_work(chip->wq, &chip->bc_lvl_handler, - msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); - } - - if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) { - comp_result = !!(status0 & FUSB_REG_STATUS0_COMP); - fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s", - comp_result ? "true" : "false"); - if (comp_result) { - /* cc level > Rd_threashold, detach */ - if (chip->cc_polarity == TYPEC_POLARITY_CC1) - chip->cc1 = TYPEC_CC_OPEN; - else - chip->cc2 = TYPEC_CC_OPEN; - tcpm_cc_change(chip->tcpm_port); - } - } - - if (interrupt & FUSB_REG_INTERRUPT_COLLISION) { - fusb302_log(chip, "IRQ: PD collision"); - tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); - } - - if (interrupta & FUSB_REG_INTERRUPTA_RETRYFAIL) { - fusb302_log(chip, "IRQ: PD retry failed"); - tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); - } - - if (interrupta & FUSB_REG_INTERRUPTA_HARDSENT) { - fusb302_log(chip, "IRQ: PD hardreset sent"); - ret = fusb302_pd_reset(chip); - if (ret < 0) { - fusb302_log(chip, "cannot PD reset, ret=%d", ret); - goto done; - } - tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); - } - - if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) { - fusb302_log(chip, "IRQ: PD tx success"); - ret = fusb302_pd_read_message(chip, &pd_msg); - if (ret < 0) { - fusb302_log(chip, - "cannot read in PD message, ret=%d", ret); - goto done; - } - } - - if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) { - fusb302_log(chip, "IRQ: PD received hardreset"); - ret = fusb302_pd_reset(chip); - if (ret < 0) { - fusb302_log(chip, "cannot PD reset, ret=%d", ret); - goto done; - } - tcpm_pd_hard_reset(chip->tcpm_port); - } - - if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) { - fusb302_log(chip, "IRQ: PD sent good CRC"); - ret = fusb302_pd_read_message(chip, &pd_msg); - if (ret < 0) { - fusb302_log(chip, - "cannot read in PD message, ret=%d", ret); - goto done; - } - } -done: - mutex_unlock(&chip->lock); - - return IRQ_HANDLED; -} - -static int init_gpio(struct fusb302_chip *chip) -{ - struct device_node *node; - int ret = 0; - - node = chip->dev->of_node; - chip->gpio_int_n = of_get_named_gpio(node, "fcs,int_n", 0); - if (!gpio_is_valid(chip->gpio_int_n)) { - ret = chip->gpio_int_n; - dev_err(chip->dev, "cannot get named GPIO Int_N, ret=%d", ret); - return ret; - } - ret = devm_gpio_request(chip->dev, chip->gpio_int_n, "fcs,int_n"); - if (ret < 0) { - dev_err(chip->dev, "cannot request GPIO Int_N, ret=%d", ret); - return ret; - } - ret = gpio_direction_input(chip->gpio_int_n); - if (ret < 0) { - dev_err(chip->dev, - "cannot set GPIO Int_N to input, ret=%d", ret); - return ret; - } - ret = gpio_to_irq(chip->gpio_int_n); - if (ret < 0) { - dev_err(chip->dev, - "cannot request IRQ for GPIO Int_N, ret=%d", ret); - return ret; - } - chip->gpio_int_n_irq = ret; - 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) -{ - struct fusb302_chip *chip; - struct i2c_adapter *adapter; - struct device *dev = &client->dev; - const char *name; - int ret = 0; - u32 v; - - adapter = to_i2c_adapter(client->dev.parent); - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { - dev_err(&client->dev, - "I2C/SMBus block functionality not supported!\n"); - return -ENODEV; - } - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->i2c_client = client; - chip->dev = &client->dev; - chip->tcpc_config = fusb302_tcpc_config; - chip->tcpc_dev.config = &chip->tcpc_config; - mutex_init(&chip->lock); - - chip->tcpc_dev.fwnode = - device_get_named_child_node(dev, "connector"); - - 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. - * This device prop is for kernel internal use only and is expected - * to be set by the platform code which also registers the i2c client - * for the fusb302. - */ - if (device_property_read_string(dev, "fcs,extcon-name", &name) == 0) { - chip->extcon = extcon_get_extcon_dev(name); - if (!chip->extcon) - return -EPROBE_DEFER; - } - - chip->vbus = devm_regulator_get(chip->dev, "vbus"); - if (IS_ERR(chip->vbus)) - return PTR_ERR(chip->vbus); - - chip->wq = create_singlethread_workqueue(dev_name(chip->dev)); - if (!chip->wq) - return -ENOMEM; - - INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); - init_tcpc_dev(&chip->tcpc_dev); - - if (client->irq) { - chip->gpio_int_n_irq = client->irq; - } else { - ret = init_gpio(chip); - if (ret < 0) - goto destroy_workqueue; - } - - chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); - if (IS_ERR(chip->tcpm_port)) { - ret = PTR_ERR(chip->tcpm_port); - if (ret != -EPROBE_DEFER) - dev_err(dev, "cannot register tcpm port, ret=%d", ret); - goto destroy_workqueue; - } - - ret = devm_request_threaded_irq(chip->dev, chip->gpio_int_n_irq, - NULL, fusb302_irq_intn, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "fsc_interrupt_int_n", chip); - if (ret < 0) { - dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); - goto tcpm_unregister_port; - } - enable_irq_wake(chip->gpio_int_n_irq); - fusb302_debugfs_init(chip); - i2c_set_clientdata(client, chip); - - return ret; - -tcpm_unregister_port: - tcpm_unregister_port(chip->tcpm_port); -destroy_workqueue: - destroy_workqueue(chip->wq); - - return ret; -} - -static int fusb302_remove(struct i2c_client *client) -{ - struct fusb302_chip *chip = i2c_get_clientdata(client); - - tcpm_unregister_port(chip->tcpm_port); - destroy_workqueue(chip->wq); - fusb302_debugfs_exit(chip); - - return 0; -} - -static int fusb302_pm_suspend(struct device *dev) -{ - struct fusb302_chip *chip = dev->driver_data; - - if (atomic_read(&chip->i2c_busy)) - return -EBUSY; - atomic_set(&chip->pm_suspend, 1); - - return 0; -} - -static int fusb302_pm_resume(struct device *dev) -{ - struct fusb302_chip *chip = dev->driver_data; - - atomic_set(&chip->pm_suspend, 0); - - return 0; -} - -static const struct of_device_id fusb302_dt_match[] = { - {.compatible = "fcs,fusb302"}, - {}, -}; -MODULE_DEVICE_TABLE(of, fusb302_dt_match); - -static const struct i2c_device_id fusb302_i2c_device_id[] = { - {"typec_fusb302", 0}, - {}, -}; -MODULE_DEVICE_TABLE(i2c, fusb302_i2c_device_id); - -static const struct dev_pm_ops fusb302_pm_ops = { - .suspend = fusb302_pm_suspend, - .resume = fusb302_pm_resume, -}; - -static struct i2c_driver fusb302_driver = { - .driver = { - .name = "typec_fusb302", - .pm = &fusb302_pm_ops, - .of_match_table = of_match_ptr(fusb302_dt_match), - }, - .probe = fusb302_probe, - .remove = fusb302_remove, - .id_table = fusb302_i2c_device_id, -}; -module_i2c_driver(fusb302_driver); - -MODULE_AUTHOR("Yueyao Zhu <yueyao.zhu@gmail.com>"); -MODULE_DESCRIPTION("Fairchild FUSB302 Type-C Chip Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/fusb302/fusb302_reg.h b/drivers/usb/typec/fusb302/fusb302_reg.h deleted file mode 100644 index 00b39d365478..000000000000 --- a/drivers/usb/typec/fusb302/fusb302_reg.h +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright 2016-2017 Google, Inc - * - * Fairchild FUSB302 Type-C Chip Driver - */ - -#ifndef FUSB302_REG_H -#define FUSB302_REG_H - -#define FUSB_REG_DEVICE_ID 0x01 -#define FUSB_REG_SWITCHES0 0x02 -#define FUSB_REG_SWITCHES0_CC2_PU_EN BIT(7) -#define FUSB_REG_SWITCHES0_CC1_PU_EN BIT(6) -#define FUSB_REG_SWITCHES0_VCONN_CC2 BIT(5) -#define FUSB_REG_SWITCHES0_VCONN_CC1 BIT(4) -#define FUSB_REG_SWITCHES0_MEAS_CC2 BIT(3) -#define FUSB_REG_SWITCHES0_MEAS_CC1 BIT(2) -#define FUSB_REG_SWITCHES0_CC2_PD_EN BIT(1) -#define FUSB_REG_SWITCHES0_CC1_PD_EN BIT(0) -#define FUSB_REG_SWITCHES1 0x03 -#define FUSB_REG_SWITCHES1_POWERROLE BIT(7) -#define FUSB_REG_SWITCHES1_SPECREV1 BIT(6) -#define FUSB_REG_SWITCHES1_SPECREV0 BIT(5) -#define FUSB_REG_SWITCHES1_DATAROLE BIT(4) -#define FUSB_REG_SWITCHES1_AUTO_GCRC BIT(2) -#define FUSB_REG_SWITCHES1_TXCC2_EN BIT(1) -#define FUSB_REG_SWITCHES1_TXCC1_EN BIT(0) -#define FUSB_REG_MEASURE 0x04 -#define FUSB_REG_MEASURE_MDAC5 BIT(7) -#define FUSB_REG_MEASURE_MDAC4 BIT(6) -#define FUSB_REG_MEASURE_MDAC3 BIT(5) -#define FUSB_REG_MEASURE_MDAC2 BIT(4) -#define FUSB_REG_MEASURE_MDAC1 BIT(3) -#define FUSB_REG_MEASURE_MDAC0 BIT(2) -#define FUSB_REG_MEASURE_VBUS BIT(1) -#define FUSB_REG_MEASURE_XXXX5 BIT(0) -#define FUSB_REG_CONTROL0 0x06 -#define FUSB_REG_CONTROL0_TX_FLUSH BIT(6) -#define FUSB_REG_CONTROL0_INT_MASK BIT(5) -#define FUSB_REG_CONTROL0_HOST_CUR_MASK (0xC) -#define FUSB_REG_CONTROL0_HOST_CUR_HIGH (0xC) -#define FUSB_REG_CONTROL0_HOST_CUR_MED (0x8) -#define FUSB_REG_CONTROL0_HOST_CUR_DEF (0x4) -#define FUSB_REG_CONTROL0_TX_START BIT(0) -#define FUSB_REG_CONTROL1 0x07 -#define FUSB_REG_CONTROL1_ENSOP2DB BIT(6) -#define FUSB_REG_CONTROL1_ENSOP1DB BIT(5) -#define FUSB_REG_CONTROL1_BIST_MODE2 BIT(4) -#define FUSB_REG_CONTROL1_RX_FLUSH BIT(2) -#define FUSB_REG_CONTROL1_ENSOP2 BIT(1) -#define FUSB_REG_CONTROL1_ENSOP1 BIT(0) -#define FUSB_REG_CONTROL2 0x08 -#define FUSB_REG_CONTROL2_MODE BIT(1) -#define FUSB_REG_CONTROL2_MODE_MASK (0x6) -#define FUSB_REG_CONTROL2_MODE_DFP (0x6) -#define FUSB_REG_CONTROL2_MODE_UFP (0x4) -#define FUSB_REG_CONTROL2_MODE_DRP (0x2) -#define FUSB_REG_CONTROL2_MODE_NONE (0x0) -#define FUSB_REG_CONTROL2_TOGGLE BIT(0) -#define FUSB_REG_CONTROL3 0x09 -#define FUSB_REG_CONTROL3_SEND_HARDRESET BIT(6) -#define FUSB_REG_CONTROL3_BIST_TMODE BIT(5) /* 302B Only */ -#define FUSB_REG_CONTROL3_AUTO_HARDRESET BIT(4) -#define FUSB_REG_CONTROL3_AUTO_SOFTRESET BIT(3) -#define FUSB_REG_CONTROL3_N_RETRIES BIT(1) -#define FUSB_REG_CONTROL3_N_RETRIES_MASK (0x6) -#define FUSB_REG_CONTROL3_N_RETRIES_3 (0x6) -#define FUSB_REG_CONTROL3_N_RETRIES_2 (0x4) -#define FUSB_REG_CONTROL3_N_RETRIES_1 (0x2) -#define FUSB_REG_CONTROL3_AUTO_RETRY BIT(0) -#define FUSB_REG_MASK 0x0A -#define FUSB_REG_MASK_VBUSOK BIT(7) -#define FUSB_REG_MASK_ACTIVITY BIT(6) -#define FUSB_REG_MASK_COMP_CHNG BIT(5) -#define FUSB_REG_MASK_CRC_CHK BIT(4) -#define FUSB_REG_MASK_ALERT BIT(3) -#define FUSB_REG_MASK_WAKE BIT(2) -#define FUSB_REG_MASK_COLLISION BIT(1) -#define FUSB_REG_MASK_BC_LVL BIT(0) -#define FUSB_REG_POWER 0x0B -#define FUSB_REG_POWER_PWR BIT(0) -#define FUSB_REG_POWER_PWR_LOW 0x1 -#define FUSB_REG_POWER_PWR_MEDIUM 0x3 -#define FUSB_REG_POWER_PWR_HIGH 0x7 -#define FUSB_REG_POWER_PWR_ALL 0xF -#define FUSB_REG_RESET 0x0C -#define FUSB_REG_RESET_PD_RESET BIT(1) -#define FUSB_REG_RESET_SW_RESET BIT(0) -#define FUSB_REG_MASKA 0x0E -#define FUSB_REG_MASKA_OCP_TEMP BIT(7) -#define FUSB_REG_MASKA_TOGDONE BIT(6) -#define FUSB_REG_MASKA_SOFTFAIL BIT(5) -#define FUSB_REG_MASKA_RETRYFAIL BIT(4) -#define FUSB_REG_MASKA_HARDSENT BIT(3) -#define FUSB_REG_MASKA_TX_SUCCESS BIT(2) -#define FUSB_REG_MASKA_SOFTRESET BIT(1) -#define FUSB_REG_MASKA_HARDRESET BIT(0) -#define FUSB_REG_MASKB 0x0F -#define FUSB_REG_MASKB_GCRCSENT BIT(0) -#define FUSB_REG_STATUS0A 0x3C -#define FUSB_REG_STATUS0A_SOFTFAIL BIT(5) -#define FUSB_REG_STATUS0A_RETRYFAIL BIT(4) -#define FUSB_REG_STATUS0A_POWER BIT(2) -#define FUSB_REG_STATUS0A_RX_SOFT_RESET BIT(1) -#define FUSB_REG_STATUS0A_RX_HARD_RESET BIT(0) -#define FUSB_REG_STATUS1A 0x3D -#define FUSB_REG_STATUS1A_TOGSS BIT(3) -#define FUSB_REG_STATUS1A_TOGSS_RUNNING 0x0 -#define FUSB_REG_STATUS1A_TOGSS_SRC1 0x1 -#define FUSB_REG_STATUS1A_TOGSS_SRC2 0x2 -#define FUSB_REG_STATUS1A_TOGSS_SNK1 0x5 -#define FUSB_REG_STATUS1A_TOGSS_SNK2 0x6 -#define FUSB_REG_STATUS1A_TOGSS_AA 0x7 -#define FUSB_REG_STATUS1A_TOGSS_POS (3) -#define FUSB_REG_STATUS1A_TOGSS_MASK (0x7) -#define FUSB_REG_STATUS1A_RXSOP2DB BIT(2) -#define FUSB_REG_STATUS1A_RXSOP1DB BIT(1) -#define FUSB_REG_STATUS1A_RXSOP BIT(0) -#define FUSB_REG_INTERRUPTA 0x3E -#define FUSB_REG_INTERRUPTA_OCP_TEMP BIT(7) -#define FUSB_REG_INTERRUPTA_TOGDONE BIT(6) -#define FUSB_REG_INTERRUPTA_SOFTFAIL BIT(5) -#define FUSB_REG_INTERRUPTA_RETRYFAIL BIT(4) -#define FUSB_REG_INTERRUPTA_HARDSENT BIT(3) -#define FUSB_REG_INTERRUPTA_TX_SUCCESS BIT(2) -#define FUSB_REG_INTERRUPTA_SOFTRESET BIT(1) -#define FUSB_REG_INTERRUPTA_HARDRESET BIT(0) -#define FUSB_REG_INTERRUPTB 0x3F -#define FUSB_REG_INTERRUPTB_GCRCSENT BIT(0) -#define FUSB_REG_STATUS0 0x40 -#define FUSB_REG_STATUS0_VBUSOK BIT(7) -#define FUSB_REG_STATUS0_ACTIVITY BIT(6) -#define FUSB_REG_STATUS0_COMP BIT(5) -#define FUSB_REG_STATUS0_CRC_CHK BIT(4) -#define FUSB_REG_STATUS0_ALERT BIT(3) -#define FUSB_REG_STATUS0_WAKE BIT(2) -#define FUSB_REG_STATUS0_BC_LVL_MASK 0x03 -#define FUSB_REG_STATUS0_BC_LVL_0_200 0x0 -#define FUSB_REG_STATUS0_BC_LVL_200_600 0x1 -#define FUSB_REG_STATUS0_BC_LVL_600_1230 0x2 -#define FUSB_REG_STATUS0_BC_LVL_1230_MAX 0x3 -#define FUSB_REG_STATUS0_BC_LVL1 BIT(1) -#define FUSB_REG_STATUS0_BC_LVL0 BIT(0) -#define FUSB_REG_STATUS1 0x41 -#define FUSB_REG_STATUS1_RXSOP2 BIT(7) -#define FUSB_REG_STATUS1_RXSOP1 BIT(6) -#define FUSB_REG_STATUS1_RX_EMPTY BIT(5) -#define FUSB_REG_STATUS1_RX_FULL BIT(4) -#define FUSB_REG_STATUS1_TX_EMPTY BIT(3) -#define FUSB_REG_STATUS1_TX_FULL BIT(2) -#define FUSB_REG_INTERRUPT 0x42 -#define FUSB_REG_INTERRUPT_VBUSOK BIT(7) -#define FUSB_REG_INTERRUPT_ACTIVITY BIT(6) -#define FUSB_REG_INTERRUPT_COMP_CHNG BIT(5) -#define FUSB_REG_INTERRUPT_CRC_CHK BIT(4) -#define FUSB_REG_INTERRUPT_ALERT BIT(3) -#define FUSB_REG_INTERRUPT_WAKE BIT(2) -#define FUSB_REG_INTERRUPT_COLLISION BIT(1) -#define FUSB_REG_INTERRUPT_BC_LVL BIT(0) -#define FUSB_REG_FIFOS 0x43 - -/* Tokens defined for the FUSB302 TX FIFO */ -enum fusb302_txfifo_tokens { - FUSB302_TKN_TXON = 0xA1, - FUSB302_TKN_SYNC1 = 0x12, - FUSB302_TKN_SYNC2 = 0x13, - FUSB302_TKN_SYNC3 = 0x1B, - FUSB302_TKN_RST1 = 0x15, - FUSB302_TKN_RST2 = 0x16, - FUSB302_TKN_PACKSYM = 0x80, - FUSB302_TKN_JAMCRC = 0xFF, - FUSB302_TKN_EOP = 0x14, - FUSB302_TKN_TXOFF = 0xFE, -}; - -#endif diff --git a/drivers/usb/typec/tcpci.c b/drivers/usb/typec/tcpci.c deleted file mode 100644 index ac6b418b15f1..000000000000 --- a/drivers/usb/typec/tcpci.c +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright 2015-2017 Google, Inc - * - * USB Type-C Port Controller Interface. - */ - -#include <linux/delay.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/i2c.h> -#include <linux/interrupt.h> -#include <linux/property.h> -#include <linux/regmap.h> -#include <linux/usb/pd.h> -#include <linux/usb/tcpm.h> -#include <linux/usb/typec.h> - -#include "tcpci.h" - -#define PD_RETRY_COUNT 3 - -struct tcpci { - struct device *dev; - - struct tcpm_port *port; - - struct regmap *regmap; - - bool controls_vbus; - - struct tcpc_dev tcpc; - struct tcpci_data *data; -}; - -struct tcpci_chip { - struct tcpci *tcpci; - struct tcpci_data data; -}; - -static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) -{ - return container_of(tcpc, struct tcpci, tcpc); -} - -static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, u16 *val) -{ - return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); -} - -static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val) -{ - return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16)); -} - -static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned int reg; - int ret; - - switch (cc) { - case TYPEC_CC_RA: - reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) | - (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT); - break; - case TYPEC_CC_RD: - reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | - (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); - break; - case TYPEC_CC_RP_DEF: - reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | - (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | - (TCPC_ROLE_CTRL_RP_VAL_DEF << - TCPC_ROLE_CTRL_RP_VAL_SHIFT); - break; - case TYPEC_CC_RP_1_5: - reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | - (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | - (TCPC_ROLE_CTRL_RP_VAL_1_5 << - TCPC_ROLE_CTRL_RP_VAL_SHIFT); - break; - case TYPEC_CC_RP_3_0: - reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | - (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | - (TCPC_ROLE_CTRL_RP_VAL_3_0 << - TCPC_ROLE_CTRL_RP_VAL_SHIFT); - break; - case TYPEC_CC_OPEN: - default: - reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) | - (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT); - break; - } - - ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); - if (ret < 0) - return ret; - - return 0; -} - -static int tcpci_start_drp_toggling(struct tcpc_dev *tcpc, - enum typec_cc_status cc) -{ - int ret; - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned int reg = TCPC_ROLE_CTRL_DRP; - - /* Handle vendor drp toggling */ - if (tcpci->data->start_drp_toggling) { - ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc); - if (ret < 0) - return ret; - } - - 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 = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); - if (ret < 0) - return ret; - return regmap_write(tcpci->regmap, TCPC_COMMAND, - TCPC_CMD_LOOK4CONNECTION); -} - -static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink) -{ - switch (cc) { - case 0x1: - return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA; - case 0x2: - return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD; - case 0x3: - if (sink) - return TYPEC_CC_RP_3_0; - /* fall through */ - case 0x0: - default: - return TYPEC_CC_OPEN; - } -} - -static int tcpci_get_cc(struct tcpc_dev *tcpc, - enum typec_cc_status *cc1, enum typec_cc_status *cc2) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned int reg; - int ret; - - ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, ®); - if (ret < 0) - return ret; - - *cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) & - TCPC_CC_STATUS_CC1_MASK, - reg & TCPC_CC_STATUS_TERM); - *cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) & - TCPC_CC_STATUS_CC2_MASK, - reg & TCPC_CC_STATUS_TERM); - - return 0; -} - -static int tcpci_set_polarity(struct tcpc_dev *tcpc, - enum typec_cc_polarity polarity) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned int reg; - int ret; - - /* Keep the disconnect cc line open */ - ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, ®); - if (ret < 0) - return ret; - - if (polarity == TYPEC_POLARITY_CC2) - reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT; - else - reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT; - ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); - if (ret < 0) - return ret; - - return regmap_write(tcpci->regmap, TCPC_TCPC_CTRL, - (polarity == TYPEC_POLARITY_CC2) ? - TCPC_TCPC_CTRL_ORIENTATION : 0); -} - -static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - int ret; - - /* Handle vendor set vconn */ - if (tcpci->data->set_vconn) { - ret = tcpci->data->set_vconn(tcpci, tcpci->data, enable); - if (ret < 0) - return ret; - } - - return regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, - TCPC_POWER_CTRL_VCONN_ENABLE, - enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); -} - -static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached, - enum typec_role role, enum typec_data_role data) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned int reg; - int ret; - - reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT; - if (role == TYPEC_SOURCE) - reg |= TCPC_MSG_HDR_INFO_PWR_ROLE; - if (data == TYPEC_HOST) - reg |= TCPC_MSG_HDR_INFO_DATA_ROLE; - ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg); - if (ret < 0) - return ret; - - return 0; -} - -static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned int reg = 0; - int ret; - - if (enable) - reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET; - ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); - if (ret < 0) - return ret; - - return 0; -} - -static int tcpci_get_vbus(struct tcpc_dev *tcpc) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned int reg; - int ret; - - ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); - if (ret < 0) - return ret; - - return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); -} - -static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - int ret; - - /* Disable both source and sink first before enabling anything */ - - if (!source) { - ret = regmap_write(tcpci->regmap, TCPC_COMMAND, - TCPC_CMD_DISABLE_SRC_VBUS); - if (ret < 0) - return ret; - } - - if (!sink) { - ret = regmap_write(tcpci->regmap, TCPC_COMMAND, - TCPC_CMD_DISABLE_SINK_VBUS); - if (ret < 0) - return ret; - } - - if (source) { - ret = regmap_write(tcpci->regmap, TCPC_COMMAND, - TCPC_CMD_SRC_VBUS_DEFAULT); - if (ret < 0) - return ret; - } - - if (sink) { - ret = regmap_write(tcpci->regmap, TCPC_COMMAND, - TCPC_CMD_SINK_VBUS); - if (ret < 0) - return ret; - } - - return 0; -} - -static int tcpci_pd_transmit(struct tcpc_dev *tcpc, - enum tcpm_transmit_type type, - const struct pd_message *msg) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - u16 header = msg ? le16_to_cpu(msg->header) : 0; - unsigned int reg, cnt; - int ret; - - cnt = msg ? pd_header_cnt(header) * 4 : 0; - ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2); - if (ret < 0) - return ret; - - ret = tcpci_write16(tcpci, TCPC_TX_HDR, header); - if (ret < 0) - return ret; - - if (cnt > 0) { - ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, - &msg->payload, cnt); - if (ret < 0) - return ret; - } - - reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | - (type << TCPC_TRANSMIT_TYPE_SHIFT); - ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg); - if (ret < 0) - return ret; - - return 0; -} - -static int tcpci_init(struct tcpc_dev *tcpc) -{ - struct tcpci *tcpci = tcpc_to_tcpci(tcpc); - unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */ - unsigned int reg; - int ret; - - while (time_before_eq(jiffies, timeout)) { - ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); - if (ret < 0) - return ret; - if (!(reg & TCPC_POWER_STATUS_UNINIT)) - break; - usleep_range(10000, 20000); - } - if (time_after(jiffies, timeout)) - return -ETIMEDOUT; - - /* Handle vendor init */ - if (tcpci->data->init) { - ret = tcpci->data->init(tcpci, tcpci->data); - if (ret < 0) - return ret; - } - - /* Clear all events */ - ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff); - if (ret < 0) - return ret; - - if (tcpci->controls_vbus) - reg = TCPC_POWER_STATUS_VBUS_PRES; - else - reg = 0; - ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg); - if (ret < 0) - return ret; - - /* Enable Vbus detection */ - ret = regmap_write(tcpci->regmap, TCPC_COMMAND, - TCPC_CMD_ENABLE_VBUS_DETECT); - if (ret < 0) - return ret; - - reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED | - TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS | - TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS; - if (tcpci->controls_vbus) - reg |= TCPC_ALERT_POWER_STATUS; - return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg); -} - -irqreturn_t tcpci_irq(struct tcpci *tcpci) -{ - u16 status; - - tcpci_read16(tcpci, TCPC_ALERT, &status); - - /* - * Clear alert status for everything except RX_STATUS, which shouldn't - * be cleared until we have successfully retrieved message. - */ - if (status & ~TCPC_ALERT_RX_STATUS) - tcpci_write16(tcpci, TCPC_ALERT, - status & ~TCPC_ALERT_RX_STATUS); - - if (status & TCPC_ALERT_CC_STATUS) - tcpm_cc_change(tcpci->port); - - if (status & TCPC_ALERT_POWER_STATUS) { - unsigned int reg; - - regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, ®); - - /* - * If power status mask has been reset, then the TCPC - * has reset. - */ - if (reg == 0xff) - tcpm_tcpc_reset(tcpci->port); - else - tcpm_vbus_change(tcpci->port); - } - - if (status & TCPC_ALERT_RX_STATUS) { - struct pd_message msg; - unsigned int cnt; - u16 header; - - regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt); - - tcpci_read16(tcpci, TCPC_RX_HDR, &header); - msg.header = cpu_to_le16(header); - - if (WARN_ON(cnt > sizeof(msg.payload))) - cnt = sizeof(msg.payload); - - if (cnt > 0) - regmap_raw_read(tcpci->regmap, TCPC_RX_DATA, - &msg.payload, cnt); - - /* Read complete, clear RX status alert bit */ - tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); - - tcpm_pd_receive(tcpci->port, &msg); - } - - if (status & TCPC_ALERT_RX_HARD_RST) - tcpm_pd_hard_reset(tcpci->port); - - if (status & TCPC_ALERT_TX_SUCCESS) - tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS); - else if (status & TCPC_ALERT_TX_DISCARDED) - tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED); - else if (status & TCPC_ALERT_TX_FAILED) - tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED); - - return IRQ_HANDLED; -} -EXPORT_SYMBOL_GPL(tcpci_irq); - -static irqreturn_t _tcpci_irq(int irq, void *dev_id) -{ - struct tcpci_chip *chip = dev_id; - - return tcpci_irq(chip->tcpci); -} - -static const struct regmap_config tcpci_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - - .max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */ -}; - -static int tcpci_parse_config(struct tcpci *tcpci) -{ - tcpci->controls_vbus = true; /* XXX */ - - tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev, - "connector"); - if (!tcpci->tcpc.fwnode) { - dev_err(tcpci->dev, "Can't find connector node.\n"); - return -EINVAL; - } - - return 0; -} - -struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) -{ - struct tcpci *tcpci; - int err; - - tcpci = devm_kzalloc(dev, sizeof(*tcpci), GFP_KERNEL); - if (!tcpci) - return ERR_PTR(-ENOMEM); - - tcpci->dev = dev; - tcpci->data = data; - tcpci->regmap = data->regmap; - - tcpci->tcpc.init = tcpci_init; - tcpci->tcpc.get_vbus = tcpci_get_vbus; - tcpci->tcpc.set_vbus = tcpci_set_vbus; - tcpci->tcpc.set_cc = tcpci_set_cc; - tcpci->tcpc.get_cc = tcpci_get_cc; - tcpci->tcpc.set_polarity = tcpci_set_polarity; - tcpci->tcpc.set_vconn = tcpci_set_vconn; - tcpci->tcpc.start_drp_toggling = tcpci_start_drp_toggling; - - tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx; - tcpci->tcpc.set_roles = tcpci_set_roles; - tcpci->tcpc.pd_transmit = tcpci_pd_transmit; - - err = tcpci_parse_config(tcpci); - if (err < 0) - return ERR_PTR(err); - - tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); - if (IS_ERR(tcpci->port)) - return ERR_CAST(tcpci->port); - - return tcpci; -} -EXPORT_SYMBOL_GPL(tcpci_register_port); - -void tcpci_unregister_port(struct tcpci *tcpci) -{ - tcpm_unregister_port(tcpci->port); -} -EXPORT_SYMBOL_GPL(tcpci_unregister_port); - -static int tcpci_probe(struct i2c_client *client, - const struct i2c_device_id *i2c_id) -{ - struct tcpci_chip *chip; - int err; - u16 val = 0; - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->data.regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config); - if (IS_ERR(chip->data.regmap)) - return PTR_ERR(chip->data.regmap); - - i2c_set_clientdata(client, chip); - - /* Disable chip interrupts before requesting irq */ - err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val, - sizeof(u16)); - if (err < 0) - return err; - - chip->tcpci = tcpci_register_port(&client->dev, &chip->data); - if (IS_ERR(chip->tcpci)) - return PTR_ERR(chip->tcpci); - - err = devm_request_threaded_irq(&client->dev, client->irq, NULL, - _tcpci_irq, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - dev_name(&client->dev), chip); - if (err < 0) { - tcpci_unregister_port(chip->tcpci); - return err; - } - - return 0; -} - -static int tcpci_remove(struct i2c_client *client) -{ - struct tcpci_chip *chip = i2c_get_clientdata(client); - - tcpci_unregister_port(chip->tcpci); - - return 0; -} - -static const struct i2c_device_id tcpci_id[] = { - { "tcpci", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, tcpci_id); - -#ifdef CONFIG_OF -static const struct of_device_id tcpci_of_match[] = { - { .compatible = "nxp,ptn5110", }, - {}, -}; -MODULE_DEVICE_TABLE(of, tcpci_of_match); -#endif - -static struct i2c_driver tcpci_i2c_driver = { - .driver = { - .name = "tcpci", - .of_match_table = of_match_ptr(tcpci_of_match), - }, - .probe = tcpci_probe, - .remove = tcpci_remove, - .id_table = tcpci_id, -}; -module_i2c_driver(tcpci_i2c_driver); - -MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpci.h b/drivers/usb/typec/tcpci.h deleted file mode 100644 index 303ebde26546..000000000000 --- a/drivers/usb/typec/tcpci.h +++ /dev/null @@ -1,139 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Copyright 2015-2017 Google, Inc - * - * USB Type-C Port Controller Interface. - */ - -#ifndef __LINUX_USB_TCPCI_H -#define __LINUX_USB_TCPCI_H - -#define TCPC_VENDOR_ID 0x0 -#define TCPC_PRODUCT_ID 0x2 -#define TCPC_BCD_DEV 0x4 -#define TCPC_TC_REV 0x6 -#define TCPC_PD_REV 0x8 -#define TCPC_PD_INT_REV 0xa - -#define TCPC_ALERT 0x10 -#define TCPC_ALERT_VBUS_DISCNCT BIT(11) -#define TCPC_ALERT_RX_BUF_OVF BIT(10) -#define TCPC_ALERT_FAULT BIT(9) -#define TCPC_ALERT_V_ALARM_LO BIT(8) -#define TCPC_ALERT_V_ALARM_HI BIT(7) -#define TCPC_ALERT_TX_SUCCESS BIT(6) -#define TCPC_ALERT_TX_DISCARDED BIT(5) -#define TCPC_ALERT_TX_FAILED BIT(4) -#define TCPC_ALERT_RX_HARD_RST BIT(3) -#define TCPC_ALERT_RX_STATUS BIT(2) -#define TCPC_ALERT_POWER_STATUS BIT(1) -#define TCPC_ALERT_CC_STATUS BIT(0) - -#define TCPC_ALERT_MASK 0x12 -#define TCPC_POWER_STATUS_MASK 0x14 -#define TCPC_FAULT_STATUS_MASK 0x15 -#define TCPC_CONFIG_STD_OUTPUT 0x18 - -#define TCPC_TCPC_CTRL 0x19 -#define TCPC_TCPC_CTRL_ORIENTATION BIT(0) - -#define TCPC_ROLE_CTRL 0x1a -#define TCPC_ROLE_CTRL_DRP BIT(6) -#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4 -#define TCPC_ROLE_CTRL_RP_VAL_MASK 0x3 -#define TCPC_ROLE_CTRL_RP_VAL_DEF 0x0 -#define TCPC_ROLE_CTRL_RP_VAL_1_5 0x1 -#define TCPC_ROLE_CTRL_RP_VAL_3_0 0x2 -#define TCPC_ROLE_CTRL_CC2_SHIFT 2 -#define TCPC_ROLE_CTRL_CC2_MASK 0x3 -#define TCPC_ROLE_CTRL_CC1_SHIFT 0 -#define TCPC_ROLE_CTRL_CC1_MASK 0x3 -#define TCPC_ROLE_CTRL_CC_RA 0x0 -#define TCPC_ROLE_CTRL_CC_RP 0x1 -#define TCPC_ROLE_CTRL_CC_RD 0x2 -#define TCPC_ROLE_CTRL_CC_OPEN 0x3 - -#define TCPC_FAULT_CTRL 0x1b - -#define TCPC_POWER_CTRL 0x1c -#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 -#define TCPC_CC_STATUS_CC1_SHIFT 0 -#define TCPC_CC_STATUS_CC1_MASK 0x3 - -#define TCPC_POWER_STATUS 0x1e -#define TCPC_POWER_STATUS_UNINIT BIT(6) -#define TCPC_POWER_STATUS_VBUS_DET BIT(3) -#define TCPC_POWER_STATUS_VBUS_PRES BIT(2) - -#define TCPC_FAULT_STATUS 0x1f - -#define TCPC_COMMAND 0x23 -#define TCPC_CMD_WAKE_I2C 0x11 -#define TCPC_CMD_DISABLE_VBUS_DETECT 0x22 -#define TCPC_CMD_ENABLE_VBUS_DETECT 0x33 -#define TCPC_CMD_DISABLE_SINK_VBUS 0x44 -#define TCPC_CMD_SINK_VBUS 0x55 -#define TCPC_CMD_DISABLE_SRC_VBUS 0x66 -#define TCPC_CMD_SRC_VBUS_DEFAULT 0x77 -#define TCPC_CMD_SRC_VBUS_HIGH 0x88 -#define TCPC_CMD_LOOK4CONNECTION 0x99 -#define TCPC_CMD_RXONEMORE 0xAA -#define TCPC_CMD_I2C_IDLE 0xFF - -#define TCPC_DEV_CAP_1 0x24 -#define TCPC_DEV_CAP_2 0x26 -#define TCPC_STD_INPUT_CAP 0x28 -#define TCPC_STD_OUTPUT_CAP 0x29 - -#define TCPC_MSG_HDR_INFO 0x2e -#define TCPC_MSG_HDR_INFO_DATA_ROLE BIT(3) -#define TCPC_MSG_HDR_INFO_PWR_ROLE BIT(0) -#define TCPC_MSG_HDR_INFO_REV_SHIFT 1 -#define TCPC_MSG_HDR_INFO_REV_MASK 0x3 - -#define TCPC_RX_DETECT 0x2f -#define TCPC_RX_DETECT_HARD_RESET BIT(5) -#define TCPC_RX_DETECT_SOP BIT(0) - -#define TCPC_RX_BYTE_CNT 0x30 -#define TCPC_RX_BUF_FRAME_TYPE 0x31 -#define TCPC_RX_HDR 0x32 -#define TCPC_RX_DATA 0x34 /* through 0x4f */ - -#define TCPC_TRANSMIT 0x50 -#define TCPC_TRANSMIT_RETRY_SHIFT 4 -#define TCPC_TRANSMIT_RETRY_MASK 0x3 -#define TCPC_TRANSMIT_TYPE_SHIFT 0 -#define TCPC_TRANSMIT_TYPE_MASK 0x7 - -#define TCPC_TX_BYTE_CNT 0x51 -#define TCPC_TX_HDR 0x52 -#define TCPC_TX_DATA 0x54 /* through 0x6f */ - -#define TCPC_VBUS_VOLTAGE 0x70 -#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72 -#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74 -#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 -#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 - -struct tcpci; -struct tcpci_data { - struct regmap *regmap; - int (*init)(struct tcpci *tcpci, struct tcpci_data *data); - int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, - bool enable); - int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data, - enum typec_cc_status cc); -}; - -struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); -void tcpci_unregister_port(struct tcpci *tcpci); -irqreturn_t tcpci_irq(struct tcpci *tcpci); - -#endif /* __LINUX_USB_TCPCI_H */ diff --git a/drivers/usb/typec/tcpci_rt1711h.c b/drivers/usb/typec/tcpci_rt1711h.c deleted file mode 100644 index 017389021b96..000000000000 --- a/drivers/usb/typec/tcpci_rt1711h.c +++ /dev/null @@ -1,312 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2018, Richtek Technology Corporation - * - * Richtek RT1711H Type-C Chip Driver - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/i2c.h> -#include <linux/interrupt.h> -#include <linux/gpio/consumer.h> -#include <linux/usb/tcpm.h> -#include <linux/regmap.h> -#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 <shufan_lee@richtek.com>"); -MODULE_DESCRIPTION("RT1711H USB Type-C Port Controller Interface Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c deleted file mode 100644 index 4f1f4215f3d6..000000000000 --- a/drivers/usb/typec/tcpm.c +++ /dev/null @@ -1,4851 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright 2015-2017 Google, Inc - * - * USB Power Delivery protocol stack. - */ - -#include <linux/completion.h> -#include <linux/debugfs.h> -#include <linux/device.h> -#include <linux/jiffies.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/mutex.h> -#include <linux/power_supply.h> -#include <linux/proc_fs.h> -#include <linux/property.h> -#include <linux/sched/clock.h> -#include <linux/seq_file.h> -#include <linux/slab.h> -#include <linux/spinlock.h> -#include <linux/usb/pd.h> -#include <linux/usb/pd_ado.h> -#include <linux/usb/pd_bdo.h> -#include <linux/usb/pd_ext_sdb.h> -#include <linux/usb/pd_vdo.h> -#include <linux/usb/role.h> -#include <linux/usb/tcpm.h> -#include <linux/usb/typec_altmode.h> -#include <linux/workqueue.h> - -#define FOREACH_STATE(S) \ - S(INVALID_STATE), \ - S(DRP_TOGGLING), \ - S(SRC_UNATTACHED), \ - S(SRC_ATTACH_WAIT), \ - S(SRC_ATTACHED), \ - S(SRC_STARTUP), \ - S(SRC_SEND_CAPABILITIES), \ - S(SRC_NEGOTIATE_CAPABILITIES), \ - S(SRC_TRANSITION_SUPPLY), \ - S(SRC_READY), \ - S(SRC_WAIT_NEW_CAPABILITIES), \ - \ - S(SNK_UNATTACHED), \ - S(SNK_ATTACH_WAIT), \ - S(SNK_DEBOUNCED), \ - S(SNK_ATTACHED), \ - S(SNK_STARTUP), \ - S(SNK_DISCOVERY), \ - S(SNK_DISCOVERY_DEBOUNCE), \ - 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), \ - \ - S(ACC_UNATTACHED), \ - S(DEBUG_ACC_ATTACHED), \ - S(AUDIO_ACC_ATTACHED), \ - S(AUDIO_ACC_DEBOUNCE), \ - \ - S(HARD_RESET_SEND), \ - S(HARD_RESET_START), \ - S(SRC_HARD_RESET_VBUS_OFF), \ - S(SRC_HARD_RESET_VBUS_ON), \ - S(SNK_HARD_RESET_SINK_OFF), \ - S(SNK_HARD_RESET_WAIT_VBUS), \ - S(SNK_HARD_RESET_SINK_ON), \ - \ - S(SOFT_RESET), \ - S(SOFT_RESET_SEND), \ - \ - S(DR_SWAP_ACCEPT), \ - S(DR_SWAP_SEND), \ - S(DR_SWAP_SEND_TIMEOUT), \ - S(DR_SWAP_CANCEL), \ - S(DR_SWAP_CHANGE_DR), \ - \ - S(PR_SWAP_ACCEPT), \ - S(PR_SWAP_SEND), \ - S(PR_SWAP_SEND_TIMEOUT), \ - S(PR_SWAP_CANCEL), \ - S(PR_SWAP_START), \ - S(PR_SWAP_SRC_SNK_TRANSITION_OFF), \ - S(PR_SWAP_SRC_SNK_SOURCE_OFF), \ - S(PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED), \ - S(PR_SWAP_SRC_SNK_SINK_ON), \ - S(PR_SWAP_SNK_SRC_SINK_OFF), \ - S(PR_SWAP_SNK_SRC_SOURCE_ON), \ - S(PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP), \ - \ - S(VCONN_SWAP_ACCEPT), \ - S(VCONN_SWAP_SEND), \ - S(VCONN_SWAP_SEND_TIMEOUT), \ - S(VCONN_SWAP_CANCEL), \ - S(VCONN_SWAP_START), \ - S(VCONN_SWAP_WAIT_FOR_VCONN), \ - S(VCONN_SWAP_TURN_ON_VCONN), \ - S(VCONN_SWAP_TURN_OFF_VCONN), \ - \ - S(SNK_TRY), \ - S(SNK_TRY_WAIT), \ - S(SNK_TRY_WAIT_DEBOUNCE), \ - S(SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS), \ - S(SRC_TRYWAIT), \ - S(SRC_TRYWAIT_DEBOUNCE), \ - S(SRC_TRYWAIT_UNATTACHED), \ - \ - S(SRC_TRY), \ - S(SRC_TRY_WAIT), \ - S(SRC_TRY_DEBOUNCE), \ - S(SNK_TRYWAIT), \ - S(SNK_TRYWAIT_DEBOUNCE), \ - 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) - -#define GENERATE_ENUM(e) e -#define GENERATE_STRING(s) #s - -enum tcpm_state { - FOREACH_STATE(GENERATE_ENUM) -}; - -static const char * const tcpm_states[] = { - FOREACH_STATE(GENERATE_STRING) -}; - -enum vdm_states { - VDM_STATE_ERR_BUSY = -3, - VDM_STATE_ERR_SEND = -2, - VDM_STATE_ERR_TMOUT = -1, - VDM_STATE_DONE = 0, - /* Anything >0 represents an active state */ - VDM_STATE_READY = 1, - VDM_STATE_BUSY = 2, - VDM_STATE_WAIT_RSP_BUSY = 3, -}; - -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, -}; - -/* Events from low level driver */ - -#define TCPM_CC_EVENT BIT(0) -#define TCPM_VBUS_EVENT BIT(1) -#define TCPM_RESET_EVENT BIT(2) - -#define LOG_BUFFER_ENTRIES 1024 -#define LOG_BUFFER_ENTRY_SIZE 128 - -/* Alternate mode support */ - -#define SVID_DISCOVERY_MAX 16 -#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) - -struct pd_mode_data { - int svid_index; /* current SVID index */ - int nsvids; - u16 svids[SVID_DISCOVERY_MAX]; - int altmodes; /* number of alternate modes */ - struct typec_altmode_desc altmode_desc[ALTMODE_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; - - struct mutex lock; /* tcpm state machine lock */ - struct workqueue_struct *wq; - - struct typec_capability typec_caps; - struct typec_port *typec_port; - - struct tcpc_dev *tcpc; - struct usb_role_switch *role_sw; - - enum typec_role vconn_role; - enum typec_role pwr_role; - enum typec_data_role data_role; - enum typec_pwr_opmode pwr_opmode; - - struct usb_pd_identity partner_ident; - struct typec_partner_desc partner_desc; - struct typec_partner *partner; - - enum typec_cc_status cc_req; - - enum typec_cc_status cc1; - enum typec_cc_status cc2; - enum typec_cc_polarity polarity; - - bool attached; - bool connected; - enum typec_port_type port_type; - bool vbus_present; - bool vbus_never_low; - bool vbus_source; - bool vbus_charge; - - bool send_discover; - bool op_vsafe5v; - - int try_role; - int try_snk_count; - int try_src_count; - - enum pd_msg_request queued_message; - - enum tcpm_state enter_state; - enum tcpm_state prev_state; - enum tcpm_state state; - enum tcpm_state delayed_state; - unsigned long delayed_runtime; - unsigned long delay_ms; - - spinlock_t pd_event_lock; - u32 pd_events; - - struct work_struct event_work; - struct delayed_work state_machine; - struct delayed_work vdm_state_machine; - bool state_machine_running; - - struct completion tx_complete; - enum tcpm_transmit_status tx_status; - - struct mutex swap_lock; /* swap command lock */ - bool swap_pending; - bool non_pd_role_swap; - struct completion swap_complete; - int swap_status; - - unsigned int negotiated_rev; - unsigned int message_id; - unsigned int caps_count; - unsigned int hard_reset_count; - bool pd_capable; - bool explicit_contract; - unsigned int rx_msgid; - - /* Partner capabilities/requests */ - u32 sink_request; - u32 source_caps[PDO_MAX_OBJECTS]; - unsigned int nr_source_caps; - u32 sink_caps[PDO_MAX_OBJECTS]; - unsigned int nr_sink_caps; - - /* Local capabilities */ - u32 src_pdo[PDO_MAX_OBJECTS]; - unsigned int nr_src_pdo; - u32 snk_pdo[PDO_MAX_OBJECTS]; - unsigned int nr_snk_pdo; - u32 snk_vdo[VDO_MAX_OBJECTS]; - unsigned int nr_snk_vdo; - - unsigned int operating_snk_mw; - bool update_sink_caps; - - /* Requested current / voltage */ - 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 */ - enum vdm_states vdm_state; - u32 vdm_retries; - /* next Vendor Defined Message to send */ - u32 vdo_data[VDO_MAX_SIZE]; - u8 vdo_count; - /* VDO to retry if UFP responder replied busy */ - u32 vdo_retry; - - /* 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[ALTMODE_DISCOVERY_MAX]; - struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX]; - - /* Deadline in jiffies to exit src_try_wait state */ - unsigned long max_wait; - -#ifdef CONFIG_DEBUG_FS - struct dentry *dentry; - struct mutex logbuffer_lock; /* log buffer access lock */ - int logbuffer_head; - int logbuffer_tail; - u8 *logbuffer[LOG_BUFFER_ENTRIES]; -#endif -}; - -struct pd_rx_event { - struct work_struct work; - struct tcpm_port *port; - struct pd_message msg; -}; - -#define tcpm_cc_is_sink(cc) \ - ((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \ - (cc) == TYPEC_CC_RP_3_0) - -#define tcpm_port_is_sink(port) \ - ((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \ - (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1))) - -#define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD) -#define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA) -#define tcpm_cc_is_open(cc) ((cc) == TYPEC_CC_OPEN) - -#define tcpm_port_is_source(port) \ - ((tcpm_cc_is_source((port)->cc1) && \ - !tcpm_cc_is_source((port)->cc2)) || \ - (tcpm_cc_is_source((port)->cc2) && \ - !tcpm_cc_is_source((port)->cc1))) - -#define tcpm_port_is_debug(port) \ - (tcpm_cc_is_source((port)->cc1) && tcpm_cc_is_source((port)->cc2)) - -#define tcpm_port_is_audio(port) \ - (tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_audio((port)->cc2)) - -#define tcpm_port_is_audio_detached(port) \ - ((tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_open((port)->cc2)) || \ - (tcpm_cc_is_audio((port)->cc2) && tcpm_cc_is_open((port)->cc1))) - -#define tcpm_try_snk(port) \ - ((port)->try_snk_count == 0 && (port)->try_role == TYPEC_SINK && \ - (port)->port_type == TYPEC_PORT_DRP) - -#define tcpm_try_src(port) \ - ((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE && \ - (port)->port_type == TYPEC_PORT_DRP) - -static enum tcpm_state tcpm_default_state(struct tcpm_port *port) -{ - if (port->port_type == TYPEC_PORT_DRP) { - if (port->try_role == TYPEC_SINK) - return SNK_UNATTACHED; - else if (port->try_role == TYPEC_SOURCE) - return SRC_UNATTACHED; - else if (port->tcpc->config->default_role == TYPEC_SINK) - return SNK_UNATTACHED; - /* Fall through to return SRC_UNATTACHED */ - } else if (port->port_type == TYPEC_PORT_SNK) { - return SNK_UNATTACHED; - } - return SRC_UNATTACHED; -} - -static inline -struct tcpm_port *typec_cap_to_tcpm(const struct typec_capability *cap) -{ - return container_of(cap, struct tcpm_port, typec_caps); -} - -static bool tcpm_port_is_disconnected(struct tcpm_port *port) -{ - return (!port->attached && port->cc1 == TYPEC_CC_OPEN && - port->cc2 == TYPEC_CC_OPEN) || - (port->attached && ((port->polarity == TYPEC_POLARITY_CC1 && - port->cc1 == TYPEC_CC_OPEN) || - (port->polarity == TYPEC_POLARITY_CC2 && - port->cc2 == TYPEC_CC_OPEN))); -} - -/* - * Logging - */ - -#ifdef CONFIG_DEBUG_FS - -static bool tcpm_log_full(struct tcpm_port *port) -{ - return port->logbuffer_tail == - (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; -} - -__printf(2, 0) -static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) -{ - char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; - u64 ts_nsec = local_clock(); - unsigned long rem_nsec; - - mutex_lock(&port->logbuffer_lock); - if (!port->logbuffer[port->logbuffer_head]) { - port->logbuffer[port->logbuffer_head] = - kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); - if (!port->logbuffer[port->logbuffer_head]) { - mutex_unlock(&port->logbuffer_lock); - return; - } - } - - vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); - - if (tcpm_log_full(port)) { - port->logbuffer_head = max(port->logbuffer_head - 1, 0); - strcpy(tmpbuffer, "overflow"); - } - - if (port->logbuffer_head < 0 || - port->logbuffer_head >= LOG_BUFFER_ENTRIES) { - dev_warn(port->dev, - "Bad log buffer index %d\n", port->logbuffer_head); - goto abort; - } - - if (!port->logbuffer[port->logbuffer_head]) { - dev_warn(port->dev, - "Log buffer index %d is NULL\n", port->logbuffer_head); - goto abort; - } - - rem_nsec = do_div(ts_nsec, 1000000000); - scnprintf(port->logbuffer[port->logbuffer_head], - LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", - (unsigned long)ts_nsec, rem_nsec / 1000, - tmpbuffer); - port->logbuffer_head = (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; - -abort: - mutex_unlock(&port->logbuffer_lock); -} - -__printf(2, 3) -static void tcpm_log(struct tcpm_port *port, const char *fmt, ...) -{ - va_list args; - - /* Do not log while disconnected and unattached */ - if (tcpm_port_is_disconnected(port) && - (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED || - port->state == DRP_TOGGLING)) - return; - - va_start(args, fmt); - _tcpm_log(port, fmt, args); - va_end(args); -} - -__printf(2, 3) -static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - _tcpm_log(port, fmt, args); - va_end(args); -} - -static void tcpm_log_source_caps(struct tcpm_port *port) -{ - int i; - - for (i = 0; i < port->nr_source_caps; i++) { - u32 pdo = port->source_caps[i]; - enum pd_pdo_type type = pdo_type(pdo); - char msg[64]; - - switch (type) { - case PDO_TYPE_FIXED: - scnprintf(msg, sizeof(msg), - "%u mV, %u mA [%s%s%s%s%s%s]", - pdo_fixed_voltage(pdo), - pdo_max_current(pdo), - (pdo & PDO_FIXED_DUAL_ROLE) ? - "R" : "", - (pdo & PDO_FIXED_SUSPEND) ? - "S" : "", - (pdo & PDO_FIXED_HIGHER_CAP) ? - "H" : "", - (pdo & PDO_FIXED_USB_COMM) ? - "U" : "", - (pdo & PDO_FIXED_DATA_SWAP) ? - "D" : "", - (pdo & PDO_FIXED_EXTPOWER) ? - "E" : ""); - break; - case PDO_TYPE_VAR: - scnprintf(msg, sizeof(msg), - "%u-%u mV, %u mA", - pdo_min_voltage(pdo), - pdo_max_voltage(pdo), - pdo_max_current(pdo)); - break; - case PDO_TYPE_BATT: - scnprintf(msg, sizeof(msg), - "%u-%u mV, %u mW", - pdo_min_voltage(pdo), - 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; - } - tcpm_log(port, " PDO %d: type %d, %s", - i, type, msg); - } -} - -static int tcpm_debug_show(struct seq_file *s, void *v) -{ - struct tcpm_port *port = (struct tcpm_port *)s->private; - int tail; - - mutex_lock(&port->logbuffer_lock); - tail = port->logbuffer_tail; - while (tail != port->logbuffer_head) { - seq_printf(s, "%s\n", port->logbuffer[tail]); - tail = (tail + 1) % LOG_BUFFER_ENTRIES; - } - if (!seq_has_overflowed(s)) - port->logbuffer_tail = tail; - mutex_unlock(&port->logbuffer_lock); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(tcpm_debug); - -static struct dentry *rootdir; - -static void tcpm_debugfs_init(struct tcpm_port *port) -{ - mutex_init(&port->logbuffer_lock); - /* /sys/kernel/debug/tcpm/usbcX */ - if (!rootdir) - rootdir = debugfs_create_dir("tcpm", NULL); - - port->dentry = debugfs_create_file(dev_name(port->dev), - S_IFREG | 0444, rootdir, - port, &tcpm_debug_fops); -} - -static void tcpm_debugfs_exit(struct tcpm_port *port) -{ - debugfs_remove(port->dentry); -} - -#else - -__printf(2, 3) -static void tcpm_log(const struct tcpm_port *port, const char *fmt, ...) { } -__printf(2, 3) -static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) { } -static void tcpm_log_source_caps(struct tcpm_port *port) { } -static void tcpm_debugfs_init(const struct tcpm_port *port) { } -static void tcpm_debugfs_exit(const struct tcpm_port *port) { } - -#endif - -static int tcpm_pd_transmit(struct tcpm_port *port, - enum tcpm_transmit_type type, - const struct pd_message *msg) -{ - unsigned long timeout; - int ret; - - if (msg) - tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header)); - else - tcpm_log(port, "PD TX, type: %#x", type); - - reinit_completion(&port->tx_complete); - ret = port->tcpc->pd_transmit(port->tcpc, type, msg); - if (ret < 0) - return ret; - - mutex_unlock(&port->lock); - timeout = wait_for_completion_timeout(&port->tx_complete, - msecs_to_jiffies(PD_T_TCPC_TX_TIMEOUT)); - mutex_lock(&port->lock); - if (!timeout) - return -ETIMEDOUT; - - switch (port->tx_status) { - case TCPC_TX_SUCCESS: - port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK; - return 0; - case TCPC_TX_DISCARDED: - return -EAGAIN; - case TCPC_TX_FAILED: - default: - return -EIO; - } -} - -void tcpm_pd_transmit_complete(struct tcpm_port *port, - enum tcpm_transmit_status status) -{ - tcpm_log(port, "PD TX complete, status: %u", status); - port->tx_status = status; - complete(&port->tx_complete); -} -EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); - -static int tcpm_mux_set(struct tcpm_port *port, int state, - enum usb_role usb_role, - enum typec_orientation orientation) -{ - int ret; - - tcpm_log(port, "Requesting mux state %d, usb-role %d, orientation %d", - state, usb_role, orientation); - - ret = typec_set_orientation(port->typec_port, orientation); - if (ret) - return ret; - - if (port->role_sw) { - ret = usb_role_switch_set_role(port->role_sw, usb_role); - if (ret) - return ret; - } - - return typec_set_mode(port->typec_port, state); -} - -static int tcpm_set_polarity(struct tcpm_port *port, - enum typec_cc_polarity polarity) -{ - int ret; - - tcpm_log(port, "polarity %d", polarity); - - ret = port->tcpc->set_polarity(port->tcpc, polarity); - if (ret < 0) - return ret; - - port->polarity = polarity; - - return 0; -} - -static int tcpm_set_vconn(struct tcpm_port *port, bool enable) -{ - int ret; - - tcpm_log(port, "vconn:=%d", enable); - - ret = port->tcpc->set_vconn(port->tcpc, enable); - if (!ret) { - port->vconn_role = enable ? TYPEC_SOURCE : TYPEC_SINK; - typec_set_vconn_role(port->typec_port, port->vconn_role); - } - - return ret; -} - -static u32 tcpm_get_current_limit(struct tcpm_port *port) -{ - enum typec_cc_status cc; - u32 limit; - - cc = port->polarity ? port->cc2 : port->cc1; - switch (cc) { - case TYPEC_CC_RP_1_5: - limit = 1500; - break; - case TYPEC_CC_RP_3_0: - limit = 3000; - break; - case TYPEC_CC_RP_DEF: - default: - if (port->tcpc->get_current_limit) - limit = port->tcpc->get_current_limit(port->tcpc); - else - limit = 0; - break; - } - - return limit; -} - -static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv) -{ - int ret = -EOPNOTSUPP; - - tcpm_log(port, "Setting voltage/current limit %u mV %u mA", mv, max_ma); - - port->supply_voltage = mv; - port->current_limit = max_ma; - - if (port->tcpc->set_current_limit) - ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv); - - return ret; -} - -/* - * Determine RP value to set based on maximum current supported - * by a port if configured as source. - * Returns CC value to report to link partner. - */ -static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port) -{ - const u32 *src_pdo = port->src_pdo; - int nr_pdo = port->nr_src_pdo; - int i; - - /* - * Search for first entry with matching voltage. - * It should report the maximum supported current. - */ - for (i = 0; i < nr_pdo; i++) { - const u32 pdo = src_pdo[i]; - - if (pdo_type(pdo) == PDO_TYPE_FIXED && - pdo_fixed_voltage(pdo) == 5000) { - unsigned int curr = pdo_max_current(pdo); - - if (curr >= 3000) - return TYPEC_CC_RP_3_0; - else if (curr >= 1500) - return TYPEC_CC_RP_1_5; - return TYPEC_CC_RP_DEF; - } - } - - return TYPEC_CC_RP_DEF; -} - -static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) -{ - return port->tcpc->set_roles(port->tcpc, attached, port->pwr_role, - port->data_role); -} - -static int tcpm_set_roles(struct tcpm_port *port, bool attached, - enum typec_role role, enum typec_data_role data) -{ - enum typec_orientation orientation; - enum usb_role usb_role; - int ret; - - if (port->polarity == TYPEC_POLARITY_CC1) - orientation = TYPEC_ORIENTATION_NORMAL; - else - orientation = TYPEC_ORIENTATION_REVERSE; - - if (data == TYPEC_HOST) - usb_role = USB_ROLE_HOST; - else - usb_role = USB_ROLE_DEVICE; - - ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation); - if (ret < 0) - return ret; - - ret = port->tcpc->set_roles(port->tcpc, attached, role, data); - if (ret < 0) - return ret; - - port->pwr_role = role; - port->data_role = data; - typec_set_data_role(port->typec_port, data); - typec_set_pwr_role(port->typec_port, role); - - return 0; -} - -static int tcpm_set_pwr_role(struct tcpm_port *port, enum typec_role role) -{ - int ret; - - ret = port->tcpc->set_roles(port->tcpc, true, role, - port->data_role); - if (ret < 0) - return ret; - - port->pwr_role = role; - typec_set_pwr_role(port->typec_port, role); - - return 0; -} - -static int tcpm_pd_send_source_caps(struct tcpm_port *port) -{ - struct pd_message msg; - int i; - - memset(&msg, 0, sizeof(msg)); - if (!port->nr_src_pdo) { - /* No source capabilities defined, sink only */ - 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); - } - for (i = 0; i < port->nr_src_pdo; i++) - msg.payload[i] = cpu_to_le32(port->src_pdo[i]); - - return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); -} - -static int tcpm_pd_send_sink_caps(struct tcpm_port *port) -{ - struct pd_message msg; - int i; - - memset(&msg, 0, sizeof(msg)); - if (!port->nr_snk_pdo) { - /* No sink capabilities defined, source only */ - 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); - } - for (i = 0; i < port->nr_snk_pdo; i++) - msg.payload[i] = cpu_to_le32(port->snk_pdo[i]); - - return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); -} - -static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, - unsigned int delay_ms) -{ - if (delay_ms) { - tcpm_log(port, "pending state change %s -> %s @ %u ms", - tcpm_states[port->state], tcpm_states[state], - delay_ms); - port->delayed_state = state; - mod_delayed_work(port->wq, &port->state_machine, - msecs_to_jiffies(delay_ms)); - port->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms); - port->delay_ms = delay_ms; - } else { - tcpm_log(port, "state change %s -> %s", - tcpm_states[port->state], tcpm_states[state]); - port->delayed_state = INVALID_STATE; - port->prev_state = port->state; - port->state = state; - /* - * Don't re-queue the state machine work item if we're currently - * in the state machine and we're immediately changing states. - * tcpm_state_machine_work() will continue running the state - * machine. - */ - if (!port->state_machine_running) - mod_delayed_work(port->wq, &port->state_machine, 0); - } -} - -static void tcpm_set_state_cond(struct tcpm_port *port, enum tcpm_state state, - unsigned int delay_ms) -{ - if (port->enter_state == port->state) - tcpm_set_state(port, state, delay_ms); - else - tcpm_log(port, - "skipped %sstate change %s -> %s [%u ms], context state %s", - delay_ms ? "delayed " : "", - tcpm_states[port->state], tcpm_states[state], - delay_ms, tcpm_states[port->enter_state]); -} - -static void tcpm_queue_message(struct tcpm_port *port, - enum pd_msg_request message) -{ - port->queued_message = message; - mod_delayed_work(port->wq, &port->state_machine, 0); -} - -/* - * VDM/VDO handling functions - */ -static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, - const u32 *data, int cnt) -{ - port->vdo_count = cnt + 1; - port->vdo_data[0] = header; - memcpy(&port->vdo_data[1], data, sizeof(u32) * cnt); - /* Set ready, vdm state machine will actually send */ - port->vdm_retries = 0; - port->vdm_state = VDM_STATE_READY; -} - -static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload, - int cnt) -{ - u32 vdo = le32_to_cpu(payload[VDO_INDEX_IDH]); - u32 product = le32_to_cpu(payload[VDO_INDEX_PRODUCT]); - - memset(&port->mode_data, 0, sizeof(port->mode_data)); - - port->partner_ident.id_header = vdo; - port->partner_ident.cert_stat = le32_to_cpu(payload[VDO_INDEX_CSTAT]); - port->partner_ident.product = product; - - typec_partner_set_identity(port->partner); - - tcpm_log(port, "Identity: %04x:%04x.%04x", - PD_IDH_VID(vdo), - PD_PRODUCT_PID(product), product & 0xffff); -} - -static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload, - int cnt) -{ - struct pd_mode_data *pmdata = &port->mode_data; - int i; - - for (i = 1; i < cnt; i++) { - u32 p = le32_to_cpu(payload[i]); - u16 svid; - - svid = (p >> 16) & 0xffff; - if (!svid) - return false; - - if (pmdata->nsvids >= SVID_DISCOVERY_MAX) - goto abort; - - pmdata->svids[pmdata->nsvids++] = svid; - tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); - - svid = p & 0xffff; - if (!svid) - return false; - - if (pmdata->nsvids >= SVID_DISCOVERY_MAX) - goto abort; - - pmdata->svids[pmdata->nsvids++] = svid; - tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); - } - return true; -abort: - tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX); - return false; -} - -static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload, - int cnt) -{ - struct pd_mode_data *pmdata = &port->mode_data; - struct typec_altmode_desc *paltmode; - int i; - - if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { - /* Already logged in svdm_consume_svids() */ - return; - } - - for (i = 1; i < cnt; i++) { - paltmode = &pmdata->altmode_desc[pmdata->altmodes]; - memset(paltmode, 0, sizeof(*paltmode)); - - paltmode->svid = pmdata->svids[pmdata->svid_index]; - paltmode->mode = i; - paltmode->vdo = le32_to_cpu(payload[i]); - - tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x", - pmdata->altmodes, paltmode->svid, - paltmode->mode, paltmode->vdo); - - pmdata->altmodes++; - } -} - -static void tcpm_register_partner_altmodes(struct tcpm_port *port) -{ - struct pd_mode_data *modep = &port->mode_data; - struct typec_altmode *altmode; - int i; - - for (i = 0; i < modep->altmodes; i++) { - altmode = typec_partner_register_altmode(port->partner, - &modep->altmode_desc[i]); - if (!altmode) - tcpm_log(port, "Failed to register partner SVID 0x%04x", - modep->altmode_desc[i].svid); - port->partner_altmode[i] = altmode; - } -} - -#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header) - -static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, - u32 *response) -{ - struct typec_altmode *adev; - struct typec_altmode *pdev; - struct pd_mode_data *modep; - u32 p[PD_MAX_PAYLOAD]; - int rlen = 0; - int cmd_type; - int cmd; - int i; - - for (i = 0; i < cnt; i++) - p[i] = le32_to_cpu(payload[i]); - - cmd_type = PD_VDO_CMDT(p[0]); - cmd = PD_VDO_CMD(p[0]); - - tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d", - p[0], cmd_type, cmd, cnt); - - modep = &port->mode_data; - - adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX, - PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); - - pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX, - PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); - - switch (cmd_type) { - case CMDT_INIT: - switch (cmd) { - case CMD_DISCOVER_IDENT: - /* 6.4.4.3.1: Only respond as UFP (device) */ - if (port->data_role == TYPEC_DEVICE && - port->nr_snk_vdo) { - for (i = 0; i < port->nr_snk_vdo; i++) - response[i + 1] = port->snk_vdo[i]; - rlen = port->nr_snk_vdo + 1; - } - break; - case CMD_DISCOVER_SVID: - break; - case CMD_DISCOVER_MODES: - break; - case CMD_ENTER_MODE: - break; - case CMD_EXIT_MODE: - break; - case CMD_ATTENTION: - /* Attention command does not have response */ - typec_altmode_attention(adev, p[1]); - return 0; - default: - break; - } - if (rlen >= 1) { - response[0] = p[0] | VDO_CMDT(CMDT_RSP_ACK); - } else if (rlen == 0) { - response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); - rlen = 1; - } else { - response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY); - rlen = 1; - } - break; - case CMDT_RSP_ACK: - /* silently drop message if we are not connected */ - if (IS_ERR_OR_NULL(port->partner)) - break; - - switch (cmd) { - case CMD_DISCOVER_IDENT: - /* 6.4.4.3.1 */ - svdm_consume_identity(port, payload, cnt); - response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID); - rlen = 1; - break; - case CMD_DISCOVER_SVID: - /* 6.4.4.3.2 */ - if (svdm_consume_svids(port, payload, cnt)) { - response[0] = VDO(USB_SID_PD, 1, - CMD_DISCOVER_SVID); - rlen = 1; - } else if (modep->nsvids && supports_modal(port)) { - response[0] = VDO(modep->svids[0], 1, - CMD_DISCOVER_MODES); - rlen = 1; - } - break; - case CMD_DISCOVER_MODES: - /* 6.4.4.3.3 */ - svdm_consume_modes(port, payload, cnt); - modep->svid_index++; - if (modep->svid_index < modep->nsvids) { - u16 svid = modep->svids[modep->svid_index]; - response[0] = VDO(svid, 1, CMD_DISCOVER_MODES); - rlen = 1; - } else { - tcpm_register_partner_altmodes(port); - } - break; - case CMD_ENTER_MODE: - typec_altmode_update_active(pdev, true); - - if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { - response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE); - response[0] |= VDO_OPOS(adev->mode); - return 1; - } - return 0; - case CMD_EXIT_MODE: - typec_altmode_update_active(pdev, false); - - /* Back to USB Operation */ - WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, - NULL)); - break; - default: - break; - } - break; - case CMDT_RSP_NAK: - switch (cmd) { - case CMD_ENTER_MODE: - /* Back to USB Operation */ - WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, - NULL)); - break; - default: - break; - } - break; - default: - break; - } - - /* Informing the alternate mode drivers about everything */ - typec_altmode_vdm(adev, p[0], &p[1], cnt); - - return rlen; -} - -static void tcpm_handle_vdm_request(struct tcpm_port *port, - const __le32 *payload, int cnt) -{ - int rlen = 0; - u32 response[8] = { }; - u32 p0 = le32_to_cpu(payload[0]); - - if (port->vdm_state == VDM_STATE_BUSY) { - /* If UFP responded busy retry after timeout */ - if (PD_VDO_CMDT(p0) == CMDT_RSP_BUSY) { - port->vdm_state = VDM_STATE_WAIT_RSP_BUSY; - port->vdo_retry = (p0 & ~VDO_CMDT_MASK) | - CMDT_INIT; - mod_delayed_work(port->wq, &port->vdm_state_machine, - msecs_to_jiffies(PD_T_VDM_BUSY)); - return; - } - port->vdm_state = VDM_STATE_DONE; - } - - if (PD_VDO_SVDM(p0)) - rlen = tcpm_pd_svdm(port, payload, cnt, response); - - if (rlen > 0) { - tcpm_queue_vdm(port, response[0], &response[1], rlen - 1); - mod_delayed_work(port->wq, &port->vdm_state_machine, 0); - } -} - -static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, - const u32 *data, int count) -{ - u32 header; - - if (WARN_ON(count > VDO_MAX_SIZE - 1)) - count = VDO_MAX_SIZE - 1; - - /* set VDM header with VID & CMD */ - header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? - 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd); - tcpm_queue_vdm(port, header, data, count); - - mod_delayed_work(port->wq, &port->vdm_state_machine, 0); -} - -static unsigned int vdm_ready_timeout(u32 vdm_hdr) -{ - unsigned int timeout; - int cmd = PD_VDO_CMD(vdm_hdr); - - /* its not a structured VDM command */ - if (!PD_VDO_SVDM(vdm_hdr)) - return PD_T_VDM_UNSTRUCTURED; - - switch (PD_VDO_CMDT(vdm_hdr)) { - case CMDT_INIT: - if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) - timeout = PD_T_VDM_WAIT_MODE_E; - else - timeout = PD_T_VDM_SNDR_RSP; - break; - default: - if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) - timeout = PD_T_VDM_E_MODE; - else - timeout = PD_T_VDM_RCVR_RSP; - break; - } - return timeout; -} - -static void vdm_run_state_machine(struct tcpm_port *port) -{ - struct pd_message msg; - int i, res; - - switch (port->vdm_state) { - case VDM_STATE_READY: - /* Only transmit VDM if attached */ - if (!port->attached) { - port->vdm_state = VDM_STATE_ERR_BUSY; - break; - } - - /* - * if there's traffic or we're not in PDO ready state don't send - * a VDM. - */ - if (port->state != SRC_READY && port->state != SNK_READY) - break; - - /* Prepare and send VDM */ - memset(&msg, 0, sizeof(msg)); - msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, - port->pwr_role, - port->data_role, - port->negotiated_rev, - port->message_id, port->vdo_count); - for (i = 0; i < port->vdo_count; i++) - msg.payload[i] = cpu_to_le32(port->vdo_data[i]); - res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); - if (res < 0) { - port->vdm_state = VDM_STATE_ERR_SEND; - } else { - unsigned long timeout; - - port->vdm_retries = 0; - port->vdm_state = VDM_STATE_BUSY; - timeout = vdm_ready_timeout(port->vdo_data[0]); - mod_delayed_work(port->wq, &port->vdm_state_machine, - timeout); - } - break; - case VDM_STATE_WAIT_RSP_BUSY: - port->vdo_data[0] = port->vdo_retry; - port->vdo_count = 1; - port->vdm_state = VDM_STATE_READY; - break; - case VDM_STATE_BUSY: - port->vdm_state = VDM_STATE_ERR_TMOUT; - break; - case VDM_STATE_ERR_SEND: - /* - * A partner which does not support USB PD will not reply, - * so this is not a fatal error. At the same time, some - * devices may not return GoodCRC under some circumstances, - * so we need to retry. - */ - if (port->vdm_retries < 3) { - tcpm_log(port, "VDM Tx error, retry"); - port->vdm_retries++; - port->vdm_state = VDM_STATE_READY; - } - break; - default: - break; - } -} - -static void vdm_state_machine_work(struct work_struct *work) -{ - struct tcpm_port *port = container_of(work, struct tcpm_port, - vdm_state_machine.work); - enum vdm_states prev_state; - - mutex_lock(&port->lock); - - /* - * Continue running as long as the port is not busy and there was - * a state change. - */ - do { - prev_state = port->vdm_state; - vdm_run_state_machine(port); - } while (port->vdm_state != prev_state && - port->vdm_state != VDM_STATE_BUSY); - - mutex_unlock(&port->lock); -} - -enum pdo_err { - PDO_NO_ERR, - PDO_ERR_NO_VSAFE5V, - PDO_ERR_VSAFE5V_NOT_FIRST, - PDO_ERR_PDO_TYPE_NOT_IN_ORDER, - 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[] = { - [PDO_ERR_NO_VSAFE5V] = - " err: source/sink caps should atleast have vSafe5V", - [PDO_ERR_VSAFE5V_NOT_FIRST] = - " err: vSafe5V Fixed Supply Object Shall always be the first object", - [PDO_ERR_PDO_TYPE_NOT_IN_ORDER] = - " err: PDOs should be in the following order: Fixed; Battery; Variable", - [PDO_ERR_FIXED_NOT_SORTED] = - " err: Fixed supply pdos should be in increasing order of their fixed voltage", - [PDO_ERR_VARIABLE_BATT_NOT_SORTED] = - " 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, - unsigned int nr_pdo) -{ - unsigned int i; - - /* Should at least contain vSafe5v */ - if (nr_pdo < 1) - return PDO_ERR_NO_VSAFE5V; - - /* The vSafe5V Fixed Supply Object Shall always be the first object */ - if (pdo_type(pdo[0]) != PDO_TYPE_FIXED || - pdo_fixed_voltage(pdo[0]) != VSAFE5V) - return PDO_ERR_VSAFE5V_NOT_FIRST; - - for (i = 1; i < nr_pdo; i++) { - if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) { - return PDO_ERR_PDO_TYPE_NOT_IN_ORDER; - } else if (pdo_type(pdo[i]) == pdo_type(pdo[i - 1])) { - enum pd_pdo_type type = pdo_type(pdo[i]); - - switch (type) { - /* - * The remaining Fixed Supply Objects, if - * present, shall be sent in voltage order; - * lowest to highest. - */ - case PDO_TYPE_FIXED: - if (pdo_fixed_voltage(pdo[i]) <= - pdo_fixed_voltage(pdo[i - 1])) - return PDO_ERR_FIXED_NOT_SORTED; - break; - /* - * The Battery Supply Objects and Variable - * supply, if present shall be sent in Minimum - * Voltage order; lowest to highest. - */ - case PDO_TYPE_VAR: - case PDO_TYPE_BATT: - if (pdo_min_voltage(pdo[i]) < - pdo_min_voltage(pdo[i - 1])) - return PDO_ERR_VARIABLE_BATT_NOT_SORTED; - else if ((pdo_min_voltage(pdo[i]) == - pdo_min_voltage(pdo[i - 1])) && - (pdo_max_voltage(pdo[i]) == - 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"); - } - } - } - - return PDO_NO_ERR; -} - -static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, - unsigned int nr_pdo) -{ - enum pdo_err err_index = tcpm_caps_err(port, pdo, nr_pdo); - - if (err_index != PDO_NO_ERR) { - tcpm_log_force(port, " %s", pdo_err_msg[err_index]); - return -EINVAL; - } - - return 0; -} - -static int tcpm_altmode_enter(struct typec_altmode *altmode) -{ - struct tcpm_port *port = typec_altmode_get_drvdata(altmode); - u32 header; - - mutex_lock(&port->lock); - header = VDO(altmode->svid, 1, CMD_ENTER_MODE); - header |= VDO_OPOS(altmode->mode); - - tcpm_queue_vdm(port, header, NULL, 0); - mod_delayed_work(port->wq, &port->vdm_state_machine, 0); - mutex_unlock(&port->lock); - - return 0; -} - -static int tcpm_altmode_exit(struct typec_altmode *altmode) -{ - struct tcpm_port *port = typec_altmode_get_drvdata(altmode); - u32 header; - - mutex_lock(&port->lock); - header = VDO(altmode->svid, 1, CMD_EXIT_MODE); - header |= VDO_OPOS(altmode->mode); - - tcpm_queue_vdm(port, header, NULL, 0); - mod_delayed_work(port->wq, &port->vdm_state_machine, 0); - mutex_unlock(&port->lock); - - return 0; -} - -static int tcpm_altmode_vdm(struct typec_altmode *altmode, - u32 header, const u32 *data, int count) -{ - struct tcpm_port *port = typec_altmode_get_drvdata(altmode); - - mutex_lock(&port->lock); - tcpm_queue_vdm(port, header, data, count - 1); - mod_delayed_work(port->wq, &port->vdm_state_machine, 0); - mutex_unlock(&port->lock); - - return 0; -} - -static const struct typec_altmode_ops tcpm_altmode_ops = { - .enter = tcpm_altmode_enter, - .exit = tcpm_altmode_exit, - .vdm = tcpm_altmode_vdm, -}; - -/* - * PD (data, control) command handling functions - */ -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) -{ - 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) { - case PD_DATA_SOURCE_CAP: - if (port->pwr_role != TYPEC_SINK) - break; - - for (i = 0; i < cnt; i++) - port->source_caps[i] = le32_to_cpu(msg->payload[i]); - - port->nr_source_caps = cnt; - - tcpm_log_source_caps(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 - * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2. - * However, at the same time, we must be ready to - * receive this message and respond to it 15ms after - * receiving PS_RDY during power swap operations, no matter - * if VBUS is available or not (USB PD specification, - * section 6.5.9.2). - * So we need to accept the message either way, - * but be prepared to keep waiting for VBUS after it was - * handled. - */ - tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); - break; - case PD_DATA_REQUEST: - if (port->pwr_role != TYPEC_SOURCE || - cnt != 1) { - 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; - case PD_DATA_SINK_CAP: - /* We don't do anything with this at the moment... */ - for (i = 0; i < cnt; i++) - port->sink_caps[i] = le32_to_cpu(msg->payload[i]); - port->nr_sink_caps = cnt; - break; - case PD_DATA_VENDOR_DEF: - tcpm_handle_vdm_request(port, msg->payload, cnt); - break; - case PD_DATA_BIST: - if (port->state == SRC_READY || port->state == SNK_READY) { - port->bist_request = le32_to_cpu(msg->payload[0]); - 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; - } -} - -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) -{ - enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); - enum tcpm_state next_state; - - switch (type) { - case PD_CTRL_GOOD_CRC: - case PD_CTRL_PING: - break; - case PD_CTRL_GET_SOURCE_CAP: - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_queue_message(port, PD_MSG_DATA_SOURCE_CAP); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } - break; - case PD_CTRL_GET_SINK_CAP: - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_queue_message(port, PD_MSG_DATA_SINK_CAP); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } - break; - case PD_CTRL_GOTO_MIN: - break; - case PD_CTRL_PS_RDY: - switch (port->state) { - case SNK_TRANSITION_SINK: - if (port->vbus_present) { - tcpm_set_current_limit(port, - port->current_limit, - port->supply_voltage); - port->explicit_contract = true; - tcpm_set_state(port, SNK_READY, 0); - } else { - /* - * Seen after power swap. Keep waiting for VBUS - * in a transitional state. - */ - tcpm_set_state(port, - SNK_TRANSITION_SINK_VBUS, 0); - } - break; - case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: - tcpm_set_state(port, PR_SWAP_SRC_SNK_SINK_ON, 0); - break; - case PR_SWAP_SNK_SRC_SINK_OFF: - tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON, 0); - break; - case VCONN_SWAP_WAIT_FOR_VCONN: - tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0); - break; - default: - break; - } - 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 */ - if (port->explicit_contract) - next_state = SNK_READY; - else - 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); - tcpm_set_state(port, DR_SWAP_CANCEL, 0); - break; - case PR_SWAP_SEND: - port->swap_status = (type == PD_CTRL_WAIT ? - -EAGAIN : -EOPNOTSUPP); - tcpm_set_state(port, PR_SWAP_CANCEL, 0); - break; - case VCONN_SWAP_SEND: - port->swap_status = (type == PD_CTRL_WAIT ? - -EAGAIN : -EOPNOTSUPP); - tcpm_set_state(port, VCONN_SWAP_CANCEL, 0); - break; - default: - break; - } - break; - 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: - port->message_id = 0; - port->rx_msgid = -1; - if (port->pwr_role == TYPEC_SOURCE) - next_state = SRC_SEND_CAPABILITIES; - else - next_state = SNK_WAIT_CAPABILITIES; - tcpm_set_state(port, next_state, 0); - break; - case DR_SWAP_SEND: - tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0); - break; - case PR_SWAP_SEND: - tcpm_set_state(port, PR_SWAP_START, 0); - break; - case VCONN_SWAP_SEND: - tcpm_set_state(port, VCONN_SWAP_START, 0); - break; - default: - break; - } - break; - case PD_CTRL_SOFT_RESET: - tcpm_set_state(port, SOFT_RESET, 0); - break; - case PD_CTRL_DR_SWAP: - if (port->port_type != TYPEC_PORT_DRP) { - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } - /* - * XXX - * 6.3.9: If an alternate mode is active, a request to swap - * alternate modes shall trigger a port reset. - */ - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_set_state(port, DR_SWAP_ACCEPT, 0); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_WAIT); - break; - } - break; - case PD_CTRL_PR_SWAP: - if (port->port_type != TYPEC_PORT_DRP) { - tcpm_queue_message(port, PD_MSG_CTRL_REJECT); - break; - } - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_set_state(port, PR_SWAP_ACCEPT, 0); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_WAIT); - break; - } - break; - case PD_CTRL_VCONN_SWAP: - switch (port->state) { - case SRC_READY: - case SNK_READY: - tcpm_set_state(port, VCONN_SWAP_ACCEPT, 0); - break; - default: - tcpm_queue_message(port, PD_MSG_CTRL_WAIT); - 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, - struct pd_rx_event, work); - const struct pd_message *msg = &event->msg; - unsigned int cnt = pd_header_cnt_le(msg->header); - struct tcpm_port *port = event->port; - - mutex_lock(&port->lock); - - tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header), - port->attached); - - if (port->attached) { - enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); - unsigned int msgid = pd_header_msgid_le(msg->header); - - /* - * USB PD standard, 6.6.1.2: - * "... if MessageID value in a received Message is the - * same as the stored value, the receiver shall return a - * GoodCRC Message with that MessageID value and drop - * the Message (this is a retry of an already received - * Message). Note: this shall not apply to the Soft_Reset - * Message which always has a MessageID value of zero." - */ - if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET) - goto done; - port->rx_msgid = msgid; - - /* - * If both ends believe to be DFP/host, we have a data role - * mismatch. - */ - if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) == - (port->data_role == TYPEC_HOST)) { - tcpm_log(port, - "Data role mismatch, initiating error recovery"); - tcpm_set_state(port, ERROR_RECOVERY, 0); - } else { - 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); - } - } - -done: - mutex_unlock(&port->lock); - kfree(event); -} - -void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) -{ - struct pd_rx_event *event; - - event = kzalloc(sizeof(*event), GFP_ATOMIC); - if (!event) - return; - - INIT_WORK(&event->work, tcpm_pd_rx_handler); - event->port = port; - memcpy(&event->msg, msg, sizeof(*msg)); - queue_work(port->wq, &event->work); -} -EXPORT_SYMBOL_GPL(tcpm_pd_receive); - -static int tcpm_pd_send_control(struct tcpm_port *port, - enum pd_ctrl_msg_type type) -{ - struct pd_message msg; - - memset(&msg, 0, sizeof(msg)); - msg.header = PD_HEADER_LE(type, port->pwr_role, - port->data_role, - port->negotiated_rev, - port->message_id, 0); - - return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); -} - -/* - * Send queued message without affecting state. - * Return true if state machine should go back to sleep, - * false otherwise. - */ -static bool tcpm_send_queued_message(struct tcpm_port *port) -{ - enum pd_msg_request queued_message; - - do { - queued_message = port->queued_message; - port->queued_message = PD_MSG_NONE; - - switch (queued_message) { - case PD_MSG_CTRL_WAIT: - tcpm_pd_send_control(port, PD_CTRL_WAIT); - break; - 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; - case PD_MSG_DATA_SOURCE_CAP: - tcpm_pd_send_source_caps(port); - break; - default: - break; - } - } while (port->queued_message != PD_MSG_NONE); - - if (port->delayed_state != INVALID_STATE) { - if (time_is_after_jiffies(port->delayed_runtime)) { - mod_delayed_work(port->wq, &port->state_machine, - port->delayed_runtime - jiffies); - return true; - } - port->delayed_state = INVALID_STATE; - } - return false; -} - -static int tcpm_pd_check_request(struct tcpm_port *port) -{ - u32 pdo, rdo = port->sink_request; - unsigned int max, op, pdo_max, index; - enum pd_pdo_type type; - - index = rdo_index(rdo); - if (!index || index > port->nr_src_pdo) - return -EINVAL; - - pdo = port->src_pdo[index - 1]; - type = pdo_type(pdo); - switch (type) { - case PDO_TYPE_FIXED: - case PDO_TYPE_VAR: - max = rdo_max_current(rdo); - op = rdo_op_current(rdo); - pdo_max = pdo_max_current(pdo); - - if (op > pdo_max) - return -EINVAL; - if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) - return -EINVAL; - - if (type == PDO_TYPE_FIXED) - tcpm_log(port, - "Requested %u mV, %u mA for %u / %u mA", - pdo_fixed_voltage(pdo), pdo_max, op, max); - else - tcpm_log(port, - "Requested %u -> %u mV, %u mA for %u / %u mA", - pdo_min_voltage(pdo), pdo_max_voltage(pdo), - pdo_max, op, max); - break; - case PDO_TYPE_BATT: - max = rdo_max_power(rdo); - op = rdo_op_power(rdo); - pdo_max = pdo_max_power(pdo); - - if (op > pdo_max) - return -EINVAL; - if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) - return -EINVAL; - tcpm_log(port, - "Requested %u -> %u mV, %u mW for %u / %u mW", - pdo_min_voltage(pdo), pdo_max_voltage(pdo), - pdo_max, op, max); - break; - default: - return -EINVAL; - } - - port->op_vsafe5v = index == 1; - - return 0; -} - -#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, 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; - - port->pps_data.supported = false; - port->usb_type = POWER_SUPPLY_USB_TYPE_PD; - - /* - * 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); - - switch (type) { - case PDO_TYPE_FIXED: - max_src_mv = pdo_fixed_voltage(pdo); - min_src_mv = max_src_mv; - 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; - port->usb_type = - POWER_SUPPLY_USB_TYPE_PD_PPS; - } - continue; - default: - tcpm_log(port, "Invalid source PDO type, ignoring"); - continue; - } - - 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]; - - switch (pdo_type(pdo)) { - case PDO_TYPE_FIXED: - max_snk_mv = pdo_fixed_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 && - 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; - } - } - } - } - - 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 = 1; 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; - unsigned int max_ma, max_mw; - enum pd_pdo_type type; - 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); - 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); - - switch (type) { - case PDO_TYPE_FIXED: - mv = pdo_fixed_voltage(pdo); - 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) { - mw = min_power(pdo, matching_snk_pdo); - ma = 1000 * mw / mv; - } else { - ma = min_current(pdo, matching_snk_pdo); - mw = ma * mv / 1000; - } - - flags = RDO_USB_COMM | RDO_NO_SUSPEND; - - /* Set mismatch bit if offered power is less than operating power */ - max_ma = ma; - max_mw = mw; - if (mw < port->operating_snk_mw) { - flags |= RDO_CAP_MISMATCH; - 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", - port->cc_req, port->cc1, port->cc2, port->vbus_source, - port->vconn_role == TYPEC_SOURCE ? "source" : "sink", - port->polarity); - - if (type == PDO_TYPE_BATT) { - *rdo = RDO_BATT(src_pdo_index + 1, mw, max_mw, flags); - - tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s", - src_pdo_index, mv, mw, - flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); - } else { - *rdo = RDO_FIXED(src_pdo_index + 1, ma, max_ma, flags); - - tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s", - src_pdo_index, mv, ma, - flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); - } - - port->current_limit = ma; - port->supply_voltage = mv; - - return 0; -} - -static int tcpm_pd_send_request(struct tcpm_port *port) -{ - struct pd_message msg; - int ret; - u32 rdo; - - ret = tcpm_pd_build_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); - - 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); - - return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); -} - -static int tcpm_set_vbus(struct tcpm_port *port, bool enable) -{ - int ret; - - if (enable && port->vbus_charge) - return -EINVAL; - - tcpm_log(port, "vbus:=%d charge=%d", enable, port->vbus_charge); - - ret = port->tcpc->set_vbus(port->tcpc, enable, port->vbus_charge); - if (ret < 0) - return ret; - - port->vbus_source = enable; - return 0; -} - -static int tcpm_set_charge(struct tcpm_port *port, bool charge) -{ - int ret; - - if (charge && port->vbus_source) - return -EINVAL; - - if (charge != port->vbus_charge) { - tcpm_log(port, "vbus=%d charge:=%d", port->vbus_source, charge); - ret = port->tcpc->set_vbus(port->tcpc, port->vbus_source, - charge); - if (ret < 0) - return ret; - } - port->vbus_charge = charge; - return 0; -} - -static bool tcpm_start_drp_toggling(struct tcpm_port *port, - enum typec_cc_status cc) -{ - int ret; - - if (port->tcpc->start_drp_toggling && - port->port_type == TYPEC_PORT_DRP) { - tcpm_log_force(port, "Start DRP toggling"); - ret = port->tcpc->start_drp_toggling(port->tcpc, cc); - if (!ret) - return true; - } - - return false; -} - -static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc) -{ - tcpm_log(port, "cc:=%d", cc); - port->cc_req = cc; - port->tcpc->set_cc(port->tcpc, cc); -} - -static int tcpm_init_vbus(struct tcpm_port *port) -{ - int ret; - - ret = port->tcpc->set_vbus(port->tcpc, false, false); - port->vbus_source = false; - port->vbus_charge = false; - return ret; -} - -static int tcpm_init_vconn(struct tcpm_port *port) -{ - int ret; - - ret = port->tcpc->set_vconn(port->tcpc, false); - port->vconn_role = TYPEC_SINK; - return ret; -} - -static void tcpm_typec_connect(struct tcpm_port *port) -{ - if (!port->connected) { - /* Make sure we don't report stale identity information */ - memset(&port->partner_ident, 0, sizeof(port->partner_ident)); - port->partner_desc.usb_pd = port->pd_capable; - if (tcpm_port_is_debug(port)) - port->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG; - else if (tcpm_port_is_audio(port)) - port->partner_desc.accessory = TYPEC_ACCESSORY_AUDIO; - else - port->partner_desc.accessory = TYPEC_ACCESSORY_NONE; - port->partner = typec_register_partner(port->typec_port, - &port->partner_desc); - port->connected = true; - } -} - -static int tcpm_src_attach(struct tcpm_port *port) -{ - enum typec_cc_polarity polarity = - port->cc2 == TYPEC_CC_RD ? TYPEC_POLARITY_CC2 - : TYPEC_POLARITY_CC1; - int ret; - - if (port->attached) - return 0; - - ret = tcpm_set_polarity(port, polarity); - if (ret < 0) - return ret; - - ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); - if (ret < 0) - return ret; - - ret = port->tcpc->set_pd_rx(port->tcpc, true); - if (ret < 0) - goto out_disable_mux; - - /* - * USB Type-C specification, version 1.2, - * chapter 4.5.2.2.8.1 (Attached.SRC Requirements) - * Enable VCONN only if the non-RD port is set to RA. - */ - if ((polarity == TYPEC_POLARITY_CC1 && port->cc2 == TYPEC_CC_RA) || - (polarity == TYPEC_POLARITY_CC2 && port->cc1 == TYPEC_CC_RA)) { - ret = tcpm_set_vconn(port, true); - if (ret < 0) - goto out_disable_pd; - } - - ret = tcpm_set_vbus(port, true); - if (ret < 0) - goto out_disable_vconn; - - port->pd_capable = false; - - port->partner = NULL; - - port->attached = true; - port->send_discover = true; - - return 0; - -out_disable_vconn: - tcpm_set_vconn(port, false); -out_disable_pd: - port->tcpc->set_pd_rx(port->tcpc, false); -out_disable_mux: - tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, - TYPEC_ORIENTATION_NONE); - return ret; -} - -static void tcpm_typec_disconnect(struct tcpm_port *port) -{ - if (port->connected) { - typec_unregister_partner(port->partner); - port->partner = NULL; - port->connected = false; - } -} - -static void tcpm_unregister_altmodes(struct tcpm_port *port) -{ - struct pd_mode_data *modep = &port->mode_data; - int i; - - for (i = 0; i < modep->altmodes; i++) { - typec_unregister_altmode(port->partner_altmode[i]); - port->partner_altmode[i] = NULL; - } - - memset(modep, 0, sizeof(*modep)); -} - -static void tcpm_reset_port(struct tcpm_port *port) -{ - tcpm_unregister_altmodes(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 - * we can check tcpm_pd_rx_handler() if we had seen it before. - */ - port->rx_msgid = -1; - - port->tcpc->set_pd_rx(port->tcpc, false); - tcpm_init_vbus(port); /* also disables charging */ - tcpm_init_vconn(port); - tcpm_set_current_limit(port, 0, 0); - tcpm_set_polarity(port, TYPEC_POLARITY_CC1); - tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, - TYPEC_ORIENTATION_NONE); - tcpm_set_attached_state(port, false); - port->try_src_count = 0; - port->try_snk_count = 0; - port->usb_type = POWER_SUPPLY_USB_TYPE_C; - - power_supply_changed(port->psy); -} - -static void tcpm_detach(struct tcpm_port *port) -{ - if (!port->attached) - return; - - if (tcpm_port_is_disconnected(port)) - port->hard_reset_count = 0; - - tcpm_reset_port(port); -} - -static void tcpm_src_detach(struct tcpm_port *port) -{ - tcpm_detach(port); -} - -static int tcpm_snk_attach(struct tcpm_port *port) -{ - int ret; - - if (port->attached) - return 0; - - ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ? - TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1); - if (ret < 0) - return ret; - - ret = tcpm_set_roles(port, true, TYPEC_SINK, TYPEC_DEVICE); - if (ret < 0) - return ret; - - port->pd_capable = false; - - port->partner = NULL; - - port->attached = true; - port->send_discover = true; - - return 0; -} - -static void tcpm_snk_detach(struct tcpm_port *port) -{ - tcpm_detach(port); -} - -static int tcpm_acc_attach(struct tcpm_port *port) -{ - int ret; - - if (port->attached) - return 0; - - ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); - if (ret < 0) - return ret; - - port->partner = NULL; - - tcpm_typec_connect(port); - - port->attached = true; - - return 0; -} - -static void tcpm_acc_detach(struct tcpm_port *port) -{ - tcpm_detach(port); -} - -static inline enum tcpm_state hard_reset_state(struct tcpm_port *port) -{ - if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) - return HARD_RESET_SEND; - if (port->pd_capable) - return ERROR_RECOVERY; - if (port->pwr_role == TYPEC_SOURCE) - return SRC_UNATTACHED; - if (port->state == SNK_WAIT_CAPABILITIES) - return SNK_READY; - return SNK_UNATTACHED; -} - -static inline enum tcpm_state unattached_state(struct tcpm_port *port) -{ - if (port->port_type == TYPEC_PORT_DRP) { - if (port->pwr_role == TYPEC_SOURCE) - return SRC_UNATTACHED; - else - return SNK_UNATTACHED; - } else if (port->port_type == TYPEC_PORT_SRC) { - return SRC_UNATTACHED; - } - - return SNK_UNATTACHED; -} - -static void tcpm_check_send_discover(struct tcpm_port *port) -{ - if (port->data_role == TYPEC_HOST && port->send_discover && - port->pd_capable) { - tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0); - port->send_discover = false; - } -} - -static void tcpm_swap_complete(struct tcpm_port *port, int result) -{ - if (port->swap_pending) { - port->swap_status = result; - port->swap_pending = false; - port->non_pd_role_swap = false; - complete(&port->swap_complete); - } -} - -static enum typec_pwr_opmode tcpm_get_pwr_opmode(enum typec_cc_status cc) -{ - switch (cc) { - case TYPEC_CC_RP_1_5: - return TYPEC_PWR_MODE_1_5A; - case TYPEC_CC_RP_3_0: - return TYPEC_PWR_MODE_3_0A; - case TYPEC_CC_RP_DEF: - default: - return TYPEC_PWR_MODE_USB; - } -} - -static void run_state_machine(struct tcpm_port *port) -{ - int ret; - enum typec_pwr_opmode opmode; - unsigned int msecs; - - port->enter_state = port->state; - switch (port->state) { - case DRP_TOGGLING: - break; - /* SRC states */ - case SRC_UNATTACHED: - if (!port->non_pd_role_swap) - tcpm_swap_complete(port, -ENOTCONN); - tcpm_src_detach(port); - if (tcpm_start_drp_toggling(port, tcpm_rp_cc(port))) { - tcpm_set_state(port, DRP_TOGGLING, 0); - break; - } - tcpm_set_cc(port, tcpm_rp_cc(port)); - if (port->port_type == TYPEC_PORT_DRP) - tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK); - break; - case SRC_ATTACH_WAIT: - if (tcpm_port_is_debug(port)) - tcpm_set_state(port, DEBUG_ACC_ATTACHED, - PD_T_CC_DEBOUNCE); - else if (tcpm_port_is_audio(port)) - tcpm_set_state(port, AUDIO_ACC_ATTACHED, - PD_T_CC_DEBOUNCE); - else if (tcpm_port_is_source(port)) - tcpm_set_state(port, - tcpm_try_snk(port) ? SNK_TRY - : SRC_ATTACHED, - PD_T_CC_DEBOUNCE); - break; - - case SNK_TRY: - port->try_snk_count++; - /* - * Requirements: - * - Do not drive vconn or vbus - * - Terminate CC pins (both) to Rd - * Action: - * - Wait for tDRPTry (PD_T_DRP_TRY). - * Until then, ignore any state changes. - */ - tcpm_set_cc(port, TYPEC_CC_RD); - tcpm_set_state(port, SNK_TRY_WAIT, PD_T_DRP_TRY); - break; - case SNK_TRY_WAIT: - if (tcpm_port_is_sink(port)) { - tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE, 0); - } else { - tcpm_set_state(port, SRC_TRYWAIT, 0); - port->max_wait = 0; - } - break; - case SNK_TRY_WAIT_DEBOUNCE: - tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, - PD_T_PD_DEBOUNCE); - break; - case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: - if (port->vbus_present && tcpm_port_is_sink(port)) { - tcpm_set_state(port, SNK_ATTACHED, 0); - } else { - tcpm_set_state(port, SRC_TRYWAIT, 0); - port->max_wait = 0; - } - break; - case SRC_TRYWAIT: - tcpm_set_cc(port, tcpm_rp_cc(port)); - if (port->max_wait == 0) { - port->max_wait = jiffies + - msecs_to_jiffies(PD_T_DRP_TRY); - tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, - PD_T_DRP_TRY); - } else { - if (time_is_after_jiffies(port->max_wait)) - tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, - jiffies_to_msecs(port->max_wait - - jiffies)); - else - tcpm_set_state(port, SNK_UNATTACHED, 0); - } - break; - case SRC_TRYWAIT_DEBOUNCE: - tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE); - break; - case SRC_TRYWAIT_UNATTACHED: - tcpm_set_state(port, SNK_UNATTACHED, 0); - break; - - case SRC_ATTACHED: - ret = tcpm_src_attach(port); - tcpm_set_state(port, SRC_UNATTACHED, - ret < 0 ? 0 : PD_T_PS_SOURCE_ON); - break; - case SRC_STARTUP: - opmode = tcpm_get_pwr_opmode(tcpm_rp_cc(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; - tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); - break; - case SRC_SEND_CAPABILITIES: - port->caps_count++; - if (port->caps_count > PD_N_CAPS_COUNT) { - tcpm_set_state(port, SRC_READY, 0); - break; - } - ret = tcpm_pd_send_source_caps(port); - if (ret < 0) { - tcpm_set_state(port, SRC_SEND_CAPABILITIES, - PD_T_SEND_SOURCE_CAP); - } else { - /* - * Per standard, we should clear the reset counter here. - * However, that can result in state machine hang-ups. - * Reset it only in READY state to improve stability. - */ - /* port->hard_reset_count = 0; */ - port->caps_count = 0; - port->pd_capable = true; - tcpm_set_state_cond(port, hard_reset_state(port), - PD_T_SEND_SOURCE_CAP); - } - break; - case SRC_NEGOTIATE_CAPABILITIES: - ret = tcpm_pd_check_request(port); - if (ret < 0) { - tcpm_pd_send_control(port, PD_CTRL_REJECT); - if (!port->explicit_contract) { - tcpm_set_state(port, - SRC_WAIT_NEW_CAPABILITIES, 0); - } else { - tcpm_set_state(port, SRC_READY, 0); - } - } else { - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - tcpm_set_state(port, SRC_TRANSITION_SUPPLY, - PD_T_SRC_TRANSITION); - } - break; - case SRC_TRANSITION_SUPPLY: - /* XXX: regulator_set_voltage(vbus, ...) */ - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - port->explicit_contract = true; - typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); - port->pwr_opmode = TYPEC_PWR_MODE_PD; - tcpm_set_state_cond(port, SRC_READY, 0); - break; - case SRC_READY: -#if 1 - port->hard_reset_count = 0; -#endif - port->try_src_count = 0; - - tcpm_swap_complete(port, 0); - tcpm_typec_connect(port); - - tcpm_check_send_discover(port); - /* - * 6.3.5 - * Sending ping messages is not necessary if - * - the source operates at vSafe5V - * or - * - The system is not operating in PD mode - * or - * - Both partners are connected using a Type-C connector - * - * There is no actual need to send PD messages since the local - * port type-c and the spec does not clearly say whether PD is - * possible when type-c is connected to Type-A/B - */ - break; - case SRC_WAIT_NEW_CAPABILITIES: - /* Nothing to do... */ - break; - - /* SNK states */ - 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, TYPEC_CC_RD)) { - tcpm_set_state(port, DRP_TOGGLING, 0); - break; - } - tcpm_set_cc(port, TYPEC_CC_RD); - if (port->port_type == TYPEC_PORT_DRP) - tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC); - break; - case SNK_ATTACH_WAIT: - if ((port->cc1 == TYPEC_CC_OPEN && - port->cc2 != TYPEC_CC_OPEN) || - (port->cc1 != TYPEC_CC_OPEN && - port->cc2 == TYPEC_CC_OPEN)) - tcpm_set_state(port, SNK_DEBOUNCED, - PD_T_CC_DEBOUNCE); - else if (tcpm_port_is_disconnected(port)) - tcpm_set_state(port, SNK_UNATTACHED, - PD_T_PD_DEBOUNCE); - break; - case SNK_DEBOUNCED: - if (tcpm_port_is_disconnected(port)) - tcpm_set_state(port, SNK_UNATTACHED, - PD_T_PD_DEBOUNCE); - else if (port->vbus_present) - tcpm_set_state(port, - tcpm_try_src(port) ? SRC_TRY - : SNK_ATTACHED, - 0); - else - /* Wait for VBUS, but not forever */ - tcpm_set_state(port, PORT_RESET, PD_T_PS_SOURCE_ON); - break; - - case SRC_TRY: - port->try_src_count++; - tcpm_set_cc(port, tcpm_rp_cc(port)); - port->max_wait = 0; - tcpm_set_state(port, SRC_TRY_WAIT, 0); - break; - case SRC_TRY_WAIT: - if (port->max_wait == 0) { - port->max_wait = jiffies + - msecs_to_jiffies(PD_T_DRP_TRY); - msecs = PD_T_DRP_TRY; - } else { - if (time_is_after_jiffies(port->max_wait)) - msecs = jiffies_to_msecs(port->max_wait - - jiffies); - else - msecs = 0; - } - tcpm_set_state(port, SNK_TRYWAIT, msecs); - break; - case SRC_TRY_DEBOUNCE: - tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE); - break; - case SNK_TRYWAIT: - tcpm_set_cc(port, TYPEC_CC_RD); - tcpm_set_state(port, SNK_TRYWAIT_VBUS, PD_T_CC_DEBOUNCE); - break; - case SNK_TRYWAIT_VBUS: - /* - * TCPM stays in this state indefinitely until VBUS - * is detected as long as Rp is not detected for - * more than a time period of tPDDebounce. - */ - if (port->vbus_present && tcpm_port_is_sink(port)) { - tcpm_set_state(port, SNK_ATTACHED, 0); - break; - } - if (!tcpm_port_is_sink(port)) - tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); - break; - case SNK_TRYWAIT_DEBOUNCE: - tcpm_set_state(port, SNK_UNATTACHED, PD_T_PD_DEBOUNCE); - break; - case SNK_ATTACHED: - ret = tcpm_snk_attach(port); - if (ret < 0) - tcpm_set_state(port, SNK_UNATTACHED, 0); - else - tcpm_set_state(port, SNK_STARTUP, 0); - break; - case SNK_STARTUP: - opmode = tcpm_get_pwr_opmode(port->polarity ? - 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; - tcpm_set_state(port, SNK_DISCOVERY, 0); - break; - case SNK_DISCOVERY: - if (port->vbus_present) { - tcpm_set_current_limit(port, - tcpm_get_current_limit(port), - 5000); - tcpm_set_charge(port, true); - tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); - break; - } - /* - * For DRP, timeouts differ. Also, handling is supposed to be - * different and much more complex (dead battery detection; - * see USB power delivery specification, section 8.3.3.6.1.5.1). - */ - tcpm_set_state(port, hard_reset_state(port), - port->port_type == TYPEC_PORT_DRP ? - PD_T_DB_DETECT : PD_T_NO_RESPONSE); - break; - case SNK_DISCOVERY_DEBOUNCE: - tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE, - PD_T_CC_DEBOUNCE); - break; - case SNK_DISCOVERY_DEBOUNCE_DONE: - if (!tcpm_port_is_disconnected(port) && - tcpm_port_is_sink(port) && - time_is_after_jiffies(port->delayed_runtime)) { - tcpm_set_state(port, SNK_DISCOVERY, - jiffies_to_msecs(port->delayed_runtime - - jiffies)); - break; - } - tcpm_set_state(port, unattached_state(port), 0); - break; - case SNK_WAIT_CAPABILITIES: - ret = port->tcpc->set_pd_rx(port->tcpc, true); - if (ret < 0) { - tcpm_set_state(port, SNK_READY, 0); - break; - } - /* - * If VBUS has never been low, and we time out waiting - * for source cap, try a soft reset first, in case we - * were already in a stable contract before this boot. - * Do this only once. - */ - if (port->vbus_never_low) { - port->vbus_never_low = false; - tcpm_set_state(port, SOFT_RESET_SEND, - PD_T_SINK_WAIT_CAP); - } else { - tcpm_set_state(port, hard_reset_state(port), - PD_T_SINK_WAIT_CAP); - } - break; - case SNK_NEGOTIATE_CAPABILITIES: - port->pd_capable = true; - port->hard_reset_count = 0; - ret = tcpm_pd_send_request(port); - if (ret < 0) { - /* Let the Source send capabilities again. */ - tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); - } else { - tcpm_set_state_cond(port, hard_reset_state(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), - PD_T_PS_TRANSITION); - 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); - port->pwr_opmode = TYPEC_PWR_MODE_PD; - } - - tcpm_swap_complete(port, 0); - tcpm_typec_connect(port); - tcpm_check_send_discover(port); - tcpm_pps_complete(port, port->pps_status); - - power_supply_changed(port->psy); - - break; - - /* Accessory states */ - case ACC_UNATTACHED: - tcpm_acc_detach(port); - tcpm_set_state(port, SRC_UNATTACHED, 0); - break; - case DEBUG_ACC_ATTACHED: - case AUDIO_ACC_ATTACHED: - ret = tcpm_acc_attach(port); - if (ret < 0) - tcpm_set_state(port, ACC_UNATTACHED, 0); - break; - case AUDIO_ACC_DEBOUNCE: - tcpm_set_state(port, ACC_UNATTACHED, PD_T_CC_DEBOUNCE); - break; - - /* Hard_Reset states */ - case HARD_RESET_SEND: - tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); - tcpm_set_state(port, HARD_RESET_START, 0); - break; - case HARD_RESET_START: - port->hard_reset_count++; - port->tcpc->set_pd_rx(port->tcpc, false); - tcpm_unregister_altmodes(port); - port->send_discover = true; - if (port->pwr_role == TYPEC_SOURCE) - tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, - PD_T_PS_HARD_RESET); - else - tcpm_set_state(port, SNK_HARD_RESET_SINK_OFF, 0); - break; - case SRC_HARD_RESET_VBUS_OFF: - tcpm_set_vconn(port, true); - tcpm_set_vbus(port, false); - tcpm_set_roles(port, false, TYPEC_SOURCE, TYPEC_HOST); - tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); - break; - case SRC_HARD_RESET_VBUS_ON: - tcpm_set_vbus(port, true); - port->tcpc->set_pd_rx(port->tcpc, true); - tcpm_set_attached_state(port, true); - 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); - /* - * VBUS may or may not toggle, depending on the adapter. - * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON - * directly after timeout. - */ - tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V); - break; - case SNK_HARD_RESET_WAIT_VBUS: - /* Assume we're disconnected if VBUS doesn't come back. */ - tcpm_set_state(port, SNK_UNATTACHED, - PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON); - break; - case SNK_HARD_RESET_SINK_ON: - /* Note: There is no guarantee that VBUS is on in this state */ - /* - * XXX: - * The specification suggests that dual mode ports in sink - * mode should transition to state PE_SRC_Transition_to_default. - * See USB power delivery specification chapter 8.3.3.6.1.3. - * This would mean to to - * - turn off VCONN, reset power supply - * - request hardware reset - * - turn on VCONN - * - Transition to state PE_Src_Startup - * SNK only ports shall transition to state Snk_Startup - * (see chapter 8.3.3.3.8). - * Similar, dual-mode ports in source mode should transition - * to PE_SNK_Transition_to_default. - */ - tcpm_set_attached_state(port, true); - tcpm_set_state(port, SNK_STARTUP, 0); - break; - - /* Soft_Reset states */ - case SOFT_RESET: - port->message_id = 0; - port->rx_msgid = -1; - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - if (port->pwr_role == TYPEC_SOURCE) - tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); - else - tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); - break; - case SOFT_RESET_SEND: - port->message_id = 0; - port->rx_msgid = -1; - if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET)) - tcpm_set_state_cond(port, hard_reset_state(port), 0); - else - tcpm_set_state_cond(port, hard_reset_state(port), - PD_T_SENDER_RESPONSE); - break; - - /* DR_Swap states */ - case DR_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_DR_SWAP); - tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, - PD_T_SENDER_RESPONSE); - break; - case DR_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); - break; - case DR_SWAP_SEND_TIMEOUT: - tcpm_swap_complete(port, -ETIMEDOUT); - tcpm_set_state(port, ready_state(port), 0); - break; - case DR_SWAP_CHANGE_DR: - if (port->data_role == TYPEC_HOST) { - tcpm_unregister_altmodes(port); - tcpm_set_roles(port, true, port->pwr_role, - TYPEC_DEVICE); - } else { - tcpm_set_roles(port, true, port->pwr_role, - TYPEC_HOST); - port->send_discover = true; - } - tcpm_set_state(port, ready_state(port), 0); - break; - - /* PR_Swap states */ - case PR_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - tcpm_set_state(port, PR_SWAP_START, 0); - break; - case PR_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_PR_SWAP); - tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT, - PD_T_SENDER_RESPONSE); - break; - case PR_SWAP_SEND_TIMEOUT: - tcpm_swap_complete(port, -ETIMEDOUT); - tcpm_set_state(port, ready_state(port), 0); - break; - case PR_SWAP_START: - if (port->pwr_role == TYPEC_SOURCE) - tcpm_set_state(port, PR_SWAP_SRC_SNK_TRANSITION_OFF, - PD_T_SRC_TRANSITION); - else - tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0); - break; - case PR_SWAP_SRC_SNK_TRANSITION_OFF: - tcpm_set_vbus(port, false); - port->explicit_contract = false; - /* allow time for Vbus discharge, must be < tSrcSwapStdby */ - tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, - PD_T_SRCSWAPSTDBY); - break; - case PR_SWAP_SRC_SNK_SOURCE_OFF: - tcpm_set_cc(port, TYPEC_CC_RD); - /* allow CC debounce */ - tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED, - PD_T_CC_DEBOUNCE); - break; - case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: - /* - * USB-PD standard, 6.2.1.4, Port Power Role: - * "During the Power Role Swap Sequence, for the initial Source - * Port, the Port Power Role field shall be set to Sink in the - * PS_RDY Message indicating that the initial Source’s power - * supply is turned off" - */ - tcpm_set_pwr_role(port, TYPEC_SINK); - if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) { - tcpm_set_state(port, ERROR_RECOVERY, 0); - break; - } - tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); - break; - case PR_SWAP_SRC_SNK_SINK_ON: - tcpm_set_state(port, SNK_STARTUP, 0); - break; - case PR_SWAP_SNK_SRC_SINK_OFF: - tcpm_set_charge(port, false); - tcpm_set_state(port, hard_reset_state(port), - PD_T_PS_SOURCE_OFF); - break; - case PR_SWAP_SNK_SRC_SOURCE_ON: - tcpm_set_cc(port, tcpm_rp_cc(port)); - tcpm_set_vbus(port, true); - /* - * allow time VBUS ramp-up, must be < tNewSrc - * Also, this window overlaps with CC debounce as well. - * So, Wait for the max of two which is PD_T_NEWSRC - */ - tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP, - PD_T_NEWSRC); - break; - case PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP: - /* - * USB PD standard, 6.2.1.4: - * "Subsequent Messages initiated by the Policy Engine, - * such as the PS_RDY Message sent to indicate that Vbus - * is ready, will have the Port Power Role field set to - * Source." - */ - tcpm_set_pwr_role(port, TYPEC_SOURCE); - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - tcpm_set_state(port, SRC_STARTUP, 0); - break; - - case VCONN_SWAP_ACCEPT: - tcpm_pd_send_control(port, PD_CTRL_ACCEPT); - tcpm_set_state(port, VCONN_SWAP_START, 0); - break; - case VCONN_SWAP_SEND: - tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP); - tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT, - PD_T_SENDER_RESPONSE); - break; - case VCONN_SWAP_SEND_TIMEOUT: - tcpm_swap_complete(port, -ETIMEDOUT); - tcpm_set_state(port, ready_state(port), 0); - break; - case VCONN_SWAP_START: - if (port->vconn_role == TYPEC_SOURCE) - tcpm_set_state(port, VCONN_SWAP_WAIT_FOR_VCONN, 0); - else - tcpm_set_state(port, VCONN_SWAP_TURN_ON_VCONN, 0); - break; - case VCONN_SWAP_WAIT_FOR_VCONN: - tcpm_set_state(port, hard_reset_state(port), - PD_T_VCONN_SOURCE_ON); - break; - case VCONN_SWAP_TURN_ON_VCONN: - tcpm_set_vconn(port, true); - tcpm_pd_send_control(port, PD_CTRL_PS_RDY); - tcpm_set_state(port, ready_state(port), 0); - break; - case VCONN_SWAP_TURN_OFF_VCONN: - tcpm_set_vconn(port, false); - tcpm_set_state(port, ready_state(port), 0); - break; - - case DR_SWAP_CANCEL: - case PR_SWAP_CANCEL: - case VCONN_SWAP_CANCEL: - tcpm_swap_complete(port, port->swap_status); - if (port->pwr_role == TYPEC_SOURCE) - tcpm_set_state(port, SRC_READY, 0); - else - tcpm_set_state(port, SNK_READY, 0); - break; - - case BIST_RX: - switch (BDO_MODE_MASK(port->bist_request)) { - case BDO_MODE_CARRIER2: - tcpm_pd_transmit(port, TCPC_TX_BIST_MODE_2, NULL); - break; - default: - break; - } - /* 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); - tcpm_set_state(port, PORT_RESET, 0); - break; - case PORT_RESET: - tcpm_reset_port(port); - tcpm_set_cc(port, TYPEC_CC_OPEN); - tcpm_set_state(port, PORT_RESET_WAIT_OFF, - PD_T_ERROR_RECOVERY); - break; - case PORT_RESET_WAIT_OFF: - tcpm_set_state(port, - tcpm_default_state(port), - port->vbus_present ? PD_T_PS_SOURCE_OFF : 0); - break; - default: - WARN(1, "Unexpected port state %d\n", port->state); - break; - } -} - -static void tcpm_state_machine_work(struct work_struct *work) -{ - struct tcpm_port *port = container_of(work, struct tcpm_port, - state_machine.work); - enum tcpm_state prev_state; - - mutex_lock(&port->lock); - port->state_machine_running = true; - - if (port->queued_message && tcpm_send_queued_message(port)) - goto done; - - /* If we were queued due to a delayed state change, update it now */ - if (port->delayed_state) { - tcpm_log(port, "state change %s -> %s [delayed %ld ms]", - tcpm_states[port->state], - tcpm_states[port->delayed_state], port->delay_ms); - port->prev_state = port->state; - port->state = port->delayed_state; - port->delayed_state = INVALID_STATE; - } - - /* - * Continue running as long as we have (non-delayed) state changes - * to make. - */ - do { - prev_state = port->state; - run_state_machine(port); - if (port->queued_message) - tcpm_send_queued_message(port); - } while (port->state != prev_state && !port->delayed_state); - -done: - port->state_machine_running = false; - mutex_unlock(&port->lock); -} - -static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, - enum typec_cc_status cc2) -{ - enum typec_cc_status old_cc1, old_cc2; - enum tcpm_state new_state; - - old_cc1 = port->cc1; - old_cc2 = port->cc2; - port->cc1 = cc1; - port->cc2 = cc2; - - tcpm_log_force(port, - "CC1: %u -> %u, CC2: %u -> %u [state %s, polarity %d, %s]", - old_cc1, cc1, old_cc2, cc2, tcpm_states[port->state], - port->polarity, - tcpm_port_is_disconnected(port) ? "disconnected" - : "connected"); - - switch (port->state) { - case DRP_TOGGLING: - if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || - tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_ATTACH_WAIT, 0); - else if (tcpm_port_is_sink(port)) - tcpm_set_state(port, SNK_ATTACH_WAIT, 0); - break; - case SRC_UNATTACHED: - case ACC_UNATTACHED: - if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || - tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_ATTACH_WAIT, 0); - break; - case SRC_ATTACH_WAIT: - if (tcpm_port_is_disconnected(port) || - tcpm_port_is_audio_detached(port)) - tcpm_set_state(port, SRC_UNATTACHED, 0); - else if (cc1 != old_cc1 || cc2 != old_cc2) - tcpm_set_state(port, SRC_ATTACH_WAIT, 0); - break; - case SRC_ATTACHED: - case SRC_SEND_CAPABILITIES: - case SRC_READY: - if (tcpm_port_is_disconnected(port) || - !tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_UNATTACHED, 0); - break; - case SNK_UNATTACHED: - if (tcpm_port_is_sink(port)) - tcpm_set_state(port, SNK_ATTACH_WAIT, 0); - break; - case SNK_ATTACH_WAIT: - if ((port->cc1 == TYPEC_CC_OPEN && - port->cc2 != TYPEC_CC_OPEN) || - (port->cc1 != TYPEC_CC_OPEN && - port->cc2 == TYPEC_CC_OPEN)) - new_state = SNK_DEBOUNCED; - else if (tcpm_port_is_disconnected(port)) - new_state = SNK_UNATTACHED; - else - break; - if (new_state != port->delayed_state) - tcpm_set_state(port, SNK_ATTACH_WAIT, 0); - break; - case SNK_DEBOUNCED: - if (tcpm_port_is_disconnected(port)) - new_state = SNK_UNATTACHED; - else if (port->vbus_present) - new_state = tcpm_try_src(port) ? SRC_TRY : SNK_ATTACHED; - else - new_state = SNK_UNATTACHED; - if (new_state != port->delayed_state) - tcpm_set_state(port, SNK_DEBOUNCED, 0); - break; - case SNK_READY: - if (tcpm_port_is_disconnected(port)) - tcpm_set_state(port, unattached_state(port), 0); - else if (!port->pd_capable && - (cc1 != old_cc1 || cc2 != old_cc2)) - tcpm_set_current_limit(port, - tcpm_get_current_limit(port), - 5000); - break; - - case AUDIO_ACC_ATTACHED: - if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) - tcpm_set_state(port, AUDIO_ACC_DEBOUNCE, 0); - break; - case AUDIO_ACC_DEBOUNCE: - if (tcpm_port_is_audio(port)) - tcpm_set_state(port, AUDIO_ACC_ATTACHED, 0); - break; - - case DEBUG_ACC_ATTACHED: - if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) - tcpm_set_state(port, ACC_UNATTACHED, 0); - break; - - case SNK_TRY: - /* Do nothing, waiting for timeout */ - break; - - case SNK_DISCOVERY: - /* CC line is unstable, wait for debounce */ - if (tcpm_port_is_disconnected(port)) - tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE, 0); - break; - case SNK_DISCOVERY_DEBOUNCE: - break; - - case SRC_TRYWAIT: - /* Hand over to state machine if needed */ - if (!port->vbus_present && tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); - break; - case SRC_TRYWAIT_DEBOUNCE: - if (port->vbus_present || !tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_TRYWAIT, 0); - break; - case SNK_TRY_WAIT_DEBOUNCE: - if (!tcpm_port_is_sink(port)) { - port->max_wait = 0; - tcpm_set_state(port, SRC_TRYWAIT, 0); - } - break; - case SRC_TRY_WAIT: - if (tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0); - break; - case SRC_TRY_DEBOUNCE: - tcpm_set_state(port, SRC_TRY_WAIT, 0); - break; - case SNK_TRYWAIT_DEBOUNCE: - if (tcpm_port_is_sink(port)) - tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0); - break; - case SNK_TRYWAIT_VBUS: - if (!tcpm_port_is_sink(port)) - tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); - break; - case SNK_TRYWAIT: - /* Do nothing, waiting for tCCDebounce */ - break; - case PR_SWAP_SNK_SRC_SINK_OFF: - case PR_SWAP_SRC_SNK_TRANSITION_OFF: - case PR_SWAP_SRC_SNK_SOURCE_OFF: - case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: - case PR_SWAP_SNK_SRC_SOURCE_ON: - /* - * CC state change is expected in PR_SWAP - * Ignore it. - */ - break; - - default: - if (tcpm_port_is_disconnected(port)) - tcpm_set_state(port, unattached_state(port), 0); - break; - } -} - -static void _tcpm_pd_vbus_on(struct tcpm_port *port) -{ - tcpm_log_force(port, "VBUS on"); - port->vbus_present = true; - switch (port->state) { - case SNK_TRANSITION_SINK_VBUS: - port->explicit_contract = true; - tcpm_set_state(port, SNK_READY, 0); - break; - case SNK_DISCOVERY: - tcpm_set_state(port, SNK_DISCOVERY, 0); - break; - - case SNK_DEBOUNCED: - tcpm_set_state(port, tcpm_try_src(port) ? SRC_TRY - : SNK_ATTACHED, - 0); - break; - case SNK_HARD_RESET_WAIT_VBUS: - tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, 0); - break; - case SRC_ATTACHED: - tcpm_set_state(port, SRC_STARTUP, 0); - break; - case SRC_HARD_RESET_VBUS_ON: - tcpm_set_state(port, SRC_STARTUP, 0); - break; - - case SNK_TRY: - /* Do nothing, waiting for timeout */ - break; - case SRC_TRYWAIT: - /* Do nothing, Waiting for Rd to be detected */ - break; - case SRC_TRYWAIT_DEBOUNCE: - tcpm_set_state(port, SRC_TRYWAIT, 0); - break; - case SNK_TRY_WAIT_DEBOUNCE: - /* Do nothing, waiting for PD_DEBOUNCE to do be done */ - break; - case SNK_TRYWAIT: - /* Do nothing, waiting for tCCDebounce */ - break; - case SNK_TRYWAIT_VBUS: - if (tcpm_port_is_sink(port)) - tcpm_set_state(port, SNK_ATTACHED, 0); - break; - case SNK_TRYWAIT_DEBOUNCE: - /* Do nothing, waiting for Rp */ - break; - case SRC_TRY_WAIT: - case SRC_TRY_DEBOUNCE: - /* Do nothing, waiting for sink detection */ - break; - default: - break; - } -} - -static void _tcpm_pd_vbus_off(struct tcpm_port *port) -{ - tcpm_log_force(port, "VBUS off"); - port->vbus_present = false; - port->vbus_never_low = false; - switch (port->state) { - case SNK_HARD_RESET_SINK_OFF: - tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0); - break; - case SRC_HARD_RESET_VBUS_OFF: - tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, 0); - break; - case HARD_RESET_SEND: - break; - - case SNK_TRY: - /* Do nothing, waiting for timeout */ - break; - case SRC_TRYWAIT: - /* Hand over to state machine if needed */ - if (tcpm_port_is_source(port)) - tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); - break; - case SNK_TRY_WAIT_DEBOUNCE: - /* Do nothing, waiting for PD_DEBOUNCE to do be done */ - break; - case SNK_TRYWAIT: - case SNK_TRYWAIT_VBUS: - case SNK_TRYWAIT_DEBOUNCE: - break; - case SNK_ATTACH_WAIT: - tcpm_set_state(port, SNK_UNATTACHED, 0); - break; - - case SNK_NEGOTIATE_CAPABILITIES: - break; - - case PR_SWAP_SRC_SNK_TRANSITION_OFF: - tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, 0); - break; - - case PR_SWAP_SNK_SRC_SINK_OFF: - /* Do nothing, expected */ - break; - - case PORT_RESET_WAIT_OFF: - tcpm_set_state(port, tcpm_default_state(port), 0); - break; - case SRC_TRY_WAIT: - case SRC_TRY_DEBOUNCE: - /* Do nothing, waiting for sink detection */ - break; - default: - if (port->pwr_role == TYPEC_SINK && - port->attached) - tcpm_set_state(port, SNK_UNATTACHED, 0); - break; - } -} - -static void _tcpm_pd_hard_reset(struct tcpm_port *port) -{ - tcpm_log_force(port, "Received hard reset"); - /* - * If we keep receiving hard reset requests, executing the hard reset - * must have failed. Revert to error recovery if that happens. - */ - tcpm_set_state(port, - port->hard_reset_count < PD_N_HARD_RESET_COUNT ? - HARD_RESET_START : ERROR_RECOVERY, - 0); -} - -static void tcpm_pd_event_handler(struct work_struct *work) -{ - struct tcpm_port *port = container_of(work, struct tcpm_port, - event_work); - u32 events; - - mutex_lock(&port->lock); - - spin_lock(&port->pd_event_lock); - while (port->pd_events) { - events = port->pd_events; - port->pd_events = 0; - spin_unlock(&port->pd_event_lock); - if (events & TCPM_RESET_EVENT) - _tcpm_pd_hard_reset(port); - if (events & TCPM_VBUS_EVENT) { - bool vbus; - - vbus = port->tcpc->get_vbus(port->tcpc); - if (vbus) - _tcpm_pd_vbus_on(port); - else - _tcpm_pd_vbus_off(port); - } - if (events & TCPM_CC_EVENT) { - enum typec_cc_status cc1, cc2; - - if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) - _tcpm_cc_change(port, cc1, cc2); - } - spin_lock(&port->pd_event_lock); - } - spin_unlock(&port->pd_event_lock); - mutex_unlock(&port->lock); -} - -void tcpm_cc_change(struct tcpm_port *port) -{ - spin_lock(&port->pd_event_lock); - port->pd_events |= TCPM_CC_EVENT; - spin_unlock(&port->pd_event_lock); - queue_work(port->wq, &port->event_work); -} -EXPORT_SYMBOL_GPL(tcpm_cc_change); - -void tcpm_vbus_change(struct tcpm_port *port) -{ - spin_lock(&port->pd_event_lock); - port->pd_events |= TCPM_VBUS_EVENT; - spin_unlock(&port->pd_event_lock); - queue_work(port->wq, &port->event_work); -} -EXPORT_SYMBOL_GPL(tcpm_vbus_change); - -void tcpm_pd_hard_reset(struct tcpm_port *port) -{ - spin_lock(&port->pd_event_lock); - port->pd_events = TCPM_RESET_EVENT; - spin_unlock(&port->pd_event_lock); - queue_work(port->wq, &port->event_work); -} -EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset); - -static int tcpm_dr_set(const struct typec_capability *cap, - enum typec_data_role data) -{ - struct tcpm_port *port = typec_cap_to_tcpm(cap); - int ret; - - mutex_lock(&port->swap_lock); - mutex_lock(&port->lock); - - if (port->port_type != TYPEC_PORT_DRP) { - ret = -EINVAL; - goto port_unlock; - } - if (port->state != SRC_READY && port->state != SNK_READY) { - ret = -EAGAIN; - goto port_unlock; - } - - if (port->data_role == data) { - ret = 0; - goto port_unlock; - } - - /* - * XXX - * 6.3.9: If an alternate mode is active, a request to swap - * alternate modes shall trigger a port reset. - * Reject data role swap request in this case. - */ - - if (!port->pd_capable) { - /* - * If the partner is not PD capable, reset the port to - * trigger a role change. This can only work if a preferred - * role is configured, and if it matches the requested role. - */ - if (port->try_role == TYPEC_NO_PREFERRED_ROLE || - port->try_role == port->pwr_role) { - ret = -EINVAL; - goto port_unlock; - } - port->non_pd_role_swap = true; - tcpm_set_state(port, PORT_RESET, 0); - } else { - tcpm_set_state(port, DR_SWAP_SEND, 0); - } - - port->swap_status = 0; - port->swap_pending = true; - reinit_completion(&port->swap_complete); - mutex_unlock(&port->lock); - - if (!wait_for_completion_timeout(&port->swap_complete, - msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) - ret = -ETIMEDOUT; - else - ret = port->swap_status; - - port->non_pd_role_swap = false; - goto swap_unlock; - -port_unlock: - mutex_unlock(&port->lock); -swap_unlock: - mutex_unlock(&port->swap_lock); - return ret; -} - -static int tcpm_pr_set(const struct typec_capability *cap, - enum typec_role role) -{ - struct tcpm_port *port = typec_cap_to_tcpm(cap); - int ret; - - mutex_lock(&port->swap_lock); - mutex_lock(&port->lock); - - if (port->port_type != TYPEC_PORT_DRP) { - ret = -EINVAL; - goto port_unlock; - } - if (port->state != SRC_READY && port->state != SNK_READY) { - ret = -EAGAIN; - goto port_unlock; - } - - if (role == port->pwr_role) { - ret = 0; - goto port_unlock; - } - - port->swap_status = 0; - port->swap_pending = true; - reinit_completion(&port->swap_complete); - tcpm_set_state(port, PR_SWAP_SEND, 0); - mutex_unlock(&port->lock); - - if (!wait_for_completion_timeout(&port->swap_complete, - msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) - ret = -ETIMEDOUT; - else - ret = port->swap_status; - - goto swap_unlock; - -port_unlock: - mutex_unlock(&port->lock); -swap_unlock: - mutex_unlock(&port->swap_lock); - return ret; -} - -static int tcpm_vconn_set(const struct typec_capability *cap, - enum typec_role role) -{ - struct tcpm_port *port = typec_cap_to_tcpm(cap); - int ret; - - mutex_lock(&port->swap_lock); - mutex_lock(&port->lock); - - if (port->state != SRC_READY && port->state != SNK_READY) { - ret = -EAGAIN; - goto port_unlock; - } - - if (role == port->vconn_role) { - ret = 0; - goto port_unlock; - } - - port->swap_status = 0; - port->swap_pending = true; - reinit_completion(&port->swap_complete); - tcpm_set_state(port, VCONN_SWAP_SEND, 0); - mutex_unlock(&port->lock); - - if (!wait_for_completion_timeout(&port->swap_complete, - msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) - ret = -ETIMEDOUT; - else - ret = port->swap_status; - - goto swap_unlock; - -port_unlock: - mutex_unlock(&port->lock); -swap_unlock: - mutex_unlock(&port->swap_lock); - return ret; -} - -static int tcpm_try_role(const struct typec_capability *cap, int role) -{ - struct tcpm_port *port = typec_cap_to_tcpm(cap); - struct tcpc_dev *tcpc = port->tcpc; - int ret = 0; - - mutex_lock(&port->lock); - if (tcpc->try_role) - ret = tcpc->try_role(tcpc, role); - if (!ret && !tcpc->config->try_role_hw) - port->try_role = role; - port->try_src_count = 0; - port->try_snk_count = 0; - mutex_unlock(&port->lock); - - return ret; -} - -static int 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 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 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; - - port->tcpc->init(port->tcpc); - - tcpm_reset_port(port); - - /* - * XXX - * Should possibly wait for VBUS to settle if it was enabled locally - * since tcpm_reset_port() will disable VBUS. - */ - port->vbus_present = port->tcpc->get_vbus(port->tcpc); - if (port->vbus_present) - port->vbus_never_low = true; - - tcpm_set_state(port, tcpm_default_state(port), 0); - - if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) - _tcpm_cc_change(port, cc1, cc2); - - /* - * Some adapters need a clean slate at startup, and won't recover - * otherwise. So do not try to be fancy and force a clean disconnect. - */ - tcpm_set_state(port, PORT_RESET, 0); -} - -static int tcpm_port_type_set(const struct typec_capability *cap, - enum typec_port_type type) -{ - struct tcpm_port *port = typec_cap_to_tcpm(cap); - - mutex_lock(&port->lock); - if (type == port->port_type) - goto port_unlock; - - port->port_type = type; - - if (!port->connected) { - tcpm_set_state(port, PORT_RESET, 0); - } else if (type == TYPEC_PORT_SNK) { - if (!(port->pwr_role == TYPEC_SINK && - port->data_role == TYPEC_DEVICE)) - tcpm_set_state(port, PORT_RESET, 0); - } else if (type == TYPEC_PORT_SRC) { - if (!(port->pwr_role == TYPEC_SOURCE && - port->data_role == TYPEC_HOST)) - tcpm_set_state(port, PORT_RESET, 0); - } - -port_unlock: - mutex_unlock(&port->lock); - return 0; -} - -void tcpm_tcpc_reset(struct tcpm_port *port) -{ - mutex_lock(&port->lock); - /* XXX: Maintain PD connection if possible? */ - tcpm_init(port); - mutex_unlock(&port->lock); -} -EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); - -static int tcpm_copy_pdos(u32 *dest_pdo, const u32 *src_pdo, - unsigned int nr_pdo) -{ - unsigned int i; - - if (nr_pdo > PDO_MAX_OBJECTS) - nr_pdo = PDO_MAX_OBJECTS; - - for (i = 0; i < nr_pdo; i++) - dest_pdo[i] = src_pdo[i]; - - return nr_pdo; -} - -static int tcpm_copy_vdos(u32 *dest_vdo, const u32 *src_vdo, - unsigned int nr_vdo) -{ - unsigned int i; - - if (nr_vdo > VDO_MAX_OBJECTS) - nr_vdo = VDO_MAX_OBJECTS; - - for (i = 0; i < nr_vdo; i++) - dest_vdo[i] = src_vdo[i]; - - return nr_vdo; -} - -static int tcpm_fw_get_caps(struct tcpm_port *port, - struct fwnode_handle *fwnode) -{ - const char *cap_str; - int ret; - u32 mw; - - if (!fwnode) - return -EINVAL; - - /* USB data support is optional */ - ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); - if (ret == 0) { - port->typec_caps.data = typec_find_port_data_role(cap_str); - if (port->typec_caps.data < 0) - return -EINVAL; - } - - ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); - if (ret < 0) - return ret; - - port->typec_caps.type = typec_find_port_power_role(cap_str); - if (port->typec_caps.type < 0) - return -EINVAL; - port->port_type = port->typec_caps.type; - - if (port->port_type == TYPEC_PORT_SNK) - goto sink; - - /* Get source pdos */ - ret = fwnode_property_read_u32_array(fwnode, "source-pdos", - NULL, 0); - if (ret <= 0) - return -EINVAL; - - port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS); - ret = fwnode_property_read_u32_array(fwnode, "source-pdos", - port->src_pdo, port->nr_src_pdo); - if ((ret < 0) || tcpm_validate_caps(port, port->src_pdo, - port->nr_src_pdo)) - return -EINVAL; - - if (port->port_type == TYPEC_PORT_SRC) - return 0; - - /* Get the preferred power role for DRP */ - ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str); - if (ret < 0) - return ret; - - port->typec_caps.prefer_role = typec_find_power_role(cap_str); - if (port->typec_caps.prefer_role < 0) - return -EINVAL; -sink: - /* Get sink pdos */ - ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", - NULL, 0); - if (ret <= 0) - return -EINVAL; - - port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS); - ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", - port->snk_pdo, port->nr_snk_pdo); - if ((ret < 0) || tcpm_validate_caps(port, port->snk_pdo, - port->nr_snk_pdo)) - return -EINVAL; - - if (fwnode_property_read_u32(fwnode, "op-sink-microwatt", &mw) < 0) - return -EINVAL; - port->operating_snk_mw = mw / 1000; - - return 0; -} - -int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, - unsigned int nr_pdo) -{ - if (tcpm_validate_caps(port, pdo, nr_pdo)) - return -EINVAL; - - mutex_lock(&port->lock); - port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo); - switch (port->state) { - case SRC_UNATTACHED: - case SRC_ATTACH_WAIT: - case SRC_TRYWAIT: - tcpm_set_cc(port, tcpm_rp_cc(port)); - break; - case SRC_SEND_CAPABILITIES: - case SRC_NEGOTIATE_CAPABILITIES: - case SRC_READY: - case SRC_WAIT_NEW_CAPABILITIES: - tcpm_set_cc(port, tcpm_rp_cc(port)); - tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); - break; - default: - break; - } - mutex_unlock(&port->lock); - return 0; -} -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 operating_snk_mw) -{ - if (tcpm_validate_caps(port, pdo, nr_pdo)) - return -EINVAL; - - 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: - 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; - } - mutex_unlock(&port->lock); - return 0; -} -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_cfg.fwnode = dev_fwnode(port->dev); - 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); -} - -static int tcpm_copy_caps(struct tcpm_port *port, - const struct tcpc_config *tcfg) -{ - if (tcpm_validate_caps(port, tcfg->src_pdo, tcfg->nr_src_pdo) || - tcpm_validate_caps(port, tcfg->snk_pdo, tcfg->nr_snk_pdo)) - return -EINVAL; - - port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcfg->src_pdo, - tcfg->nr_src_pdo); - port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcfg->snk_pdo, - tcfg->nr_snk_pdo); - - port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcfg->snk_vdo, - tcfg->nr_snk_vdo); - - port->operating_snk_mw = tcfg->operating_snk_mw; - - port->typec_caps.prefer_role = tcfg->default_role; - port->typec_caps.type = tcfg->type; - port->typec_caps.data = tcfg->data; - - return 0; -} - -struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) -{ - struct tcpm_port *port; - int i, err; - - if (!dev || !tcpc || - !tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc || - !tcpc->set_polarity || !tcpc->set_vconn || !tcpc->set_vbus || - !tcpc->set_pd_rx || !tcpc->set_roles || !tcpc->pd_transmit) - return ERR_PTR(-EINVAL); - - port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); - if (!port) - return ERR_PTR(-ENOMEM); - - port->dev = dev; - port->tcpc = tcpc; - - mutex_init(&port->lock); - mutex_init(&port->swap_lock); - - port->wq = create_singlethread_workqueue(dev_name(dev)); - if (!port->wq) - return ERR_PTR(-ENOMEM); - INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work); - INIT_DELAYED_WORK(&port->vdm_state_machine, vdm_state_machine_work); - INIT_WORK(&port->event_work, tcpm_pd_event_handler); - - spin_lock_init(&port->pd_event_lock); - - init_completion(&port->tx_complete); - init_completion(&port->swap_complete); - init_completion(&port->pps_complete); - tcpm_debugfs_init(port); - - err = tcpm_fw_get_caps(port, tcpc->fwnode); - if ((err < 0) && tcpc->config) - err = tcpm_copy_caps(port, tcpc->config); - if (err < 0) - goto out_destroy_wq; - - if (!tcpc->config || !tcpc->config->try_role_hw) - port->try_role = port->typec_caps.prefer_role; - else - port->try_role = TYPEC_NO_PREFERRED_ROLE; - - port->typec_caps.fwnode = tcpc->fwnode; - port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ - port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ - port->typec_caps.dr_set = tcpm_dr_set; - port->typec_caps.pr_set = tcpm_pr_set; - port->typec_caps.vconn_set = tcpm_vconn_set; - port->typec_caps.try_role = tcpm_try_role; - port->typec_caps.port_type_set = tcpm_port_type_set; - - port->partner_desc.identity = &port->partner_ident; - port->port_type = port->typec_caps.type; - - port->role_sw = usb_role_switch_get(port->dev); - if (IS_ERR(port->role_sw)) { - err = PTR_ERR(port->role_sw); - 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); - goto out_destroy_wq; - } - - if (tcpc->config && tcpc->config->alt_modes) { - const struct typec_altmode_desc *paltmode = tcpc->config->alt_modes; - - i = 0; - while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) { - struct typec_altmode *alt; - - alt = typec_port_register_altmode(port->typec_port, - paltmode); - if (IS_ERR(alt)) { - tcpm_log(port, - "%s: failed to register port alternate mode 0x%x", - dev_name(dev), paltmode->svid); - break; - } - typec_altmode_set_drvdata(alt, port); - alt->ops = &tcpm_altmode_ops; - port->port_altmode[i] = alt; - i++; - paltmode++; - } - } - - mutex_lock(&port->lock); - tcpm_init(port); - mutex_unlock(&port->lock); - - tcpm_log(port, "%s: registered", dev_name(dev)); - return port; - -out_destroy_wq: - usb_role_switch_put(port->role_sw); - destroy_workqueue(port->wq); - return ERR_PTR(err); -} -EXPORT_SYMBOL_GPL(tcpm_register_port); - -void tcpm_unregister_port(struct tcpm_port *port) -{ - int i; - - tcpm_reset_port(port); - for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++) - typec_unregister_altmode(port->port_altmode[i]); - typec_unregister_port(port->typec_port); - usb_role_switch_put(port->role_sw); - tcpm_debugfs_exit(port); - destroy_workqueue(port->wq); -} -EXPORT_SYMBOL_GPL(tcpm_unregister_port); - -MODULE_AUTHOR("Guenter Roeck <groeck@chromium.org>"); -MODULE_DESCRIPTION("USB Type-C Port Manager"); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig new file mode 100644 index 000000000000..f03ea8a61768 --- /dev/null +++ b/drivers/usb/typec/tcpm/Kconfig @@ -0,0 +1,52 @@ +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. + +if TYPEC_TCPM + +config TYPEC_TCPCI + tristate "Type-C Port Controller Interface driver" + depends on I2C + select REGMAP_I2C + help + Type-C Port Controller driver for TCPCI-compliant controller. + +if TYPEC_TCPCI + +config TYPEC_RT1711H + tristate "Richtek RT1711H Type-C chip driver" + 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 # TYPEC_TCPCI + +config TYPEC_FUSB302 + tristate "Fairchild FUSB302 Type-C chip driver" + 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 + Type-C functionalities. + +config TYPEC_WCOVE + tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver" + depends on ACPI + depends on INTEL_SOC_PMIC + depends on INTEL_PMC_IPC + depends on BXT_WC_PMIC_OPREGION + help + This driver adds support for USB Type-C on Intel Broxton platforms + that have Intel Whiskey Cove PMIC. The driver works with USB Type-C + Port Controller Manager to provide USB PD and Type-C functionalities. + + To compile this driver as module, choose M here: the module will be + called typec_wcove.ko + +endif # TYPEC_TCPM diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile new file mode 100644 index 000000000000..a5ff6c8eb892 --- /dev/null +++ b/drivers/usb/typec/tcpm/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_TYPEC_TCPM) += tcpm.o +obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o +obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o +typec_wcove-y := wcove.o +obj-$(CONFIG_TYPEC_TCPCI) += tcpci.o +obj-$(CONFIG_TYPEC_RT1711H) += tcpci_rt1711h.o diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c new file mode 100644 index 000000000000..6e9370a813f7 --- /dev/null +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -0,0 +1,1861 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2016-2017 Google, Inc + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/extcon.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pinctrl/consumer.h> +#include <linux/proc_fs.h> +#include <linux/regulator/consumer.h> +#include <linux/sched/clock.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/usb/typec.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/pd.h> +#include <linux/workqueue.h> + +#include "fusb302_reg.h" + +/* + * When the device is SNK, BC_LVL interrupt is used to monitor cc pins + * for the current capability offered by the SRC. As FUSB302 chip fires + * the BC_LVL interrupt on PD signalings, cc lvl should be handled after + * a delay to avoid measuring on PD activities. The delay is slightly + * longer than PD_T_PD_DEBPUNCE (10-20ms). + */ +#define T_BC_LVL_DEBOUNCE_DELAY_MS 30 + +enum toggling_mode { + TOGGLINE_MODE_OFF, + TOGGLING_MODE_DRP, + TOGGLING_MODE_SNK, + TOGGLING_MODE_SRC, +}; + +enum src_current_status { + SRC_CURRENT_DEFAULT, + SRC_CURRENT_MEDIUM, + SRC_CURRENT_HIGH, +}; + +static const u8 ra_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 4, /* 210mV */ + [SRC_CURRENT_MEDIUM] = 9, /* 420mV */ + [SRC_CURRENT_HIGH] = 18, /* 798mV */ +}; + +static const u8 rd_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 38, /* 1638mV */ + [SRC_CURRENT_MEDIUM] = 38, /* 1638mV */ + [SRC_CURRENT_HIGH] = 61, /* 2604mV */ +}; + +#define LOG_BUFFER_ENTRIES 1024 +#define LOG_BUFFER_ENTRY_SIZE 128 + +struct fusb302_chip { + struct device *dev; + struct i2c_client *i2c_client; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc_dev; + struct tcpc_config tcpc_config; + + struct regulator *vbus; + + int gpio_int_n; + int gpio_int_n_irq; + struct extcon_dev *extcon; + + struct workqueue_struct *wq; + struct delayed_work bc_lvl_handler; + + atomic_t pm_suspend; + atomic_t i2c_busy; + + /* lock for sharing chip states */ + struct mutex lock; + + /* chip status */ + enum toggling_mode toggling_mode; + enum src_current_status src_current_status; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + + /* port status */ + bool pull_up; + bool vconn_on; + bool vbus_on; + bool charge_on; + bool vbus_present; + 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; + /* lock for log buffer access */ + struct mutex logbuffer_lock; + int logbuffer_head; + int logbuffer_tail; + u8 *logbuffer[LOG_BUFFER_ENTRIES]; +#endif +}; + +/* + * Logging + */ + +#ifdef CONFIG_DEBUG_FS + +static bool fusb302_log_full(struct fusb302_chip *chip) +{ + return chip->logbuffer_tail == + (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; +} + +static void _fusb302_log(struct fusb302_chip *chip, const char *fmt, + va_list args) +{ + char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; + u64 ts_nsec = local_clock(); + unsigned long rem_nsec; + + if (!chip->logbuffer[chip->logbuffer_head]) { + chip->logbuffer[chip->logbuffer_head] = + kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); + if (!chip->logbuffer[chip->logbuffer_head]) + return; + } + + vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + + mutex_lock(&chip->logbuffer_lock); + + if (fusb302_log_full(chip)) { + chip->logbuffer_head = max(chip->logbuffer_head - 1, 0); + strlcpy(tmpbuffer, "overflow", sizeof(tmpbuffer)); + } + + if (chip->logbuffer_head < 0 || + chip->logbuffer_head >= LOG_BUFFER_ENTRIES) { + dev_warn(chip->dev, + "Bad log buffer index %d\n", chip->logbuffer_head); + goto abort; + } + + if (!chip->logbuffer[chip->logbuffer_head]) { + dev_warn(chip->dev, + "Log buffer index %d is NULL\n", chip->logbuffer_head); + goto abort; + } + + rem_nsec = do_div(ts_nsec, 1000000000); + scnprintf(chip->logbuffer[chip->logbuffer_head], + LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", + (unsigned long)ts_nsec, rem_nsec / 1000, + tmpbuffer); + chip->logbuffer_head = (chip->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; + +abort: + mutex_unlock(&chip->logbuffer_lock); +} + +static void fusb302_log(struct fusb302_chip *chip, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + _fusb302_log(chip, fmt, args); + va_end(args); +} + +static int fusb302_debug_show(struct seq_file *s, void *v) +{ + struct fusb302_chip *chip = (struct fusb302_chip *)s->private; + int tail; + + mutex_lock(&chip->logbuffer_lock); + tail = chip->logbuffer_tail; + while (tail != chip->logbuffer_head) { + seq_printf(s, "%s\n", chip->logbuffer[tail]); + tail = (tail + 1) % LOG_BUFFER_ENTRIES; + } + if (!seq_has_overflowed(s)) + chip->logbuffer_tail = tail; + mutex_unlock(&chip->logbuffer_lock); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(fusb302_debug); + +static struct dentry *rootdir; + +static void fusb302_debugfs_init(struct fusb302_chip *chip) +{ + mutex_init(&chip->logbuffer_lock); + if (!rootdir) + rootdir = debugfs_create_dir("fusb302", NULL); + + chip->dentry = debugfs_create_file(dev_name(chip->dev), + S_IFREG | 0444, rootdir, + chip, &fusb302_debug_fops); +} + +static void fusb302_debugfs_exit(struct fusb302_chip *chip) +{ + debugfs_remove(chip->dentry); + debugfs_remove(rootdir); +} + +#else + +static void fusb302_log(const struct fusb302_chip *chip, + const char *fmt, ...) { } +static void fusb302_debugfs_init(const struct fusb302_chip *chip) { } +static void fusb302_debugfs_exit(const struct fusb302_chip *chip) { } + +#endif + +#define FUSB302_RESUME_RETRY 10 +#define FUSB302_RESUME_RETRY_SLEEP 50 + +static bool fusb302_is_suspended(struct fusb302_chip *chip) +{ + int retry_cnt; + + for (retry_cnt = 0; retry_cnt < FUSB302_RESUME_RETRY; retry_cnt++) { + if (atomic_read(&chip->pm_suspend)) { + dev_err(chip->dev, "i2c: pm suspend, retry %d/%d\n", + retry_cnt + 1, FUSB302_RESUME_RETRY); + msleep(FUSB302_RESUME_RETRY_SLEEP); + } else { + return false; + } + } + + return true; +} + +static int fusb302_i2c_write(struct fusb302_chip *chip, + u8 address, u8 data) +{ + int ret = 0; + + atomic_set(&chip->i2c_busy, 1); + + if (fusb302_is_suspended(chip)) { + atomic_set(&chip->i2c_busy, 0); + return -ETIMEDOUT; + } + + ret = i2c_smbus_write_byte_data(chip->i2c_client, address, data); + if (ret < 0) + fusb302_log(chip, "cannot write 0x%02x to 0x%02x, ret=%d", + data, address, ret); + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_block_write(struct fusb302_chip *chip, u8 address, + u8 length, const u8 *data) +{ + int ret = 0; + + if (length <= 0) + return ret; + atomic_set(&chip->i2c_busy, 1); + + if (fusb302_is_suspended(chip)) { + atomic_set(&chip->i2c_busy, 0); + return -ETIMEDOUT; + } + + ret = i2c_smbus_write_i2c_block_data(chip->i2c_client, address, + length, data); + if (ret < 0) + fusb302_log(chip, "cannot block write 0x%02x, len=%d, ret=%d", + address, length, ret); + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_read(struct fusb302_chip *chip, + u8 address, u8 *data) +{ + int ret = 0; + + atomic_set(&chip->i2c_busy, 1); + + if (fusb302_is_suspended(chip)) { + atomic_set(&chip->i2c_busy, 0); + return -ETIMEDOUT; + } + + ret = i2c_smbus_read_byte_data(chip->i2c_client, address); + *data = (u8)ret; + if (ret < 0) + fusb302_log(chip, "cannot read %02x, ret=%d", address, ret); + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_block_read(struct fusb302_chip *chip, u8 address, + u8 length, u8 *data) +{ + int ret = 0; + + if (length <= 0) + return ret; + atomic_set(&chip->i2c_busy, 1); + + if (fusb302_is_suspended(chip)) { + atomic_set(&chip->i2c_busy, 0); + return -ETIMEDOUT; + } + + ret = i2c_smbus_read_i2c_block_data(chip->i2c_client, address, + length, data); + if (ret < 0) { + fusb302_log(chip, "cannot block read 0x%02x, len=%d, ret=%d", + address, length, ret); + goto done; + } + if (ret != length) { + fusb302_log(chip, "only read %d/%d bytes from 0x%02x", + ret, length, address); + ret = -EIO; + } + +done: + atomic_set(&chip->i2c_busy, 0); + + return ret; +} + +static int fusb302_i2c_mask_write(struct fusb302_chip *chip, u8 address, + u8 mask, u8 value) +{ + int ret = 0; + u8 data; + + ret = fusb302_i2c_read(chip, address, &data); + if (ret < 0) + return ret; + data &= ~mask; + data |= value; + ret = fusb302_i2c_write(chip, address, data); + if (ret < 0) + return ret; + + return ret; +} + +static int fusb302_i2c_set_bits(struct fusb302_chip *chip, u8 address, + u8 set_bits) +{ + return fusb302_i2c_mask_write(chip, address, 0x00, set_bits); +} + +static int fusb302_i2c_clear_bits(struct fusb302_chip *chip, u8 address, + u8 clear_bits) +{ + return fusb302_i2c_mask_write(chip, address, clear_bits, 0x00); +} + +static int fusb302_sw_reset(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_RESET, + FUSB_REG_RESET_SW_RESET); + if (ret < 0) + fusb302_log(chip, "cannot sw reset the chip, ret=%d", ret); + else + fusb302_log(chip, "sw reset"); + + return ret; +} + +static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, + FUSB_REG_CONTROL3_N_RETRIES_3 | + FUSB_REG_CONTROL3_AUTO_RETRY); + + return ret; +} + +/* + * initialize interrupt on the chip + * - unmasked interrupt: VBUS_OK + */ +static int fusb302_init_interrupt(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_MASK, + 0xFF & ~FUSB_REG_MASK_VBUSOK); + if (ret < 0) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKA, 0xFF); + if (ret < 0) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKB, 0xFF); + if (ret < 0) + return ret; + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_INT_MASK); + if (ret < 0) + return ret; + + return ret; +} + +static int fusb302_set_power_mode(struct fusb302_chip *chip, u8 power_mode) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_POWER, power_mode); + + return ret; +} + +static int tcpm_init(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 data; + + ret = fusb302_sw_reset(chip); + if (ret < 0) + return ret; + ret = fusb302_enable_tx_auto_retries(chip); + if (ret < 0) + return ret; + ret = fusb302_init_interrupt(chip); + if (ret < 0) + return ret; + ret = fusb302_set_power_mode(chip, FUSB_REG_POWER_PWR_ALL); + if (ret < 0) + return ret; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &data); + if (ret < 0) + return ret; + chip->vbus_present = !!(data & FUSB_REG_STATUS0_VBUSOK); + ret = fusb302_i2c_read(chip, FUSB_REG_DEVICE_ID, &data); + if (ret < 0) + return ret; + fusb302_log(chip, "fusb302 device ID: 0x%02x", data); + + return ret; +} + +static int tcpm_get_vbus(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + ret = chip->vbus_present ? 1 : 0; + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_get_current_limit(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int current_limit = 0; + unsigned long timeout; + + if (!chip->extcon) + return 0; + + /* + * USB2 Charger detection may still be in progress when we get here, + * this can take upto 600ms, wait 800ms max. + */ + timeout = jiffies + msecs_to_jiffies(800); + do { + if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_SDP) == 1) + current_limit = 500; + + if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_CDP) == 1 || + extcon_get_state(chip->extcon, EXTCON_CHG_USB_ACA) == 1) + current_limit = 1500; + + if (extcon_get_state(chip->extcon, EXTCON_CHG_USB_DCP) == 1) + current_limit = 2000; + + msleep(50); + } while (current_limit == 0 && time_before(jiffies, timeout)); + + return current_limit; +} + +static int fusb302_set_cc_pull(struct fusb302_chip *chip, + bool pull_up, bool pull_down) +{ + int ret = 0; + u8 data = 0x00; + u8 mask = FUSB_REG_SWITCHES0_CC1_PU_EN | + FUSB_REG_SWITCHES0_CC2_PU_EN | + FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + + if (pull_up) + data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_CC1_PU_EN : + FUSB_REG_SWITCHES0_CC2_PU_EN; + if (pull_down) + data |= FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + mask, data); + if (ret < 0) + return ret; + chip->pull_up = pull_up; + + return ret; +} + +static int fusb302_set_src_current(struct fusb302_chip *chip, + enum src_current_status status) +{ + int ret = 0; + + chip->src_current_status = status; + switch (status) { + case SRC_CURRENT_DEFAULT: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_DEF); + break; + case SRC_CURRENT_MEDIUM: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_MED); + break; + case SRC_CURRENT_HIGH: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_HIGH); + break; + default: + break; + } + + return ret; +} + +static int fusb302_set_toggling(struct fusb302_chip *chip, + enum toggling_mode mode) +{ + int ret = 0; + + /* first disable toggling */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret < 0) + return ret; + /* mask interrupts for SRC or SNK */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) + return ret; + chip->intr_bc_lvl = false; + chip->intr_comp_chng = false; + /* configure toggling mode: none/snk/src/drp */ + switch (mode) { + case TOGGLINE_MODE_OFF: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_NONE); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_SNK: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_UFP); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_SRC: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DFP); + if (ret < 0) + return ret; + break; + case TOGGLING_MODE_DRP: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DRP); + if (ret < 0) + return ret; + break; + default: + break; + } + + if (mode == TOGGLINE_MODE_OFF) { + /* mask TOGDONE interrupt */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret < 0) + return ret; + chip->intr_togdone = false; + } else { + /* unmask TOGDONE interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret < 0) + return ret; + chip->intr_togdone = true; + /* start toggling */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret < 0) + return ret; + /* during toggling, consider cc as Open */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + } + chip->toggling_mode = mode; + + return ret; +} + +static const char * const typec_cc_status_name[] = { + [TYPEC_CC_OPEN] = "Open", + [TYPEC_CC_RA] = "Ra", + [TYPEC_CC_RD] = "Rd", + [TYPEC_CC_RP_DEF] = "Rp-def", + [TYPEC_CC_RP_1_5] = "Rp-1.5", + [TYPEC_CC_RP_3_0] = "Rp-3.0", +}; + +static const enum src_current_status cc_src_current[] = { + [TYPEC_CC_OPEN] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RA] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RD] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_DEF] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_1_5] = SRC_CURRENT_MEDIUM, + [TYPEC_CC_RP_3_0] = SRC_CURRENT_HIGH, +}; + +static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + bool pull_up, pull_down; + u8 rd_mda; + + mutex_lock(&chip->lock); + switch (cc) { + case TYPEC_CC_OPEN: + pull_up = false; + pull_down = false; + break; + case TYPEC_CC_RD: + pull_up = false; + pull_down = true; + break; + case TYPEC_CC_RP_DEF: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + pull_up = true; + pull_down = false; + break; + default: + fusb302_log(chip, "unsupported cc value %s", + typec_cc_status_name[cc]); + ret = -EINVAL; + goto done; + } + ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, "cannot stop toggling, ret=%d", ret); + goto done; + } + ret = fusb302_set_cc_pull(chip, pull_up, pull_down); + if (ret < 0) { + fusb302_log(chip, + "cannot set cc pulling up %s, down %s, ret = %d", + pull_up ? "True" : "False", + pull_down ? "True" : "False", + ret); + goto done; + } + /* reset the cc status */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + /* adjust current for SRC */ + if (pull_up) { + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret < 0) { + fusb302_log(chip, "cannot set src current %s, ret=%d", + typec_cc_status_name[cc], ret); + goto done; + } + } + /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */ + if (pull_up) { + rd_mda = rd_mda_value[cc_src_current[cc]]; + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) { + fusb302_log(chip, + "cannot set SRC measure value, ret=%d", + ret); + goto done; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) { + fusb302_log(chip, "cannot set SRC interrupt, ret=%d", + ret); + goto done; + } + chip->intr_bc_lvl = false; + chip->intr_comp_chng = true; + } + if (pull_down) { + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_BC_LVL); + if (ret < 0) { + fusb302_log(chip, "cannot set SRC interrupt, ret=%d", + ret); + goto done; + } + chip->intr_bc_lvl = true; + chip->intr_comp_chng = false; + } + fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_get_cc(struct tcpc_dev *dev, enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + + mutex_lock(&chip->lock); + *cc1 = chip->cc1; + *cc2 = chip->cc2; + fusb302_log(chip, "cc1=%s, cc2=%s", typec_cc_status_name[*cc1], + typec_cc_status_name[*cc2]); + mutex_unlock(&chip->lock); + + return 0; +} + +static int tcpm_set_polarity(struct tcpc_dev *dev, + enum typec_cc_polarity polarity) +{ + return 0; +} + +static int tcpm_set_vconn(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches0_data = 0x00; + u8 switches0_mask = FUSB_REG_SWITCHES0_VCONN_CC1 | + FUSB_REG_SWITCHES0_VCONN_CC2; + + mutex_lock(&chip->lock); + if (chip->vconn_on == on) { + fusb302_log(chip, "vconn is already %s", on ? "On" : "Off"); + goto done; + } + if (on) { + switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_VCONN_CC2 : + FUSB_REG_SWITCHES0_VCONN_CC1; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret < 0) + goto done; + chip->vconn_on = on; + fusb302_log(chip, "vconn := %s", on ? "On" : "Off"); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + if (chip->vbus_on == on) { + fusb302_log(chip, "vbus is already %s", on ? "On" : "Off"); + } else { + if (on) + ret = regulator_enable(chip->vbus); + else + ret = regulator_disable(chip->vbus); + if (ret < 0) { + fusb302_log(chip, "cannot %s vbus regulator, ret=%d", + on ? "enable" : "disable", ret); + goto done; + } + chip->vbus_on = on; + fusb302_log(chip, "vbus := %s", on ? "On" : "Off"); + } + if (chip->charge_on == charge) + fusb302_log(chip, "charge is already %s", + charge ? "On" : "Off"); + else + chip->charge_on = charge; + +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int fusb302_pd_tx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_TX_FLUSH); +} + +static int fusb302_pd_rx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL1, + FUSB_REG_CONTROL1_RX_FLUSH); +} + +static int fusb302_pd_set_auto_goodcrc(struct fusb302_chip *chip, bool on) +{ + if (on) + return fusb302_i2c_set_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); + return fusb302_i2c_clear_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); +} + +static int fusb302_pd_set_interrupts(struct fusb302_chip *chip, bool on) +{ + int ret = 0; + u8 mask_interrupts = FUSB_REG_MASK_COLLISION; + u8 maska_interrupts = FUSB_REG_MASKA_RETRYFAIL | + FUSB_REG_MASKA_HARDSENT | + FUSB_REG_MASKA_TX_SUCCESS | + FUSB_REG_MASKA_HARDRESET; + u8 maskb_interrupts = FUSB_REG_MASKB_GCRCSENT; + + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, mask_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASK, mask_interrupts); + if (ret < 0) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, maska_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, maska_interrupts); + if (ret < 0) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKB, maskb_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKB, maskb_interrupts); + return ret; +} + +static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + ret = fusb302_pd_rx_flush(chip); + if (ret < 0) { + fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret); + goto done; + } + ret = fusb302_pd_tx_flush(chip); + if (ret < 0) { + fusb302_log(chip, "cannot flush pd tx buffer, ret=%d", ret); + goto done; + } + ret = fusb302_pd_set_auto_goodcrc(chip, on); + if (ret < 0) { + fusb302_log(chip, "cannot turn %s auto GCRC, ret=%d", + on ? "on" : "off", ret); + goto done; + } + ret = fusb302_pd_set_interrupts(chip, on); + if (ret < 0) { + fusb302_log(chip, "cannot turn %s pd interrupts, ret=%d", + on ? "on" : "off", ret); + goto done; + } + fusb302_log(chip, "pd := %s", on ? "on" : "off"); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static const char * const typec_role_name[] = { + [TYPEC_SINK] = "Sink", + [TYPEC_SOURCE] = "Source", +}; + +static const char * const typec_data_role_name[] = { + [TYPEC_DEVICE] = "Device", + [TYPEC_HOST] = "Host", +}; + +static int tcpm_set_roles(struct tcpc_dev *dev, bool attached, + enum typec_role pwr, enum typec_data_role data) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches1_mask = FUSB_REG_SWITCHES1_POWERROLE | + FUSB_REG_SWITCHES1_DATAROLE; + u8 switches1_data = 0x00; + + mutex_lock(&chip->lock); + if (pwr == TYPEC_SOURCE) + switches1_data |= FUSB_REG_SWITCHES1_POWERROLE; + if (data == TYPEC_HOST) + switches1_data |= FUSB_REG_SWITCHES1_DATAROLE; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret < 0) { + fusb302_log(chip, "unable to set pd header %s, %s, ret=%d", + typec_role_name[pwr], typec_data_role_name[data], + ret); + goto done; + } + fusb302_log(chip, "pd header := %s, %s", typec_role_name[pwr], + typec_data_role_name[data]); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int tcpm_start_drp_toggling(struct tcpc_dev *dev, + enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret < 0) { + fusb302_log(chip, "unable to set src current %s, ret=%d", + typec_cc_status_name[cc], ret); + goto done; + } + ret = fusb302_set_toggling(chip, TOGGLING_MODE_DRP); + if (ret < 0) { + fusb302_log(chip, + "unable to start drp toggling, ret=%d", ret); + goto done; + } + fusb302_log(chip, "start drp toggling"); +done: + mutex_unlock(&chip->lock); + + return ret; +} + +static int fusb302_pd_send_message(struct fusb302_chip *chip, + const struct pd_message *msg) +{ + int ret = 0; + u8 buf[40]; + u8 pos = 0; + int len; + + /* SOP tokens */ + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC2; + + len = pd_header_cnt_le(msg->header) * 4; + /* plug 2 for header */ + len += 2; + if (len > 0x1F) { + fusb302_log(chip, + "PD message too long %d (incl. header)", len); + return -EINVAL; + } + /* packsym tells the FUSB302 chip that the next X bytes are payload */ + buf[pos++] = FUSB302_TKN_PACKSYM | (len & 0x1F); + memcpy(&buf[pos], &msg->header, sizeof(msg->header)); + pos += sizeof(msg->header); + + len -= 2; + memcpy(&buf[pos], msg->payload, len); + pos += len; + + /* CRC */ + buf[pos++] = FUSB302_TKN_JAMCRC; + /* EOP */ + buf[pos++] = FUSB302_TKN_EOP; + /* turn tx off after sending message */ + buf[pos++] = FUSB302_TKN_TXOFF; + /* start transmission */ + buf[pos++] = FUSB302_TKN_TXON; + + ret = fusb302_i2c_block_write(chip, FUSB_REG_FIFOS, pos, buf); + if (ret < 0) + return ret; + fusb302_log(chip, "sending PD message header: %x", msg->header); + fusb302_log(chip, "sending PD message len: %d", len); + + return ret; +} + +static int fusb302_pd_send_hardreset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, + FUSB_REG_CONTROL3_SEND_HARDRESET); +} + +static const char * const transmit_type_name[] = { + [TCPC_TX_SOP] = "SOP", + [TCPC_TX_SOP_PRIME] = "SOP'", + [TCPC_TX_SOP_PRIME_PRIME] = "SOP''", + [TCPC_TX_SOP_DEBUG_PRIME] = "DEBUG'", + [TCPC_TX_SOP_DEBUG_PRIME_PRIME] = "DEBUG''", + [TCPC_TX_HARD_RESET] = "HARD_RESET", + [TCPC_TX_CABLE_RESET] = "CABLE_RESET", + [TCPC_TX_BIST_MODE_2] = "BIST_MODE_2", +}; + +static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + mutex_lock(&chip->lock); + switch (type) { + case TCPC_TX_SOP: + ret = fusb302_pd_send_message(chip, msg); + if (ret < 0) + fusb302_log(chip, + "cannot send PD message, ret=%d", ret); + break; + case TCPC_TX_HARD_RESET: + ret = fusb302_pd_send_hardreset(chip); + if (ret < 0) + fusb302_log(chip, + "cannot send hardreset, ret=%d", ret); + break; + default: + fusb302_log(chip, "type %s not supported", + transmit_type_name[type]); + ret = -EINVAL; + } + mutex_unlock(&chip->lock); + + return ret; +} + +static enum typec_cc_status fusb302_bc_lvl_to_cc(u8 bc_lvl) +{ + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_1230_MAX) + return TYPEC_CC_RP_3_0; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_600_1230) + return TYPEC_CC_RP_1_5; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_200_600) + return TYPEC_CC_RP_DEF; + return TYPEC_CC_OPEN; +} + +static void fusb302_bc_lvl_handler_work(struct work_struct *work) +{ + struct fusb302_chip *chip = container_of(work, struct fusb302_chip, + bc_lvl_handler.work); + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_status cc_status; + + mutex_lock(&chip->lock); + if (!chip->intr_bc_lvl) { + fusb302_log(chip, "BC_LVL interrupt is turned off, abort"); + goto done; + } + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + goto done; + fusb302_log(chip, "BC_LVL handler, status0=0x%02x", status0); + if (status0 & FUSB_REG_STATUS0_ACTIVITY) { + fusb302_log(chip, "CC activities detected, delay handling"); + mod_delayed_work(chip->wq, &chip->bc_lvl_handler, + msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); + goto done; + } + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status = fusb302_bc_lvl_to_cc(bc_lvl); + if (chip->cc_polarity == TYPEC_POLARITY_CC1) { + if (chip->cc1 != cc_status) { + fusb302_log(chip, "cc1: %s -> %s", + typec_cc_status_name[chip->cc1], + typec_cc_status_name[cc_status]); + chip->cc1 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } else { + if (chip->cc2 != cc_status) { + fusb302_log(chip, "cc2: %s -> %s", + typec_cc_status_name[chip->cc2], + typec_cc_status_name[cc_status]); + chip->cc2 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } + +done: + mutex_unlock(&chip->lock); +} + +#define PDO_FIXED_FLAGS \ + (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) + +static const u32 src_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), +}; + +static const struct tcpc_config fusb302_tcpc_config = { + .src_pdo = src_pdo, + .nr_src_pdo = ARRAY_SIZE(src_pdo), + .operating_snk_mw = 2500, + .type = TYPEC_PORT_DRP, + .data = TYPEC_PORT_DRD, + .default_role = TYPEC_SINK, + .alt_modes = NULL, +}; + +static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) +{ + fusb302_tcpc_dev->init = tcpm_init; + fusb302_tcpc_dev->get_vbus = tcpm_get_vbus; + fusb302_tcpc_dev->get_current_limit = tcpm_get_current_limit; + fusb302_tcpc_dev->set_cc = tcpm_set_cc; + fusb302_tcpc_dev->get_cc = tcpm_get_cc; + fusb302_tcpc_dev->set_polarity = tcpm_set_polarity; + fusb302_tcpc_dev->set_vconn = tcpm_set_vconn; + fusb302_tcpc_dev->set_vbus = tcpm_set_vbus; + fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx; + fusb302_tcpc_dev->set_roles = tcpm_set_roles; + fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling; + fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit; +} + +static const char * const cc_polarity_name[] = { + [TYPEC_POLARITY_CC1] = "Polarity_CC1", + [TYPEC_POLARITY_CC2] = "Polarity_CC2", +}; + +static int fusb302_set_cc_polarity(struct fusb302_chip *chip, + enum typec_cc_polarity cc_polarity) +{ + int ret = 0; + u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN | + FUSB_REG_SWITCHES0_CC2_PU_EN | + FUSB_REG_SWITCHES0_VCONN_CC1 | + FUSB_REG_SWITCHES0_VCONN_CC2 | + FUSB_REG_SWITCHES0_MEAS_CC1 | + FUSB_REG_SWITCHES0_MEAS_CC2; + u8 switches0_data = 0x00; + u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN | + FUSB_REG_SWITCHES1_TXCC2_EN; + u8 switches1_data = 0x00; + + if (cc_polarity == TYPEC_POLARITY_CC1) { + switches0_data = FUSB_REG_SWITCHES0_MEAS_CC1; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2; + if (chip->pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN; + } else { + switches0_data = FUSB_REG_SWITCHES0_MEAS_CC2; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1; + if (chip->pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret < 0) + return ret; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret < 0) + return ret; + chip->cc_polarity = cc_polarity; + + return ret; +} + +static int fusb302_handle_togdone_snk(struct fusb302_chip *chip, + u8 togdone_result) +{ + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc_status_active, cc1, cc2; + + /* set pull_up, pull_down */ + ret = fusb302_set_cc_pull(chip, false, true); + if (ret < 0) { + fusb302_log(chip, "cannot set cc to pull down, ret=%d", ret); + return ret; + } + /* set polarity */ + cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ? + TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; + ret = fusb302_set_cc_polarity(chip, cc_polarity); + if (ret < 0) { + fusb302_log(chip, "cannot set cc polarity %s, ret=%d", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* fusb302_set_cc_polarity() has set the correct measure block */ + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status_active = fusb302_bc_lvl_to_cc(bc_lvl); + /* restart toggling if the cc status on the active line is OPEN */ + if (cc_status_active == TYPEC_CC_OPEN) { + fusb302_log(chip, "restart toggling as CC_OPEN detected"); + ret = fusb302_set_toggling(chip, chip->toggling_mode); + return ret; + } + /* update tcpm with the new cc value */ + cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? + cc_status_active : TYPEC_CC_OPEN; + cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? + cc_status_active : TYPEC_CC_OPEN; + if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* turn off toggling */ + ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, + "cannot set toggling mode off, ret=%d", ret); + return ret; + } + /* unmask bc_lvl interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, FUSB_REG_MASK_BC_LVL); + if (ret < 0) { + fusb302_log(chip, + "cannot unmask bc_lcl interrupt, ret=%d", ret); + return ret; + } + chip->intr_bc_lvl = true; + fusb302_log(chip, "detected cc1=%s, cc2=%s", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +static int fusb302_handle_togdone_src(struct fusb302_chip *chip, + u8 togdone_result) +{ + /* + * - set polarity (measure cc, vconn, tx) + * - set pull_up, pull_down + * - set cc1, cc2, and update to tcpm_port + * - set I_COMP interrupt on + */ + int ret = 0; + u8 status0; + u8 ra_mda = ra_mda_value[chip->src_current_status]; + u8 rd_mda = rd_mda_value[chip->src_current_status]; + bool ra_comp, rd_comp; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc_status_active, cc1, cc2; + + /* set pull_up, pull_down */ + ret = fusb302_set_cc_pull(chip, true, false); + if (ret < 0) { + fusb302_log(chip, "cannot set cc to pull up, ret=%d", ret); + return ret; + } + /* set polarity */ + cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) ? + TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; + ret = fusb302_set_cc_polarity(chip, cc_polarity); + if (ret < 0) { + fusb302_log(chip, "cannot set cc polarity %s, ret=%d", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* fusb302_set_cc_polarity() has set the correct measure block */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) + return ret; + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + rd_comp = !!(status0 & FUSB_REG_STATUS0_COMP); + if (!rd_comp) { + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda); + if (ret < 0) + return ret; + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + ra_comp = !!(status0 & FUSB_REG_STATUS0_COMP); + } + if (rd_comp) + cc_status_active = TYPEC_CC_OPEN; + else if (ra_comp) + cc_status_active = TYPEC_CC_RD; + else + /* Ra is not supported, report as Open */ + cc_status_active = TYPEC_CC_OPEN; + /* restart toggling if the cc status on the active line is OPEN */ + if (cc_status_active == TYPEC_CC_OPEN) { + fusb302_log(chip, "restart toggling as CC_OPEN detected"); + ret = fusb302_set_toggling(chip, chip->toggling_mode); + return ret; + } + /* update tcpm with the new cc value */ + cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? + cc_status_active : TYPEC_CC_OPEN; + cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? + cc_status_active : TYPEC_CC_OPEN; + if ((chip->cc1 != cc1) || (chip->cc2 != cc2)) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* turn off toggling */ + ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + if (ret < 0) { + fusb302_log(chip, + "cannot set toggling mode off, ret=%d", ret); + return ret; + } + /* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret < 0) + return ret; + /* unmask comp_chng interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_COMP_CHNG); + if (ret < 0) { + fusb302_log(chip, + "cannot unmask bc_lcl interrupt, ret=%d", ret); + return ret; + } + chip->intr_comp_chng = true; + fusb302_log(chip, "detected cc1=%s, cc2=%s", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +static int fusb302_handle_togdone(struct fusb302_chip *chip) +{ + int ret = 0; + u8 status1a; + u8 togdone_result; + + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS1A, &status1a); + if (ret < 0) + return ret; + togdone_result = (status1a >> FUSB_REG_STATUS1A_TOGSS_POS) & + FUSB_REG_STATUS1A_TOGSS_MASK; + switch (togdone_result) { + case FUSB_REG_STATUS1A_TOGSS_SNK1: + case FUSB_REG_STATUS1A_TOGSS_SNK2: + return fusb302_handle_togdone_snk(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_SRC1: + case FUSB_REG_STATUS1A_TOGSS_SRC2: + return fusb302_handle_togdone_src(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_AA: + /* doesn't support */ + fusb302_log(chip, "AudioAccessory not supported"); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + default: + fusb302_log(chip, "TOGDONE with an invalid state: %d", + togdone_result); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + } + return ret; +} + +static int fusb302_pd_reset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_RESET, + FUSB_REG_RESET_PD_RESET); +} + +static int fusb302_pd_read_message(struct fusb302_chip *chip, + struct pd_message *msg) +{ + int ret = 0; + u8 token; + u8 crc[4]; + int len; + + /* first SOP token */ + ret = fusb302_i2c_read(chip, FUSB_REG_FIFOS, &token); + if (ret < 0) + return ret; + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 2, + (u8 *)&msg->header); + if (ret < 0) + return ret; + len = pd_header_cnt_le(msg->header) * 4; + /* add 4 to length to include the CRC */ + if (len > PD_MAX_PAYLOAD * 4) { + fusb302_log(chip, "PD message too long %d", len); + return -EINVAL; + } + if (len > 0) { + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, len, + (u8 *)msg->payload); + if (ret < 0) + return ret; + } + /* another 4 bytes to read CRC out */ + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 4, crc); + if (ret < 0) + return ret; + fusb302_log(chip, "PD message header: %x", msg->header); + fusb302_log(chip, "PD message len: %d", len); + + /* + * Check if we've read off a GoodCRC message. If so then indicate to + * TCPM that the previous transmission has completed. Otherwise we pass + * the received message over to TCPM for processing. + * + * We make this check here instead of basing the reporting decision on + * the IRQ event type, as it's possible for the chip to report the + * TX_SUCCESS and GCRCSENT events out of order on occasion, so we need + * to check the message type to ensure correct reporting to TCPM. + */ + if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC)) + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + else + tcpm_pd_receive(chip->tcpm_port, msg); + + return ret; +} + +static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) +{ + struct fusb302_chip *chip = dev_id; + int ret = 0; + u8 interrupt; + u8 interrupta; + u8 interruptb; + u8 status0; + bool vbus_present; + bool comp_result; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + struct pd_message pd_msg; + + mutex_lock(&chip->lock); + /* grab a snapshot of intr flags */ + intr_togdone = chip->intr_togdone; + intr_bc_lvl = chip->intr_bc_lvl; + intr_comp_chng = chip->intr_comp_chng; + + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPT, &interrupt); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTA, &interrupta); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTB, &interruptb); + if (ret < 0) + goto done; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + goto done; + fusb302_log(chip, + "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x", + interrupt, interrupta, interruptb, status0); + + if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { + vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); + fusb302_log(chip, "IRQ: VBUS_OK, vbus=%s", + vbus_present ? "On" : "Off"); + if (vbus_present != chip->vbus_present) { + chip->vbus_present = vbus_present; + tcpm_vbus_change(chip->tcpm_port); + } + } + + if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) { + fusb302_log(chip, "IRQ: TOGDONE"); + ret = fusb302_handle_togdone(chip); + if (ret < 0) { + fusb302_log(chip, + "handle togdone error, ret=%d", ret); + goto done; + } + } + + if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) { + fusb302_log(chip, "IRQ: BC_LVL, handler pending"); + /* + * as BC_LVL interrupt can be affected by PD activity, + * apply delay to for the handler to wait for the PD + * signaling to finish. + */ + mod_delayed_work(chip->wq, &chip->bc_lvl_handler, + msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); + } + + if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) { + comp_result = !!(status0 & FUSB_REG_STATUS0_COMP); + fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s", + comp_result ? "true" : "false"); + if (comp_result) { + /* cc level > Rd_threashold, detach */ + if (chip->cc_polarity == TYPEC_POLARITY_CC1) + chip->cc1 = TYPEC_CC_OPEN; + else + chip->cc2 = TYPEC_CC_OPEN; + tcpm_cc_change(chip->tcpm_port); + } + } + + if (interrupt & FUSB_REG_INTERRUPT_COLLISION) { + fusb302_log(chip, "IRQ: PD collision"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_RETRYFAIL) { + fusb302_log(chip, "IRQ: PD retry failed"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDSENT) { + fusb302_log(chip, "IRQ: PD hardreset sent"); + ret = fusb302_pd_reset(chip); + if (ret < 0) { + fusb302_log(chip, "cannot PD reset, ret=%d", ret); + goto done; + } + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + } + + if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) { + fusb302_log(chip, "IRQ: PD tx success"); + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret < 0) { + fusb302_log(chip, + "cannot read in PD message, ret=%d", ret); + goto done; + } + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) { + fusb302_log(chip, "IRQ: PD received hardreset"); + ret = fusb302_pd_reset(chip); + if (ret < 0) { + fusb302_log(chip, "cannot PD reset, ret=%d", ret); + goto done; + } + tcpm_pd_hard_reset(chip->tcpm_port); + } + + if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) { + fusb302_log(chip, "IRQ: PD sent good CRC"); + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret < 0) { + fusb302_log(chip, + "cannot read in PD message, ret=%d", ret); + goto done; + } + } +done: + mutex_unlock(&chip->lock); + + return IRQ_HANDLED; +} + +static int init_gpio(struct fusb302_chip *chip) +{ + struct device_node *node; + int ret = 0; + + node = chip->dev->of_node; + chip->gpio_int_n = of_get_named_gpio(node, "fcs,int_n", 0); + if (!gpio_is_valid(chip->gpio_int_n)) { + ret = chip->gpio_int_n; + dev_err(chip->dev, "cannot get named GPIO Int_N, ret=%d", ret); + return ret; + } + ret = devm_gpio_request(chip->dev, chip->gpio_int_n, "fcs,int_n"); + if (ret < 0) { + dev_err(chip->dev, "cannot request GPIO Int_N, ret=%d", ret); + return ret; + } + ret = gpio_direction_input(chip->gpio_int_n); + if (ret < 0) { + dev_err(chip->dev, + "cannot set GPIO Int_N to input, ret=%d", ret); + return ret; + } + ret = gpio_to_irq(chip->gpio_int_n); + if (ret < 0) { + dev_err(chip->dev, + "cannot request IRQ for GPIO Int_N, ret=%d", ret); + return ret; + } + chip->gpio_int_n_irq = ret; + 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) +{ + struct fusb302_chip *chip; + struct i2c_adapter *adapter; + struct device *dev = &client->dev; + const char *name; + int ret = 0; + u32 v; + + adapter = to_i2c_adapter(client->dev.parent); + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&client->dev, + "I2C/SMBus block functionality not supported!\n"); + return -ENODEV; + } + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->i2c_client = client; + chip->dev = &client->dev; + chip->tcpc_config = fusb302_tcpc_config; + chip->tcpc_dev.config = &chip->tcpc_config; + mutex_init(&chip->lock); + + chip->tcpc_dev.fwnode = + device_get_named_child_node(dev, "connector"); + + 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. + * This device prop is for kernel internal use only and is expected + * to be set by the platform code which also registers the i2c client + * for the fusb302. + */ + if (device_property_read_string(dev, "fcs,extcon-name", &name) == 0) { + chip->extcon = extcon_get_extcon_dev(name); + if (!chip->extcon) + return -EPROBE_DEFER; + } + + chip->vbus = devm_regulator_get(chip->dev, "vbus"); + if (IS_ERR(chip->vbus)) + return PTR_ERR(chip->vbus); + + chip->wq = create_singlethread_workqueue(dev_name(chip->dev)); + if (!chip->wq) + return -ENOMEM; + + INIT_DELAYED_WORK(&chip->bc_lvl_handler, fusb302_bc_lvl_handler_work); + init_tcpc_dev(&chip->tcpc_dev); + + if (client->irq) { + chip->gpio_int_n_irq = client->irq; + } else { + ret = init_gpio(chip); + if (ret < 0) + goto destroy_workqueue; + } + + chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev); + if (IS_ERR(chip->tcpm_port)) { + ret = PTR_ERR(chip->tcpm_port); + if (ret != -EPROBE_DEFER) + dev_err(dev, "cannot register tcpm port, ret=%d", ret); + goto destroy_workqueue; + } + + ret = devm_request_threaded_irq(chip->dev, chip->gpio_int_n_irq, + NULL, fusb302_irq_intn, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "fsc_interrupt_int_n", chip); + if (ret < 0) { + dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret); + goto tcpm_unregister_port; + } + enable_irq_wake(chip->gpio_int_n_irq); + fusb302_debugfs_init(chip); + i2c_set_clientdata(client, chip); + + return ret; + +tcpm_unregister_port: + tcpm_unregister_port(chip->tcpm_port); +destroy_workqueue: + destroy_workqueue(chip->wq); + + return ret; +} + +static int fusb302_remove(struct i2c_client *client) +{ + struct fusb302_chip *chip = i2c_get_clientdata(client); + + tcpm_unregister_port(chip->tcpm_port); + destroy_workqueue(chip->wq); + fusb302_debugfs_exit(chip); + + return 0; +} + +static int fusb302_pm_suspend(struct device *dev) +{ + struct fusb302_chip *chip = dev->driver_data; + + if (atomic_read(&chip->i2c_busy)) + return -EBUSY; + atomic_set(&chip->pm_suspend, 1); + + return 0; +} + +static int fusb302_pm_resume(struct device *dev) +{ + struct fusb302_chip *chip = dev->driver_data; + + atomic_set(&chip->pm_suspend, 0); + + return 0; +} + +static const struct of_device_id fusb302_dt_match[] = { + {.compatible = "fcs,fusb302"}, + {}, +}; +MODULE_DEVICE_TABLE(of, fusb302_dt_match); + +static const struct i2c_device_id fusb302_i2c_device_id[] = { + {"typec_fusb302", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, fusb302_i2c_device_id); + +static const struct dev_pm_ops fusb302_pm_ops = { + .suspend = fusb302_pm_suspend, + .resume = fusb302_pm_resume, +}; + +static struct i2c_driver fusb302_driver = { + .driver = { + .name = "typec_fusb302", + .pm = &fusb302_pm_ops, + .of_match_table = of_match_ptr(fusb302_dt_match), + }, + .probe = fusb302_probe, + .remove = fusb302_remove, + .id_table = fusb302_i2c_device_id, +}; +module_i2c_driver(fusb302_driver); + +MODULE_AUTHOR("Yueyao Zhu <yueyao.zhu@gmail.com>"); +MODULE_DESCRIPTION("Fairchild FUSB302 Type-C Chip Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/fusb302_reg.h b/drivers/usb/typec/tcpm/fusb302_reg.h new file mode 100644 index 000000000000..00b39d365478 --- /dev/null +++ b/drivers/usb/typec/tcpm/fusb302_reg.h @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2016-2017 Google, Inc + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#ifndef FUSB302_REG_H +#define FUSB302_REG_H + +#define FUSB_REG_DEVICE_ID 0x01 +#define FUSB_REG_SWITCHES0 0x02 +#define FUSB_REG_SWITCHES0_CC2_PU_EN BIT(7) +#define FUSB_REG_SWITCHES0_CC1_PU_EN BIT(6) +#define FUSB_REG_SWITCHES0_VCONN_CC2 BIT(5) +#define FUSB_REG_SWITCHES0_VCONN_CC1 BIT(4) +#define FUSB_REG_SWITCHES0_MEAS_CC2 BIT(3) +#define FUSB_REG_SWITCHES0_MEAS_CC1 BIT(2) +#define FUSB_REG_SWITCHES0_CC2_PD_EN BIT(1) +#define FUSB_REG_SWITCHES0_CC1_PD_EN BIT(0) +#define FUSB_REG_SWITCHES1 0x03 +#define FUSB_REG_SWITCHES1_POWERROLE BIT(7) +#define FUSB_REG_SWITCHES1_SPECREV1 BIT(6) +#define FUSB_REG_SWITCHES1_SPECREV0 BIT(5) +#define FUSB_REG_SWITCHES1_DATAROLE BIT(4) +#define FUSB_REG_SWITCHES1_AUTO_GCRC BIT(2) +#define FUSB_REG_SWITCHES1_TXCC2_EN BIT(1) +#define FUSB_REG_SWITCHES1_TXCC1_EN BIT(0) +#define FUSB_REG_MEASURE 0x04 +#define FUSB_REG_MEASURE_MDAC5 BIT(7) +#define FUSB_REG_MEASURE_MDAC4 BIT(6) +#define FUSB_REG_MEASURE_MDAC3 BIT(5) +#define FUSB_REG_MEASURE_MDAC2 BIT(4) +#define FUSB_REG_MEASURE_MDAC1 BIT(3) +#define FUSB_REG_MEASURE_MDAC0 BIT(2) +#define FUSB_REG_MEASURE_VBUS BIT(1) +#define FUSB_REG_MEASURE_XXXX5 BIT(0) +#define FUSB_REG_CONTROL0 0x06 +#define FUSB_REG_CONTROL0_TX_FLUSH BIT(6) +#define FUSB_REG_CONTROL0_INT_MASK BIT(5) +#define FUSB_REG_CONTROL0_HOST_CUR_MASK (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_HIGH (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_MED (0x8) +#define FUSB_REG_CONTROL0_HOST_CUR_DEF (0x4) +#define FUSB_REG_CONTROL0_TX_START BIT(0) +#define FUSB_REG_CONTROL1 0x07 +#define FUSB_REG_CONTROL1_ENSOP2DB BIT(6) +#define FUSB_REG_CONTROL1_ENSOP1DB BIT(5) +#define FUSB_REG_CONTROL1_BIST_MODE2 BIT(4) +#define FUSB_REG_CONTROL1_RX_FLUSH BIT(2) +#define FUSB_REG_CONTROL1_ENSOP2 BIT(1) +#define FUSB_REG_CONTROL1_ENSOP1 BIT(0) +#define FUSB_REG_CONTROL2 0x08 +#define FUSB_REG_CONTROL2_MODE BIT(1) +#define FUSB_REG_CONTROL2_MODE_MASK (0x6) +#define FUSB_REG_CONTROL2_MODE_DFP (0x6) +#define FUSB_REG_CONTROL2_MODE_UFP (0x4) +#define FUSB_REG_CONTROL2_MODE_DRP (0x2) +#define FUSB_REG_CONTROL2_MODE_NONE (0x0) +#define FUSB_REG_CONTROL2_TOGGLE BIT(0) +#define FUSB_REG_CONTROL3 0x09 +#define FUSB_REG_CONTROL3_SEND_HARDRESET BIT(6) +#define FUSB_REG_CONTROL3_BIST_TMODE BIT(5) /* 302B Only */ +#define FUSB_REG_CONTROL3_AUTO_HARDRESET BIT(4) +#define FUSB_REG_CONTROL3_AUTO_SOFTRESET BIT(3) +#define FUSB_REG_CONTROL3_N_RETRIES BIT(1) +#define FUSB_REG_CONTROL3_N_RETRIES_MASK (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_3 (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_2 (0x4) +#define FUSB_REG_CONTROL3_N_RETRIES_1 (0x2) +#define FUSB_REG_CONTROL3_AUTO_RETRY BIT(0) +#define FUSB_REG_MASK 0x0A +#define FUSB_REG_MASK_VBUSOK BIT(7) +#define FUSB_REG_MASK_ACTIVITY BIT(6) +#define FUSB_REG_MASK_COMP_CHNG BIT(5) +#define FUSB_REG_MASK_CRC_CHK BIT(4) +#define FUSB_REG_MASK_ALERT BIT(3) +#define FUSB_REG_MASK_WAKE BIT(2) +#define FUSB_REG_MASK_COLLISION BIT(1) +#define FUSB_REG_MASK_BC_LVL BIT(0) +#define FUSB_REG_POWER 0x0B +#define FUSB_REG_POWER_PWR BIT(0) +#define FUSB_REG_POWER_PWR_LOW 0x1 +#define FUSB_REG_POWER_PWR_MEDIUM 0x3 +#define FUSB_REG_POWER_PWR_HIGH 0x7 +#define FUSB_REG_POWER_PWR_ALL 0xF +#define FUSB_REG_RESET 0x0C +#define FUSB_REG_RESET_PD_RESET BIT(1) +#define FUSB_REG_RESET_SW_RESET BIT(0) +#define FUSB_REG_MASKA 0x0E +#define FUSB_REG_MASKA_OCP_TEMP BIT(7) +#define FUSB_REG_MASKA_TOGDONE BIT(6) +#define FUSB_REG_MASKA_SOFTFAIL BIT(5) +#define FUSB_REG_MASKA_RETRYFAIL BIT(4) +#define FUSB_REG_MASKA_HARDSENT BIT(3) +#define FUSB_REG_MASKA_TX_SUCCESS BIT(2) +#define FUSB_REG_MASKA_SOFTRESET BIT(1) +#define FUSB_REG_MASKA_HARDRESET BIT(0) +#define FUSB_REG_MASKB 0x0F +#define FUSB_REG_MASKB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0A 0x3C +#define FUSB_REG_STATUS0A_SOFTFAIL BIT(5) +#define FUSB_REG_STATUS0A_RETRYFAIL BIT(4) +#define FUSB_REG_STATUS0A_POWER BIT(2) +#define FUSB_REG_STATUS0A_RX_SOFT_RESET BIT(1) +#define FUSB_REG_STATUS0A_RX_HARD_RESET BIT(0) +#define FUSB_REG_STATUS1A 0x3D +#define FUSB_REG_STATUS1A_TOGSS BIT(3) +#define FUSB_REG_STATUS1A_TOGSS_RUNNING 0x0 +#define FUSB_REG_STATUS1A_TOGSS_SRC1 0x1 +#define FUSB_REG_STATUS1A_TOGSS_SRC2 0x2 +#define FUSB_REG_STATUS1A_TOGSS_SNK1 0x5 +#define FUSB_REG_STATUS1A_TOGSS_SNK2 0x6 +#define FUSB_REG_STATUS1A_TOGSS_AA 0x7 +#define FUSB_REG_STATUS1A_TOGSS_POS (3) +#define FUSB_REG_STATUS1A_TOGSS_MASK (0x7) +#define FUSB_REG_STATUS1A_RXSOP2DB BIT(2) +#define FUSB_REG_STATUS1A_RXSOP1DB BIT(1) +#define FUSB_REG_STATUS1A_RXSOP BIT(0) +#define FUSB_REG_INTERRUPTA 0x3E +#define FUSB_REG_INTERRUPTA_OCP_TEMP BIT(7) +#define FUSB_REG_INTERRUPTA_TOGDONE BIT(6) +#define FUSB_REG_INTERRUPTA_SOFTFAIL BIT(5) +#define FUSB_REG_INTERRUPTA_RETRYFAIL BIT(4) +#define FUSB_REG_INTERRUPTA_HARDSENT BIT(3) +#define FUSB_REG_INTERRUPTA_TX_SUCCESS BIT(2) +#define FUSB_REG_INTERRUPTA_SOFTRESET BIT(1) +#define FUSB_REG_INTERRUPTA_HARDRESET BIT(0) +#define FUSB_REG_INTERRUPTB 0x3F +#define FUSB_REG_INTERRUPTB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0 0x40 +#define FUSB_REG_STATUS0_VBUSOK BIT(7) +#define FUSB_REG_STATUS0_ACTIVITY BIT(6) +#define FUSB_REG_STATUS0_COMP BIT(5) +#define FUSB_REG_STATUS0_CRC_CHK BIT(4) +#define FUSB_REG_STATUS0_ALERT BIT(3) +#define FUSB_REG_STATUS0_WAKE BIT(2) +#define FUSB_REG_STATUS0_BC_LVL_MASK 0x03 +#define FUSB_REG_STATUS0_BC_LVL_0_200 0x0 +#define FUSB_REG_STATUS0_BC_LVL_200_600 0x1 +#define FUSB_REG_STATUS0_BC_LVL_600_1230 0x2 +#define FUSB_REG_STATUS0_BC_LVL_1230_MAX 0x3 +#define FUSB_REG_STATUS0_BC_LVL1 BIT(1) +#define FUSB_REG_STATUS0_BC_LVL0 BIT(0) +#define FUSB_REG_STATUS1 0x41 +#define FUSB_REG_STATUS1_RXSOP2 BIT(7) +#define FUSB_REG_STATUS1_RXSOP1 BIT(6) +#define FUSB_REG_STATUS1_RX_EMPTY BIT(5) +#define FUSB_REG_STATUS1_RX_FULL BIT(4) +#define FUSB_REG_STATUS1_TX_EMPTY BIT(3) +#define FUSB_REG_STATUS1_TX_FULL BIT(2) +#define FUSB_REG_INTERRUPT 0x42 +#define FUSB_REG_INTERRUPT_VBUSOK BIT(7) +#define FUSB_REG_INTERRUPT_ACTIVITY BIT(6) +#define FUSB_REG_INTERRUPT_COMP_CHNG BIT(5) +#define FUSB_REG_INTERRUPT_CRC_CHK BIT(4) +#define FUSB_REG_INTERRUPT_ALERT BIT(3) +#define FUSB_REG_INTERRUPT_WAKE BIT(2) +#define FUSB_REG_INTERRUPT_COLLISION BIT(1) +#define FUSB_REG_INTERRUPT_BC_LVL BIT(0) +#define FUSB_REG_FIFOS 0x43 + +/* Tokens defined for the FUSB302 TX FIFO */ +enum fusb302_txfifo_tokens { + FUSB302_TKN_TXON = 0xA1, + FUSB302_TKN_SYNC1 = 0x12, + FUSB302_TKN_SYNC2 = 0x13, + FUSB302_TKN_SYNC3 = 0x1B, + FUSB302_TKN_RST1 = 0x15, + FUSB302_TKN_RST2 = 0x16, + FUSB302_TKN_PACKSYM = 0x80, + FUSB302_TKN_JAMCRC = 0xFF, + FUSB302_TKN_EOP = 0x14, + FUSB302_TKN_TXOFF = 0xFE, +}; + +#endif diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c new file mode 100644 index 000000000000..ac6b418b15f1 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2015-2017 Google, Inc + * + * USB Type-C Port Controller Interface. + */ + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/usb/pd.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec.h> + +#include "tcpci.h" + +#define PD_RETRY_COUNT 3 + +struct tcpci { + struct device *dev; + + struct tcpm_port *port; + + struct regmap *regmap; + + bool controls_vbus; + + struct tcpc_dev tcpc; + struct tcpci_data *data; +}; + +struct tcpci_chip { + struct tcpci *tcpci; + struct tcpci_data data; +}; + +static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) +{ + return container_of(tcpc, struct tcpci, tcpc); +} + +static int tcpci_read16(struct tcpci *tcpci, unsigned int reg, u16 *val) +{ + return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); +} + +static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val) +{ + return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16)); +} + +static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + switch (cc) { + case TYPEC_CC_RA: + reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT); + break; + case TYPEC_CC_RD: + reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); + break; + case TYPEC_CC_RP_DEF: + reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | + (TCPC_ROLE_CTRL_RP_VAL_DEF << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_1_5: + reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | + (TCPC_ROLE_CTRL_RP_VAL_1_5 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_RP_3_0: + reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | + (TCPC_ROLE_CTRL_RP_VAL_3_0 << + TCPC_ROLE_CTRL_RP_VAL_SHIFT); + break; + case TYPEC_CC_OPEN: + default: + reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT); + break; + } + + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_start_drp_toggling(struct tcpc_dev *tcpc, + enum typec_cc_status cc) +{ + int ret; + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg = TCPC_ROLE_CTRL_DRP; + + /* Handle vendor drp toggling */ + if (tcpci->data->start_drp_toggling) { + ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc); + if (ret < 0) + return ret; + } + + 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 = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + return regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_LOOK4CONNECTION); +} + +static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink) +{ + switch (cc) { + case 0x1: + return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA; + case 0x2: + return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD; + case 0x3: + if (sink) + return TYPEC_CC_RP_3_0; + /* fall through */ + case 0x0: + default: + return TYPEC_CC_OPEN; + } +} + +static int tcpci_get_cc(struct tcpc_dev *tcpc, + enum typec_cc_status *cc1, enum typec_cc_status *cc2) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, ®); + if (ret < 0) + return ret; + + *cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) & + TCPC_CC_STATUS_CC1_MASK, + reg & TCPC_CC_STATUS_TERM); + *cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) & + TCPC_CC_STATUS_CC2_MASK, + reg & TCPC_CC_STATUS_TERM); + + return 0; +} + +static int tcpci_set_polarity(struct tcpc_dev *tcpc, + enum typec_cc_polarity polarity) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + /* Keep the disconnect cc line open */ + ret = regmap_read(tcpci->regmap, TCPC_ROLE_CTRL, ®); + if (ret < 0) + return ret; + + if (polarity == TYPEC_POLARITY_CC2) + reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT; + else + reg |= TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT; + ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg); + if (ret < 0) + return ret; + + return regmap_write(tcpci->regmap, TCPC_TCPC_CTRL, + (polarity == TYPEC_POLARITY_CC2) ? + TCPC_TCPC_CTRL_ORIENTATION : 0); +} + +static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + int ret; + + /* Handle vendor set vconn */ + if (tcpci->data->set_vconn) { + ret = tcpci->data->set_vconn(tcpci, tcpci->data, enable); + if (ret < 0) + return ret; + } + + return regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, + TCPC_POWER_CTRL_VCONN_ENABLE, + enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0); +} + +static int tcpci_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role role, enum typec_data_role data) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT; + if (role == TYPEC_SOURCE) + reg |= TCPC_MSG_HDR_INFO_PWR_ROLE; + if (data == TYPEC_HOST) + reg |= TCPC_MSG_HDR_INFO_DATA_ROLE; + ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg = 0; + int ret; + + if (enable) + reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET; + ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_get_vbus(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned int reg; + int ret; + + ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); + if (ret < 0) + return ret; + + return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); +} + +static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + int ret; + + /* Disable both source and sink first before enabling anything */ + + if (!source) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_DISABLE_SRC_VBUS); + if (ret < 0) + return ret; + } + + if (!sink) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_DISABLE_SINK_VBUS); + if (ret < 0) + return ret; + } + + if (source) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_SRC_VBUS_DEFAULT); + if (ret < 0) + return ret; + } + + if (sink) { + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_SINK_VBUS); + if (ret < 0) + return ret; + } + + return 0; +} + +static int tcpci_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + u16 header = msg ? le16_to_cpu(msg->header) : 0; + unsigned int reg, cnt; + int ret; + + cnt = msg ? pd_header_cnt(header) * 4 : 0; + ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt + 2); + if (ret < 0) + return ret; + + ret = tcpci_write16(tcpci, TCPC_TX_HDR, header); + if (ret < 0) + return ret; + + if (cnt > 0) { + ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA, + &msg->payload, cnt); + if (ret < 0) + return ret; + } + + reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | + (type << TCPC_TRANSMIT_TYPE_SHIFT); + ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg); + if (ret < 0) + return ret; + + return 0; +} + +static int tcpci_init(struct tcpc_dev *tcpc) +{ + struct tcpci *tcpci = tcpc_to_tcpci(tcpc); + unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */ + unsigned int reg; + int ret; + + while (time_before_eq(jiffies, timeout)) { + ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); + if (ret < 0) + return ret; + if (!(reg & TCPC_POWER_STATUS_UNINIT)) + break; + usleep_range(10000, 20000); + } + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + /* Handle vendor init */ + if (tcpci->data->init) { + ret = tcpci->data->init(tcpci, tcpci->data); + if (ret < 0) + return ret; + } + + /* Clear all events */ + ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff); + if (ret < 0) + return ret; + + if (tcpci->controls_vbus) + reg = TCPC_POWER_STATUS_VBUS_PRES; + else + reg = 0; + ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg); + if (ret < 0) + return ret; + + /* Enable Vbus detection */ + ret = regmap_write(tcpci->regmap, TCPC_COMMAND, + TCPC_CMD_ENABLE_VBUS_DETECT); + if (ret < 0) + return ret; + + reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED | + TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS | + TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS; + if (tcpci->controls_vbus) + reg |= TCPC_ALERT_POWER_STATUS; + return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg); +} + +irqreturn_t tcpci_irq(struct tcpci *tcpci) +{ + u16 status; + + tcpci_read16(tcpci, TCPC_ALERT, &status); + + /* + * Clear alert status for everything except RX_STATUS, which shouldn't + * be cleared until we have successfully retrieved message. + */ + if (status & ~TCPC_ALERT_RX_STATUS) + tcpci_write16(tcpci, TCPC_ALERT, + status & ~TCPC_ALERT_RX_STATUS); + + if (status & TCPC_ALERT_CC_STATUS) + tcpm_cc_change(tcpci->port); + + if (status & TCPC_ALERT_POWER_STATUS) { + unsigned int reg; + + regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, ®); + + /* + * If power status mask has been reset, then the TCPC + * has reset. + */ + if (reg == 0xff) + tcpm_tcpc_reset(tcpci->port); + else + tcpm_vbus_change(tcpci->port); + } + + if (status & TCPC_ALERT_RX_STATUS) { + struct pd_message msg; + unsigned int cnt; + u16 header; + + regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt); + + tcpci_read16(tcpci, TCPC_RX_HDR, &header); + msg.header = cpu_to_le16(header); + + if (WARN_ON(cnt > sizeof(msg.payload))) + cnt = sizeof(msg.payload); + + if (cnt > 0) + regmap_raw_read(tcpci->regmap, TCPC_RX_DATA, + &msg.payload, cnt); + + /* Read complete, clear RX status alert bit */ + tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS); + + tcpm_pd_receive(tcpci->port, &msg); + } + + if (status & TCPC_ALERT_RX_HARD_RST) + tcpm_pd_hard_reset(tcpci->port); + + if (status & TCPC_ALERT_TX_SUCCESS) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS); + else if (status & TCPC_ALERT_TX_DISCARDED) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED); + else if (status & TCPC_ALERT_TX_FAILED) + tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(tcpci_irq); + +static irqreturn_t _tcpci_irq(int irq, void *dev_id) +{ + struct tcpci_chip *chip = dev_id; + + return tcpci_irq(chip->tcpci); +} + +static const struct regmap_config tcpci_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */ +}; + +static int tcpci_parse_config(struct tcpci *tcpci) +{ + tcpci->controls_vbus = true; /* XXX */ + + tcpci->tcpc.fwnode = device_get_named_child_node(tcpci->dev, + "connector"); + if (!tcpci->tcpc.fwnode) { + dev_err(tcpci->dev, "Can't find connector node.\n"); + return -EINVAL; + } + + return 0; +} + +struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data) +{ + struct tcpci *tcpci; + int err; + + tcpci = devm_kzalloc(dev, sizeof(*tcpci), GFP_KERNEL); + if (!tcpci) + return ERR_PTR(-ENOMEM); + + tcpci->dev = dev; + tcpci->data = data; + tcpci->regmap = data->regmap; + + tcpci->tcpc.init = tcpci_init; + tcpci->tcpc.get_vbus = tcpci_get_vbus; + tcpci->tcpc.set_vbus = tcpci_set_vbus; + tcpci->tcpc.set_cc = tcpci_set_cc; + tcpci->tcpc.get_cc = tcpci_get_cc; + tcpci->tcpc.set_polarity = tcpci_set_polarity; + tcpci->tcpc.set_vconn = tcpci_set_vconn; + tcpci->tcpc.start_drp_toggling = tcpci_start_drp_toggling; + + tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx; + tcpci->tcpc.set_roles = tcpci_set_roles; + tcpci->tcpc.pd_transmit = tcpci_pd_transmit; + + err = tcpci_parse_config(tcpci); + if (err < 0) + return ERR_PTR(err); + + tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc); + if (IS_ERR(tcpci->port)) + return ERR_CAST(tcpci->port); + + return tcpci; +} +EXPORT_SYMBOL_GPL(tcpci_register_port); + +void tcpci_unregister_port(struct tcpci *tcpci) +{ + tcpm_unregister_port(tcpci->port); +} +EXPORT_SYMBOL_GPL(tcpci_unregister_port); + +static int tcpci_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct tcpci_chip *chip; + int err; + u16 val = 0; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->data.regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config); + if (IS_ERR(chip->data.regmap)) + return PTR_ERR(chip->data.regmap); + + i2c_set_clientdata(client, chip); + + /* Disable chip interrupts before requesting irq */ + err = regmap_raw_write(chip->data.regmap, TCPC_ALERT_MASK, &val, + sizeof(u16)); + if (err < 0) + return err; + + chip->tcpci = tcpci_register_port(&client->dev, &chip->data); + if (IS_ERR(chip->tcpci)) + return PTR_ERR(chip->tcpci); + + err = devm_request_threaded_irq(&client->dev, client->irq, NULL, + _tcpci_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + dev_name(&client->dev), chip); + if (err < 0) { + tcpci_unregister_port(chip->tcpci); + return err; + } + + return 0; +} + +static int tcpci_remove(struct i2c_client *client) +{ + struct tcpci_chip *chip = i2c_get_clientdata(client); + + tcpci_unregister_port(chip->tcpci); + + return 0; +} + +static const struct i2c_device_id tcpci_id[] = { + { "tcpci", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tcpci_id); + +#ifdef CONFIG_OF +static const struct of_device_id tcpci_of_match[] = { + { .compatible = "nxp,ptn5110", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tcpci_of_match); +#endif + +static struct i2c_driver tcpci_i2c_driver = { + .driver = { + .name = "tcpci", + .of_match_table = of_match_ptr(tcpci_of_match), + }, + .probe = tcpci_probe, + .remove = tcpci_remove, + .id_table = tcpci_id, +}; +module_i2c_driver(tcpci_i2c_driver); + +MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h new file mode 100644 index 000000000000..303ebde26546 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpci.h @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2015-2017 Google, Inc + * + * USB Type-C Port Controller Interface. + */ + +#ifndef __LINUX_USB_TCPCI_H +#define __LINUX_USB_TCPCI_H + +#define TCPC_VENDOR_ID 0x0 +#define TCPC_PRODUCT_ID 0x2 +#define TCPC_BCD_DEV 0x4 +#define TCPC_TC_REV 0x6 +#define TCPC_PD_REV 0x8 +#define TCPC_PD_INT_REV 0xa + +#define TCPC_ALERT 0x10 +#define TCPC_ALERT_VBUS_DISCNCT BIT(11) +#define TCPC_ALERT_RX_BUF_OVF BIT(10) +#define TCPC_ALERT_FAULT BIT(9) +#define TCPC_ALERT_V_ALARM_LO BIT(8) +#define TCPC_ALERT_V_ALARM_HI BIT(7) +#define TCPC_ALERT_TX_SUCCESS BIT(6) +#define TCPC_ALERT_TX_DISCARDED BIT(5) +#define TCPC_ALERT_TX_FAILED BIT(4) +#define TCPC_ALERT_RX_HARD_RST BIT(3) +#define TCPC_ALERT_RX_STATUS BIT(2) +#define TCPC_ALERT_POWER_STATUS BIT(1) +#define TCPC_ALERT_CC_STATUS BIT(0) + +#define TCPC_ALERT_MASK 0x12 +#define TCPC_POWER_STATUS_MASK 0x14 +#define TCPC_FAULT_STATUS_MASK 0x15 +#define TCPC_CONFIG_STD_OUTPUT 0x18 + +#define TCPC_TCPC_CTRL 0x19 +#define TCPC_TCPC_CTRL_ORIENTATION BIT(0) + +#define TCPC_ROLE_CTRL 0x1a +#define TCPC_ROLE_CTRL_DRP BIT(6) +#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4 +#define TCPC_ROLE_CTRL_RP_VAL_MASK 0x3 +#define TCPC_ROLE_CTRL_RP_VAL_DEF 0x0 +#define TCPC_ROLE_CTRL_RP_VAL_1_5 0x1 +#define TCPC_ROLE_CTRL_RP_VAL_3_0 0x2 +#define TCPC_ROLE_CTRL_CC2_SHIFT 2 +#define TCPC_ROLE_CTRL_CC2_MASK 0x3 +#define TCPC_ROLE_CTRL_CC1_SHIFT 0 +#define TCPC_ROLE_CTRL_CC1_MASK 0x3 +#define TCPC_ROLE_CTRL_CC_RA 0x0 +#define TCPC_ROLE_CTRL_CC_RP 0x1 +#define TCPC_ROLE_CTRL_CC_RD 0x2 +#define TCPC_ROLE_CTRL_CC_OPEN 0x3 + +#define TCPC_FAULT_CTRL 0x1b + +#define TCPC_POWER_CTRL 0x1c +#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 +#define TCPC_CC_STATUS_CC1_SHIFT 0 +#define TCPC_CC_STATUS_CC1_MASK 0x3 + +#define TCPC_POWER_STATUS 0x1e +#define TCPC_POWER_STATUS_UNINIT BIT(6) +#define TCPC_POWER_STATUS_VBUS_DET BIT(3) +#define TCPC_POWER_STATUS_VBUS_PRES BIT(2) + +#define TCPC_FAULT_STATUS 0x1f + +#define TCPC_COMMAND 0x23 +#define TCPC_CMD_WAKE_I2C 0x11 +#define TCPC_CMD_DISABLE_VBUS_DETECT 0x22 +#define TCPC_CMD_ENABLE_VBUS_DETECT 0x33 +#define TCPC_CMD_DISABLE_SINK_VBUS 0x44 +#define TCPC_CMD_SINK_VBUS 0x55 +#define TCPC_CMD_DISABLE_SRC_VBUS 0x66 +#define TCPC_CMD_SRC_VBUS_DEFAULT 0x77 +#define TCPC_CMD_SRC_VBUS_HIGH 0x88 +#define TCPC_CMD_LOOK4CONNECTION 0x99 +#define TCPC_CMD_RXONEMORE 0xAA +#define TCPC_CMD_I2C_IDLE 0xFF + +#define TCPC_DEV_CAP_1 0x24 +#define TCPC_DEV_CAP_2 0x26 +#define TCPC_STD_INPUT_CAP 0x28 +#define TCPC_STD_OUTPUT_CAP 0x29 + +#define TCPC_MSG_HDR_INFO 0x2e +#define TCPC_MSG_HDR_INFO_DATA_ROLE BIT(3) +#define TCPC_MSG_HDR_INFO_PWR_ROLE BIT(0) +#define TCPC_MSG_HDR_INFO_REV_SHIFT 1 +#define TCPC_MSG_HDR_INFO_REV_MASK 0x3 + +#define TCPC_RX_DETECT 0x2f +#define TCPC_RX_DETECT_HARD_RESET BIT(5) +#define TCPC_RX_DETECT_SOP BIT(0) + +#define TCPC_RX_BYTE_CNT 0x30 +#define TCPC_RX_BUF_FRAME_TYPE 0x31 +#define TCPC_RX_HDR 0x32 +#define TCPC_RX_DATA 0x34 /* through 0x4f */ + +#define TCPC_TRANSMIT 0x50 +#define TCPC_TRANSMIT_RETRY_SHIFT 4 +#define TCPC_TRANSMIT_RETRY_MASK 0x3 +#define TCPC_TRANSMIT_TYPE_SHIFT 0 +#define TCPC_TRANSMIT_TYPE_MASK 0x7 + +#define TCPC_TX_BYTE_CNT 0x51 +#define TCPC_TX_HDR 0x52 +#define TCPC_TX_DATA 0x54 /* through 0x6f */ + +#define TCPC_VBUS_VOLTAGE 0x70 +#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72 +#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74 +#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 +#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 + +struct tcpci; +struct tcpci_data { + struct regmap *regmap; + int (*init)(struct tcpci *tcpci, struct tcpci_data *data); + int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data, + bool enable); + int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data, + enum typec_cc_status cc); +}; + +struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data); +void tcpci_unregister_port(struct tcpci *tcpci); +irqreturn_t tcpci_irq(struct tcpci *tcpci); + +#endif /* __LINUX_USB_TCPCI_H */ diff --git a/drivers/usb/typec/tcpm/tcpci_rt1711h.c b/drivers/usb/typec/tcpm/tcpci_rt1711h.c new file mode 100644 index 000000000000..017389021b96 --- /dev/null +++ b/drivers/usb/typec/tcpm/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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/gpio/consumer.h> +#include <linux/usb/tcpm.h> +#include <linux/regmap.h> +#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 <shufan_lee@richtek.com>"); +MODULE_DESCRIPTION("RT1711H USB Type-C Port Controller Interface Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c new file mode 100644 index 000000000000..4f1f4215f3d6 --- /dev/null +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -0,0 +1,4851 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2015-2017 Google, Inc + * + * USB Power Delivery protocol stack. + */ + +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/proc_fs.h> +#include <linux/property.h> +#include <linux/sched/clock.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/usb/pd.h> +#include <linux/usb/pd_ado.h> +#include <linux/usb/pd_bdo.h> +#include <linux/usb/pd_ext_sdb.h> +#include <linux/usb/pd_vdo.h> +#include <linux/usb/role.h> +#include <linux/usb/tcpm.h> +#include <linux/usb/typec_altmode.h> +#include <linux/workqueue.h> + +#define FOREACH_STATE(S) \ + S(INVALID_STATE), \ + S(DRP_TOGGLING), \ + S(SRC_UNATTACHED), \ + S(SRC_ATTACH_WAIT), \ + S(SRC_ATTACHED), \ + S(SRC_STARTUP), \ + S(SRC_SEND_CAPABILITIES), \ + S(SRC_NEGOTIATE_CAPABILITIES), \ + S(SRC_TRANSITION_SUPPLY), \ + S(SRC_READY), \ + S(SRC_WAIT_NEW_CAPABILITIES), \ + \ + S(SNK_UNATTACHED), \ + S(SNK_ATTACH_WAIT), \ + S(SNK_DEBOUNCED), \ + S(SNK_ATTACHED), \ + S(SNK_STARTUP), \ + S(SNK_DISCOVERY), \ + S(SNK_DISCOVERY_DEBOUNCE), \ + 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), \ + \ + S(ACC_UNATTACHED), \ + S(DEBUG_ACC_ATTACHED), \ + S(AUDIO_ACC_ATTACHED), \ + S(AUDIO_ACC_DEBOUNCE), \ + \ + S(HARD_RESET_SEND), \ + S(HARD_RESET_START), \ + S(SRC_HARD_RESET_VBUS_OFF), \ + S(SRC_HARD_RESET_VBUS_ON), \ + S(SNK_HARD_RESET_SINK_OFF), \ + S(SNK_HARD_RESET_WAIT_VBUS), \ + S(SNK_HARD_RESET_SINK_ON), \ + \ + S(SOFT_RESET), \ + S(SOFT_RESET_SEND), \ + \ + S(DR_SWAP_ACCEPT), \ + S(DR_SWAP_SEND), \ + S(DR_SWAP_SEND_TIMEOUT), \ + S(DR_SWAP_CANCEL), \ + S(DR_SWAP_CHANGE_DR), \ + \ + S(PR_SWAP_ACCEPT), \ + S(PR_SWAP_SEND), \ + S(PR_SWAP_SEND_TIMEOUT), \ + S(PR_SWAP_CANCEL), \ + S(PR_SWAP_START), \ + S(PR_SWAP_SRC_SNK_TRANSITION_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF), \ + S(PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED), \ + S(PR_SWAP_SRC_SNK_SINK_ON), \ + S(PR_SWAP_SNK_SRC_SINK_OFF), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON), \ + S(PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP), \ + \ + S(VCONN_SWAP_ACCEPT), \ + S(VCONN_SWAP_SEND), \ + S(VCONN_SWAP_SEND_TIMEOUT), \ + S(VCONN_SWAP_CANCEL), \ + S(VCONN_SWAP_START), \ + S(VCONN_SWAP_WAIT_FOR_VCONN), \ + S(VCONN_SWAP_TURN_ON_VCONN), \ + S(VCONN_SWAP_TURN_OFF_VCONN), \ + \ + S(SNK_TRY), \ + S(SNK_TRY_WAIT), \ + S(SNK_TRY_WAIT_DEBOUNCE), \ + S(SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS), \ + S(SRC_TRYWAIT), \ + S(SRC_TRYWAIT_DEBOUNCE), \ + S(SRC_TRYWAIT_UNATTACHED), \ + \ + S(SRC_TRY), \ + S(SRC_TRY_WAIT), \ + S(SRC_TRY_DEBOUNCE), \ + S(SNK_TRYWAIT), \ + S(SNK_TRYWAIT_DEBOUNCE), \ + 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) + +#define GENERATE_ENUM(e) e +#define GENERATE_STRING(s) #s + +enum tcpm_state { + FOREACH_STATE(GENERATE_ENUM) +}; + +static const char * const tcpm_states[] = { + FOREACH_STATE(GENERATE_STRING) +}; + +enum vdm_states { + VDM_STATE_ERR_BUSY = -3, + VDM_STATE_ERR_SEND = -2, + VDM_STATE_ERR_TMOUT = -1, + VDM_STATE_DONE = 0, + /* Anything >0 represents an active state */ + VDM_STATE_READY = 1, + VDM_STATE_BUSY = 2, + VDM_STATE_WAIT_RSP_BUSY = 3, +}; + +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, +}; + +/* Events from low level driver */ + +#define TCPM_CC_EVENT BIT(0) +#define TCPM_VBUS_EVENT BIT(1) +#define TCPM_RESET_EVENT BIT(2) + +#define LOG_BUFFER_ENTRIES 1024 +#define LOG_BUFFER_ENTRY_SIZE 128 + +/* Alternate mode support */ + +#define SVID_DISCOVERY_MAX 16 +#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX) + +struct pd_mode_data { + int svid_index; /* current SVID index */ + int nsvids; + u16 svids[SVID_DISCOVERY_MAX]; + int altmodes; /* number of alternate modes */ + struct typec_altmode_desc altmode_desc[ALTMODE_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; + + struct mutex lock; /* tcpm state machine lock */ + struct workqueue_struct *wq; + + struct typec_capability typec_caps; + struct typec_port *typec_port; + + struct tcpc_dev *tcpc; + struct usb_role_switch *role_sw; + + enum typec_role vconn_role; + enum typec_role pwr_role; + enum typec_data_role data_role; + enum typec_pwr_opmode pwr_opmode; + + struct usb_pd_identity partner_ident; + struct typec_partner_desc partner_desc; + struct typec_partner *partner; + + enum typec_cc_status cc_req; + + enum typec_cc_status cc1; + enum typec_cc_status cc2; + enum typec_cc_polarity polarity; + + bool attached; + bool connected; + enum typec_port_type port_type; + bool vbus_present; + bool vbus_never_low; + bool vbus_source; + bool vbus_charge; + + bool send_discover; + bool op_vsafe5v; + + int try_role; + int try_snk_count; + int try_src_count; + + enum pd_msg_request queued_message; + + enum tcpm_state enter_state; + enum tcpm_state prev_state; + enum tcpm_state state; + enum tcpm_state delayed_state; + unsigned long delayed_runtime; + unsigned long delay_ms; + + spinlock_t pd_event_lock; + u32 pd_events; + + struct work_struct event_work; + struct delayed_work state_machine; + struct delayed_work vdm_state_machine; + bool state_machine_running; + + struct completion tx_complete; + enum tcpm_transmit_status tx_status; + + struct mutex swap_lock; /* swap command lock */ + bool swap_pending; + bool non_pd_role_swap; + struct completion swap_complete; + int swap_status; + + unsigned int negotiated_rev; + unsigned int message_id; + unsigned int caps_count; + unsigned int hard_reset_count; + bool pd_capable; + bool explicit_contract; + unsigned int rx_msgid; + + /* Partner capabilities/requests */ + u32 sink_request; + u32 source_caps[PDO_MAX_OBJECTS]; + unsigned int nr_source_caps; + u32 sink_caps[PDO_MAX_OBJECTS]; + unsigned int nr_sink_caps; + + /* Local capabilities */ + u32 src_pdo[PDO_MAX_OBJECTS]; + unsigned int nr_src_pdo; + u32 snk_pdo[PDO_MAX_OBJECTS]; + unsigned int nr_snk_pdo; + u32 snk_vdo[VDO_MAX_OBJECTS]; + unsigned int nr_snk_vdo; + + unsigned int operating_snk_mw; + bool update_sink_caps; + + /* Requested current / voltage */ + 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 */ + enum vdm_states vdm_state; + u32 vdm_retries; + /* next Vendor Defined Message to send */ + u32 vdo_data[VDO_MAX_SIZE]; + u8 vdo_count; + /* VDO to retry if UFP responder replied busy */ + u32 vdo_retry; + + /* 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[ALTMODE_DISCOVERY_MAX]; + struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX]; + + /* Deadline in jiffies to exit src_try_wait state */ + unsigned long max_wait; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; + struct mutex logbuffer_lock; /* log buffer access lock */ + int logbuffer_head; + int logbuffer_tail; + u8 *logbuffer[LOG_BUFFER_ENTRIES]; +#endif +}; + +struct pd_rx_event { + struct work_struct work; + struct tcpm_port *port; + struct pd_message msg; +}; + +#define tcpm_cc_is_sink(cc) \ + ((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \ + (cc) == TYPEC_CC_RP_3_0) + +#define tcpm_port_is_sink(port) \ + ((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \ + (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1))) + +#define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD) +#define tcpm_cc_is_audio(cc) ((cc) == TYPEC_CC_RA) +#define tcpm_cc_is_open(cc) ((cc) == TYPEC_CC_OPEN) + +#define tcpm_port_is_source(port) \ + ((tcpm_cc_is_source((port)->cc1) && \ + !tcpm_cc_is_source((port)->cc2)) || \ + (tcpm_cc_is_source((port)->cc2) && \ + !tcpm_cc_is_source((port)->cc1))) + +#define tcpm_port_is_debug(port) \ + (tcpm_cc_is_source((port)->cc1) && tcpm_cc_is_source((port)->cc2)) + +#define tcpm_port_is_audio(port) \ + (tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_audio((port)->cc2)) + +#define tcpm_port_is_audio_detached(port) \ + ((tcpm_cc_is_audio((port)->cc1) && tcpm_cc_is_open((port)->cc2)) || \ + (tcpm_cc_is_audio((port)->cc2) && tcpm_cc_is_open((port)->cc1))) + +#define tcpm_try_snk(port) \ + ((port)->try_snk_count == 0 && (port)->try_role == TYPEC_SINK && \ + (port)->port_type == TYPEC_PORT_DRP) + +#define tcpm_try_src(port) \ + ((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE && \ + (port)->port_type == TYPEC_PORT_DRP) + +static enum tcpm_state tcpm_default_state(struct tcpm_port *port) +{ + if (port->port_type == TYPEC_PORT_DRP) { + if (port->try_role == TYPEC_SINK) + return SNK_UNATTACHED; + else if (port->try_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + else if (port->tcpc->config->default_role == TYPEC_SINK) + return SNK_UNATTACHED; + /* Fall through to return SRC_UNATTACHED */ + } else if (port->port_type == TYPEC_PORT_SNK) { + return SNK_UNATTACHED; + } + return SRC_UNATTACHED; +} + +static inline +struct tcpm_port *typec_cap_to_tcpm(const struct typec_capability *cap) +{ + return container_of(cap, struct tcpm_port, typec_caps); +} + +static bool tcpm_port_is_disconnected(struct tcpm_port *port) +{ + return (!port->attached && port->cc1 == TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN) || + (port->attached && ((port->polarity == TYPEC_POLARITY_CC1 && + port->cc1 == TYPEC_CC_OPEN) || + (port->polarity == TYPEC_POLARITY_CC2 && + port->cc2 == TYPEC_CC_OPEN))); +} + +/* + * Logging + */ + +#ifdef CONFIG_DEBUG_FS + +static bool tcpm_log_full(struct tcpm_port *port) +{ + return port->logbuffer_tail == + (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; +} + +__printf(2, 0) +static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args) +{ + char tmpbuffer[LOG_BUFFER_ENTRY_SIZE]; + u64 ts_nsec = local_clock(); + unsigned long rem_nsec; + + mutex_lock(&port->logbuffer_lock); + if (!port->logbuffer[port->logbuffer_head]) { + port->logbuffer[port->logbuffer_head] = + kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL); + if (!port->logbuffer[port->logbuffer_head]) { + mutex_unlock(&port->logbuffer_lock); + return; + } + } + + vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args); + + if (tcpm_log_full(port)) { + port->logbuffer_head = max(port->logbuffer_head - 1, 0); + strcpy(tmpbuffer, "overflow"); + } + + if (port->logbuffer_head < 0 || + port->logbuffer_head >= LOG_BUFFER_ENTRIES) { + dev_warn(port->dev, + "Bad log buffer index %d\n", port->logbuffer_head); + goto abort; + } + + if (!port->logbuffer[port->logbuffer_head]) { + dev_warn(port->dev, + "Log buffer index %d is NULL\n", port->logbuffer_head); + goto abort; + } + + rem_nsec = do_div(ts_nsec, 1000000000); + scnprintf(port->logbuffer[port->logbuffer_head], + LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s", + (unsigned long)ts_nsec, rem_nsec / 1000, + tmpbuffer); + port->logbuffer_head = (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES; + +abort: + mutex_unlock(&port->logbuffer_lock); +} + +__printf(2, 3) +static void tcpm_log(struct tcpm_port *port, const char *fmt, ...) +{ + va_list args; + + /* Do not log while disconnected and unattached */ + if (tcpm_port_is_disconnected(port) && + (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED || + port->state == DRP_TOGGLING)) + return; + + va_start(args, fmt); + _tcpm_log(port, fmt, args); + va_end(args); +} + +__printf(2, 3) +static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + _tcpm_log(port, fmt, args); + va_end(args); +} + +static void tcpm_log_source_caps(struct tcpm_port *port) +{ + int i; + + for (i = 0; i < port->nr_source_caps; i++) { + u32 pdo = port->source_caps[i]; + enum pd_pdo_type type = pdo_type(pdo); + char msg[64]; + + switch (type) { + case PDO_TYPE_FIXED: + scnprintf(msg, sizeof(msg), + "%u mV, %u mA [%s%s%s%s%s%s]", + pdo_fixed_voltage(pdo), + pdo_max_current(pdo), + (pdo & PDO_FIXED_DUAL_ROLE) ? + "R" : "", + (pdo & PDO_FIXED_SUSPEND) ? + "S" : "", + (pdo & PDO_FIXED_HIGHER_CAP) ? + "H" : "", + (pdo & PDO_FIXED_USB_COMM) ? + "U" : "", + (pdo & PDO_FIXED_DATA_SWAP) ? + "D" : "", + (pdo & PDO_FIXED_EXTPOWER) ? + "E" : ""); + break; + case PDO_TYPE_VAR: + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mA", + pdo_min_voltage(pdo), + pdo_max_voltage(pdo), + pdo_max_current(pdo)); + break; + case PDO_TYPE_BATT: + scnprintf(msg, sizeof(msg), + "%u-%u mV, %u mW", + pdo_min_voltage(pdo), + 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; + } + tcpm_log(port, " PDO %d: type %d, %s", + i, type, msg); + } +} + +static int tcpm_debug_show(struct seq_file *s, void *v) +{ + struct tcpm_port *port = (struct tcpm_port *)s->private; + int tail; + + mutex_lock(&port->logbuffer_lock); + tail = port->logbuffer_tail; + while (tail != port->logbuffer_head) { + seq_printf(s, "%s\n", port->logbuffer[tail]); + tail = (tail + 1) % LOG_BUFFER_ENTRIES; + } + if (!seq_has_overflowed(s)) + port->logbuffer_tail = tail; + mutex_unlock(&port->logbuffer_lock); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(tcpm_debug); + +static struct dentry *rootdir; + +static void tcpm_debugfs_init(struct tcpm_port *port) +{ + mutex_init(&port->logbuffer_lock); + /* /sys/kernel/debug/tcpm/usbcX */ + if (!rootdir) + rootdir = debugfs_create_dir("tcpm", NULL); + + port->dentry = debugfs_create_file(dev_name(port->dev), + S_IFREG | 0444, rootdir, + port, &tcpm_debug_fops); +} + +static void tcpm_debugfs_exit(struct tcpm_port *port) +{ + debugfs_remove(port->dentry); +} + +#else + +__printf(2, 3) +static void tcpm_log(const struct tcpm_port *port, const char *fmt, ...) { } +__printf(2, 3) +static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...) { } +static void tcpm_log_source_caps(struct tcpm_port *port) { } +static void tcpm_debugfs_init(const struct tcpm_port *port) { } +static void tcpm_debugfs_exit(const struct tcpm_port *port) { } + +#endif + +static int tcpm_pd_transmit(struct tcpm_port *port, + enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + unsigned long timeout; + int ret; + + if (msg) + tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header)); + else + tcpm_log(port, "PD TX, type: %#x", type); + + reinit_completion(&port->tx_complete); + ret = port->tcpc->pd_transmit(port->tcpc, type, msg); + if (ret < 0) + return ret; + + mutex_unlock(&port->lock); + timeout = wait_for_completion_timeout(&port->tx_complete, + msecs_to_jiffies(PD_T_TCPC_TX_TIMEOUT)); + mutex_lock(&port->lock); + if (!timeout) + return -ETIMEDOUT; + + switch (port->tx_status) { + case TCPC_TX_SUCCESS: + port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK; + return 0; + case TCPC_TX_DISCARDED: + return -EAGAIN; + case TCPC_TX_FAILED: + default: + return -EIO; + } +} + +void tcpm_pd_transmit_complete(struct tcpm_port *port, + enum tcpm_transmit_status status) +{ + tcpm_log(port, "PD TX complete, status: %u", status); + port->tx_status = status; + complete(&port->tx_complete); +} +EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete); + +static int tcpm_mux_set(struct tcpm_port *port, int state, + enum usb_role usb_role, + enum typec_orientation orientation) +{ + int ret; + + tcpm_log(port, "Requesting mux state %d, usb-role %d, orientation %d", + state, usb_role, orientation); + + ret = typec_set_orientation(port->typec_port, orientation); + if (ret) + return ret; + + if (port->role_sw) { + ret = usb_role_switch_set_role(port->role_sw, usb_role); + if (ret) + return ret; + } + + return typec_set_mode(port->typec_port, state); +} + +static int tcpm_set_polarity(struct tcpm_port *port, + enum typec_cc_polarity polarity) +{ + int ret; + + tcpm_log(port, "polarity %d", polarity); + + ret = port->tcpc->set_polarity(port->tcpc, polarity); + if (ret < 0) + return ret; + + port->polarity = polarity; + + return 0; +} + +static int tcpm_set_vconn(struct tcpm_port *port, bool enable) +{ + int ret; + + tcpm_log(port, "vconn:=%d", enable); + + ret = port->tcpc->set_vconn(port->tcpc, enable); + if (!ret) { + port->vconn_role = enable ? TYPEC_SOURCE : TYPEC_SINK; + typec_set_vconn_role(port->typec_port, port->vconn_role); + } + + return ret; +} + +static u32 tcpm_get_current_limit(struct tcpm_port *port) +{ + enum typec_cc_status cc; + u32 limit; + + cc = port->polarity ? port->cc2 : port->cc1; + switch (cc) { + case TYPEC_CC_RP_1_5: + limit = 1500; + break; + case TYPEC_CC_RP_3_0: + limit = 3000; + break; + case TYPEC_CC_RP_DEF: + default: + if (port->tcpc->get_current_limit) + limit = port->tcpc->get_current_limit(port->tcpc); + else + limit = 0; + break; + } + + return limit; +} + +static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv) +{ + int ret = -EOPNOTSUPP; + + tcpm_log(port, "Setting voltage/current limit %u mV %u mA", mv, max_ma); + + port->supply_voltage = mv; + port->current_limit = max_ma; + + if (port->tcpc->set_current_limit) + ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv); + + return ret; +} + +/* + * Determine RP value to set based on maximum current supported + * by a port if configured as source. + * Returns CC value to report to link partner. + */ +static enum typec_cc_status tcpm_rp_cc(struct tcpm_port *port) +{ + const u32 *src_pdo = port->src_pdo; + int nr_pdo = port->nr_src_pdo; + int i; + + /* + * Search for first entry with matching voltage. + * It should report the maximum supported current. + */ + for (i = 0; i < nr_pdo; i++) { + const u32 pdo = src_pdo[i]; + + if (pdo_type(pdo) == PDO_TYPE_FIXED && + pdo_fixed_voltage(pdo) == 5000) { + unsigned int curr = pdo_max_current(pdo); + + if (curr >= 3000) + return TYPEC_CC_RP_3_0; + else if (curr >= 1500) + return TYPEC_CC_RP_1_5; + return TYPEC_CC_RP_DEF; + } + } + + return TYPEC_CC_RP_DEF; +} + +static int tcpm_set_attached_state(struct tcpm_port *port, bool attached) +{ + return port->tcpc->set_roles(port->tcpc, attached, port->pwr_role, + port->data_role); +} + +static int tcpm_set_roles(struct tcpm_port *port, bool attached, + enum typec_role role, enum typec_data_role data) +{ + enum typec_orientation orientation; + enum usb_role usb_role; + int ret; + + if (port->polarity == TYPEC_POLARITY_CC1) + orientation = TYPEC_ORIENTATION_NORMAL; + else + orientation = TYPEC_ORIENTATION_REVERSE; + + if (data == TYPEC_HOST) + usb_role = USB_ROLE_HOST; + else + usb_role = USB_ROLE_DEVICE; + + ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation); + if (ret < 0) + return ret; + + ret = port->tcpc->set_roles(port->tcpc, attached, role, data); + if (ret < 0) + return ret; + + port->pwr_role = role; + port->data_role = data; + typec_set_data_role(port->typec_port, data); + typec_set_pwr_role(port->typec_port, role); + + return 0; +} + +static int tcpm_set_pwr_role(struct tcpm_port *port, enum typec_role role) +{ + int ret; + + ret = port->tcpc->set_roles(port->tcpc, true, role, + port->data_role); + if (ret < 0) + return ret; + + port->pwr_role = role; + typec_set_pwr_role(port->typec_port, role); + + return 0; +} + +static int tcpm_pd_send_source_caps(struct tcpm_port *port) +{ + struct pd_message msg; + int i; + + memset(&msg, 0, sizeof(msg)); + if (!port->nr_src_pdo) { + /* No source capabilities defined, sink only */ + 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); + } + for (i = 0; i < port->nr_src_pdo; i++) + msg.payload[i] = cpu_to_le32(port->src_pdo[i]); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_pd_send_sink_caps(struct tcpm_port *port) +{ + struct pd_message msg; + int i; + + memset(&msg, 0, sizeof(msg)); + if (!port->nr_snk_pdo) { + /* No sink capabilities defined, source only */ + 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); + } + for (i = 0; i < port->nr_snk_pdo; i++) + msg.payload[i] = cpu_to_le32(port->snk_pdo[i]); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state, + unsigned int delay_ms) +{ + if (delay_ms) { + tcpm_log(port, "pending state change %s -> %s @ %u ms", + tcpm_states[port->state], tcpm_states[state], + delay_ms); + port->delayed_state = state; + mod_delayed_work(port->wq, &port->state_machine, + msecs_to_jiffies(delay_ms)); + port->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms); + port->delay_ms = delay_ms; + } else { + tcpm_log(port, "state change %s -> %s", + tcpm_states[port->state], tcpm_states[state]); + port->delayed_state = INVALID_STATE; + port->prev_state = port->state; + port->state = state; + /* + * Don't re-queue the state machine work item if we're currently + * in the state machine and we're immediately changing states. + * tcpm_state_machine_work() will continue running the state + * machine. + */ + if (!port->state_machine_running) + mod_delayed_work(port->wq, &port->state_machine, 0); + } +} + +static void tcpm_set_state_cond(struct tcpm_port *port, enum tcpm_state state, + unsigned int delay_ms) +{ + if (port->enter_state == port->state) + tcpm_set_state(port, state, delay_ms); + else + tcpm_log(port, + "skipped %sstate change %s -> %s [%u ms], context state %s", + delay_ms ? "delayed " : "", + tcpm_states[port->state], tcpm_states[state], + delay_ms, tcpm_states[port->enter_state]); +} + +static void tcpm_queue_message(struct tcpm_port *port, + enum pd_msg_request message) +{ + port->queued_message = message; + mod_delayed_work(port->wq, &port->state_machine, 0); +} + +/* + * VDM/VDO handling functions + */ +static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header, + const u32 *data, int cnt) +{ + port->vdo_count = cnt + 1; + port->vdo_data[0] = header; + memcpy(&port->vdo_data[1], data, sizeof(u32) * cnt); + /* Set ready, vdm state machine will actually send */ + port->vdm_retries = 0; + port->vdm_state = VDM_STATE_READY; +} + +static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + u32 vdo = le32_to_cpu(payload[VDO_INDEX_IDH]); + u32 product = le32_to_cpu(payload[VDO_INDEX_PRODUCT]); + + memset(&port->mode_data, 0, sizeof(port->mode_data)); + + port->partner_ident.id_header = vdo; + port->partner_ident.cert_stat = le32_to_cpu(payload[VDO_INDEX_CSTAT]); + port->partner_ident.product = product; + + typec_partner_set_identity(port->partner); + + tcpm_log(port, "Identity: %04x:%04x.%04x", + PD_IDH_VID(vdo), + PD_PRODUCT_PID(product), product & 0xffff); +} + +static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + struct pd_mode_data *pmdata = &port->mode_data; + int i; + + for (i = 1; i < cnt; i++) { + u32 p = le32_to_cpu(payload[i]); + u16 svid; + + svid = (p >> 16) & 0xffff; + if (!svid) + return false; + + if (pmdata->nsvids >= SVID_DISCOVERY_MAX) + goto abort; + + pmdata->svids[pmdata->nsvids++] = svid; + tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); + + svid = p & 0xffff; + if (!svid) + return false; + + if (pmdata->nsvids >= SVID_DISCOVERY_MAX) + goto abort; + + pmdata->svids[pmdata->nsvids++] = svid; + tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid); + } + return true; +abort: + tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX); + return false; +} + +static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload, + int cnt) +{ + struct pd_mode_data *pmdata = &port->mode_data; + struct typec_altmode_desc *paltmode; + int i; + + if (pmdata->altmodes >= ARRAY_SIZE(port->partner_altmode)) { + /* Already logged in svdm_consume_svids() */ + return; + } + + for (i = 1; i < cnt; i++) { + paltmode = &pmdata->altmode_desc[pmdata->altmodes]; + memset(paltmode, 0, sizeof(*paltmode)); + + paltmode->svid = pmdata->svids[pmdata->svid_index]; + paltmode->mode = i; + paltmode->vdo = le32_to_cpu(payload[i]); + + tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x", + pmdata->altmodes, paltmode->svid, + paltmode->mode, paltmode->vdo); + + pmdata->altmodes++; + } +} + +static void tcpm_register_partner_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data; + struct typec_altmode *altmode; + int i; + + for (i = 0; i < modep->altmodes; i++) { + altmode = typec_partner_register_altmode(port->partner, + &modep->altmode_desc[i]); + if (!altmode) + tcpm_log(port, "Failed to register partner SVID 0x%04x", + modep->altmode_desc[i].svid); + port->partner_altmode[i] = altmode; + } +} + +#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header) + +static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt, + u32 *response) +{ + struct typec_altmode *adev; + struct typec_altmode *pdev; + struct pd_mode_data *modep; + u32 p[PD_MAX_PAYLOAD]; + int rlen = 0; + int cmd_type; + int cmd; + int i; + + for (i = 0; i < cnt; i++) + p[i] = le32_to_cpu(payload[i]); + + cmd_type = PD_VDO_CMDT(p[0]); + cmd = PD_VDO_CMD(p[0]); + + tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d", + p[0], cmd_type, cmd, cnt); + + modep = &port->mode_data; + + adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); + + pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX, + PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0])); + + switch (cmd_type) { + case CMDT_INIT: + switch (cmd) { + case CMD_DISCOVER_IDENT: + /* 6.4.4.3.1: Only respond as UFP (device) */ + if (port->data_role == TYPEC_DEVICE && + port->nr_snk_vdo) { + for (i = 0; i < port->nr_snk_vdo; i++) + response[i + 1] = port->snk_vdo[i]; + rlen = port->nr_snk_vdo + 1; + } + break; + case CMD_DISCOVER_SVID: + break; + case CMD_DISCOVER_MODES: + break; + case CMD_ENTER_MODE: + break; + case CMD_EXIT_MODE: + break; + case CMD_ATTENTION: + /* Attention command does not have response */ + typec_altmode_attention(adev, p[1]); + return 0; + default: + break; + } + if (rlen >= 1) { + response[0] = p[0] | VDO_CMDT(CMDT_RSP_ACK); + } else if (rlen == 0) { + response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK); + rlen = 1; + } else { + response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY); + rlen = 1; + } + break; + case CMDT_RSP_ACK: + /* silently drop message if we are not connected */ + if (IS_ERR_OR_NULL(port->partner)) + break; + + switch (cmd) { + case CMD_DISCOVER_IDENT: + /* 6.4.4.3.1 */ + svdm_consume_identity(port, payload, cnt); + response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID); + rlen = 1; + break; + case CMD_DISCOVER_SVID: + /* 6.4.4.3.2 */ + if (svdm_consume_svids(port, payload, cnt)) { + response[0] = VDO(USB_SID_PD, 1, + CMD_DISCOVER_SVID); + rlen = 1; + } else if (modep->nsvids && supports_modal(port)) { + response[0] = VDO(modep->svids[0], 1, + CMD_DISCOVER_MODES); + rlen = 1; + } + break; + case CMD_DISCOVER_MODES: + /* 6.4.4.3.3 */ + svdm_consume_modes(port, payload, cnt); + modep->svid_index++; + if (modep->svid_index < modep->nsvids) { + u16 svid = modep->svids[modep->svid_index]; + response[0] = VDO(svid, 1, CMD_DISCOVER_MODES); + rlen = 1; + } else { + tcpm_register_partner_altmodes(port); + } + break; + case CMD_ENTER_MODE: + typec_altmode_update_active(pdev, true); + + if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) { + response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE); + response[0] |= VDO_OPOS(adev->mode); + return 1; + } + return 0; + case CMD_EXIT_MODE: + typec_altmode_update_active(pdev, false); + + /* Back to USB Operation */ + WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, + NULL)); + break; + default: + break; + } + break; + case CMDT_RSP_NAK: + switch (cmd) { + case CMD_ENTER_MODE: + /* Back to USB Operation */ + WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, + NULL)); + break; + default: + break; + } + break; + default: + break; + } + + /* Informing the alternate mode drivers about everything */ + typec_altmode_vdm(adev, p[0], &p[1], cnt); + + return rlen; +} + +static void tcpm_handle_vdm_request(struct tcpm_port *port, + const __le32 *payload, int cnt) +{ + int rlen = 0; + u32 response[8] = { }; + u32 p0 = le32_to_cpu(payload[0]); + + if (port->vdm_state == VDM_STATE_BUSY) { + /* If UFP responded busy retry after timeout */ + if (PD_VDO_CMDT(p0) == CMDT_RSP_BUSY) { + port->vdm_state = VDM_STATE_WAIT_RSP_BUSY; + port->vdo_retry = (p0 & ~VDO_CMDT_MASK) | + CMDT_INIT; + mod_delayed_work(port->wq, &port->vdm_state_machine, + msecs_to_jiffies(PD_T_VDM_BUSY)); + return; + } + port->vdm_state = VDM_STATE_DONE; + } + + if (PD_VDO_SVDM(p0)) + rlen = tcpm_pd_svdm(port, payload, cnt, response); + + if (rlen > 0) { + tcpm_queue_vdm(port, response[0], &response[1], rlen - 1); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + } +} + +static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd, + const u32 *data, int count) +{ + u32 header; + + if (WARN_ON(count > VDO_MAX_SIZE - 1)) + count = VDO_MAX_SIZE - 1; + + /* set VDM header with VID & CMD */ + header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ? + 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd); + tcpm_queue_vdm(port, header, data, count); + + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); +} + +static unsigned int vdm_ready_timeout(u32 vdm_hdr) +{ + unsigned int timeout; + int cmd = PD_VDO_CMD(vdm_hdr); + + /* its not a structured VDM command */ + if (!PD_VDO_SVDM(vdm_hdr)) + return PD_T_VDM_UNSTRUCTURED; + + switch (PD_VDO_CMDT(vdm_hdr)) { + case CMDT_INIT: + if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) + timeout = PD_T_VDM_WAIT_MODE_E; + else + timeout = PD_T_VDM_SNDR_RSP; + break; + default: + if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE) + timeout = PD_T_VDM_E_MODE; + else + timeout = PD_T_VDM_RCVR_RSP; + break; + } + return timeout; +} + +static void vdm_run_state_machine(struct tcpm_port *port) +{ + struct pd_message msg; + int i, res; + + switch (port->vdm_state) { + case VDM_STATE_READY: + /* Only transmit VDM if attached */ + if (!port->attached) { + port->vdm_state = VDM_STATE_ERR_BUSY; + break; + } + + /* + * if there's traffic or we're not in PDO ready state don't send + * a VDM. + */ + if (port->state != SRC_READY && port->state != SNK_READY) + break; + + /* Prepare and send VDM */ + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, + port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, port->vdo_count); + for (i = 0; i < port->vdo_count; i++) + msg.payload[i] = cpu_to_le32(port->vdo_data[i]); + res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); + if (res < 0) { + port->vdm_state = VDM_STATE_ERR_SEND; + } else { + unsigned long timeout; + + port->vdm_retries = 0; + port->vdm_state = VDM_STATE_BUSY; + timeout = vdm_ready_timeout(port->vdo_data[0]); + mod_delayed_work(port->wq, &port->vdm_state_machine, + timeout); + } + break; + case VDM_STATE_WAIT_RSP_BUSY: + port->vdo_data[0] = port->vdo_retry; + port->vdo_count = 1; + port->vdm_state = VDM_STATE_READY; + break; + case VDM_STATE_BUSY: + port->vdm_state = VDM_STATE_ERR_TMOUT; + break; + case VDM_STATE_ERR_SEND: + /* + * A partner which does not support USB PD will not reply, + * so this is not a fatal error. At the same time, some + * devices may not return GoodCRC under some circumstances, + * so we need to retry. + */ + if (port->vdm_retries < 3) { + tcpm_log(port, "VDM Tx error, retry"); + port->vdm_retries++; + port->vdm_state = VDM_STATE_READY; + } + break; + default: + break; + } +} + +static void vdm_state_machine_work(struct work_struct *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, + vdm_state_machine.work); + enum vdm_states prev_state; + + mutex_lock(&port->lock); + + /* + * Continue running as long as the port is not busy and there was + * a state change. + */ + do { + prev_state = port->vdm_state; + vdm_run_state_machine(port); + } while (port->vdm_state != prev_state && + port->vdm_state != VDM_STATE_BUSY); + + mutex_unlock(&port->lock); +} + +enum pdo_err { + PDO_NO_ERR, + PDO_ERR_NO_VSAFE5V, + PDO_ERR_VSAFE5V_NOT_FIRST, + PDO_ERR_PDO_TYPE_NOT_IN_ORDER, + 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[] = { + [PDO_ERR_NO_VSAFE5V] = + " err: source/sink caps should atleast have vSafe5V", + [PDO_ERR_VSAFE5V_NOT_FIRST] = + " err: vSafe5V Fixed Supply Object Shall always be the first object", + [PDO_ERR_PDO_TYPE_NOT_IN_ORDER] = + " err: PDOs should be in the following order: Fixed; Battery; Variable", + [PDO_ERR_FIXED_NOT_SORTED] = + " err: Fixed supply pdos should be in increasing order of their fixed voltage", + [PDO_ERR_VARIABLE_BATT_NOT_SORTED] = + " 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, + unsigned int nr_pdo) +{ + unsigned int i; + + /* Should at least contain vSafe5v */ + if (nr_pdo < 1) + return PDO_ERR_NO_VSAFE5V; + + /* The vSafe5V Fixed Supply Object Shall always be the first object */ + if (pdo_type(pdo[0]) != PDO_TYPE_FIXED || + pdo_fixed_voltage(pdo[0]) != VSAFE5V) + return PDO_ERR_VSAFE5V_NOT_FIRST; + + for (i = 1; i < nr_pdo; i++) { + if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) { + return PDO_ERR_PDO_TYPE_NOT_IN_ORDER; + } else if (pdo_type(pdo[i]) == pdo_type(pdo[i - 1])) { + enum pd_pdo_type type = pdo_type(pdo[i]); + + switch (type) { + /* + * The remaining Fixed Supply Objects, if + * present, shall be sent in voltage order; + * lowest to highest. + */ + case PDO_TYPE_FIXED: + if (pdo_fixed_voltage(pdo[i]) <= + pdo_fixed_voltage(pdo[i - 1])) + return PDO_ERR_FIXED_NOT_SORTED; + break; + /* + * The Battery Supply Objects and Variable + * supply, if present shall be sent in Minimum + * Voltage order; lowest to highest. + */ + case PDO_TYPE_VAR: + case PDO_TYPE_BATT: + if (pdo_min_voltage(pdo[i]) < + pdo_min_voltage(pdo[i - 1])) + return PDO_ERR_VARIABLE_BATT_NOT_SORTED; + else if ((pdo_min_voltage(pdo[i]) == + pdo_min_voltage(pdo[i - 1])) && + (pdo_max_voltage(pdo[i]) == + 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"); + } + } + } + + return PDO_NO_ERR; +} + +static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo) +{ + enum pdo_err err_index = tcpm_caps_err(port, pdo, nr_pdo); + + if (err_index != PDO_NO_ERR) { + tcpm_log_force(port, " %s", pdo_err_msg[err_index]); + return -EINVAL; + } + + return 0; +} + +static int tcpm_altmode_enter(struct typec_altmode *altmode) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + u32 header; + + mutex_lock(&port->lock); + header = VDO(altmode->svid, 1, CMD_ENTER_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm(port, header, NULL, 0); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + mutex_unlock(&port->lock); + + return 0; +} + +static int tcpm_altmode_exit(struct typec_altmode *altmode) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + u32 header; + + mutex_lock(&port->lock); + header = VDO(altmode->svid, 1, CMD_EXIT_MODE); + header |= VDO_OPOS(altmode->mode); + + tcpm_queue_vdm(port, header, NULL, 0); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + mutex_unlock(&port->lock); + + return 0; +} + +static int tcpm_altmode_vdm(struct typec_altmode *altmode, + u32 header, const u32 *data, int count) +{ + struct tcpm_port *port = typec_altmode_get_drvdata(altmode); + + mutex_lock(&port->lock); + tcpm_queue_vdm(port, header, data, count - 1); + mod_delayed_work(port->wq, &port->vdm_state_machine, 0); + mutex_unlock(&port->lock); + + return 0; +} + +static const struct typec_altmode_ops tcpm_altmode_ops = { + .enter = tcpm_altmode_enter, + .exit = tcpm_altmode_exit, + .vdm = tcpm_altmode_vdm, +}; + +/* + * PD (data, control) command handling functions + */ +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) +{ + 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) { + case PD_DATA_SOURCE_CAP: + if (port->pwr_role != TYPEC_SINK) + break; + + for (i = 0; i < cnt; i++) + port->source_caps[i] = le32_to_cpu(msg->payload[i]); + + port->nr_source_caps = cnt; + + tcpm_log_source_caps(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 + * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2. + * However, at the same time, we must be ready to + * receive this message and respond to it 15ms after + * receiving PS_RDY during power swap operations, no matter + * if VBUS is available or not (USB PD specification, + * section 6.5.9.2). + * So we need to accept the message either way, + * but be prepared to keep waiting for VBUS after it was + * handled. + */ + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); + break; + case PD_DATA_REQUEST: + if (port->pwr_role != TYPEC_SOURCE || + cnt != 1) { + 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; + case PD_DATA_SINK_CAP: + /* We don't do anything with this at the moment... */ + for (i = 0; i < cnt; i++) + port->sink_caps[i] = le32_to_cpu(msg->payload[i]); + port->nr_sink_caps = cnt; + break; + case PD_DATA_VENDOR_DEF: + tcpm_handle_vdm_request(port, msg->payload, cnt); + break; + case PD_DATA_BIST: + if (port->state == SRC_READY || port->state == SNK_READY) { + port->bist_request = le32_to_cpu(msg->payload[0]); + 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; + } +} + +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) +{ + enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); + enum tcpm_state next_state; + + switch (type) { + case PD_CTRL_GOOD_CRC: + case PD_CTRL_PING: + break; + case PD_CTRL_GET_SOURCE_CAP: + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_queue_message(port, PD_MSG_DATA_SOURCE_CAP); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + break; + case PD_CTRL_GET_SINK_CAP: + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_queue_message(port, PD_MSG_DATA_SINK_CAP); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + break; + case PD_CTRL_GOTO_MIN: + break; + case PD_CTRL_PS_RDY: + switch (port->state) { + case SNK_TRANSITION_SINK: + if (port->vbus_present) { + tcpm_set_current_limit(port, + port->current_limit, + port->supply_voltage); + port->explicit_contract = true; + tcpm_set_state(port, SNK_READY, 0); + } else { + /* + * Seen after power swap. Keep waiting for VBUS + * in a transitional state. + */ + tcpm_set_state(port, + SNK_TRANSITION_SINK_VBUS, 0); + } + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: + tcpm_set_state(port, PR_SWAP_SRC_SNK_SINK_ON, 0); + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON, 0); + break; + case VCONN_SWAP_WAIT_FOR_VCONN: + tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0); + break; + default: + break; + } + 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 */ + if (port->explicit_contract) + next_state = SNK_READY; + else + 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); + tcpm_set_state(port, DR_SWAP_CANCEL, 0); + break; + case PR_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, PR_SWAP_CANCEL, 0); + break; + case VCONN_SWAP_SEND: + port->swap_status = (type == PD_CTRL_WAIT ? + -EAGAIN : -EOPNOTSUPP); + tcpm_set_state(port, VCONN_SWAP_CANCEL, 0); + break; + default: + break; + } + break; + 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: + port->message_id = 0; + port->rx_msgid = -1; + if (port->pwr_role == TYPEC_SOURCE) + next_state = SRC_SEND_CAPABILITIES; + else + next_state = SNK_WAIT_CAPABILITIES; + tcpm_set_state(port, next_state, 0); + break; + case DR_SWAP_SEND: + tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0); + break; + case PR_SWAP_SEND: + tcpm_set_state(port, PR_SWAP_START, 0); + break; + case VCONN_SWAP_SEND: + tcpm_set_state(port, VCONN_SWAP_START, 0); + break; + default: + break; + } + break; + case PD_CTRL_SOFT_RESET: + tcpm_set_state(port, SOFT_RESET, 0); + break; + case PD_CTRL_DR_SWAP: + if (port->port_type != TYPEC_PORT_DRP) { + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + /* + * XXX + * 6.3.9: If an alternate mode is active, a request to swap + * alternate modes shall trigger a port reset. + */ + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, DR_SWAP_ACCEPT, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + break; + case PD_CTRL_PR_SWAP: + if (port->port_type != TYPEC_PORT_DRP) { + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); + break; + } + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, PR_SWAP_ACCEPT, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + break; + } + break; + case PD_CTRL_VCONN_SWAP: + switch (port->state) { + case SRC_READY: + case SNK_READY: + tcpm_set_state(port, VCONN_SWAP_ACCEPT, 0); + break; + default: + tcpm_queue_message(port, PD_MSG_CTRL_WAIT); + 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, + struct pd_rx_event, work); + const struct pd_message *msg = &event->msg; + unsigned int cnt = pd_header_cnt_le(msg->header); + struct tcpm_port *port = event->port; + + mutex_lock(&port->lock); + + tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header), + port->attached); + + if (port->attached) { + enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); + unsigned int msgid = pd_header_msgid_le(msg->header); + + /* + * USB PD standard, 6.6.1.2: + * "... if MessageID value in a received Message is the + * same as the stored value, the receiver shall return a + * GoodCRC Message with that MessageID value and drop + * the Message (this is a retry of an already received + * Message). Note: this shall not apply to the Soft_Reset + * Message which always has a MessageID value of zero." + */ + if (msgid == port->rx_msgid && type != PD_CTRL_SOFT_RESET) + goto done; + port->rx_msgid = msgid; + + /* + * If both ends believe to be DFP/host, we have a data role + * mismatch. + */ + if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) == + (port->data_role == TYPEC_HOST)) { + tcpm_log(port, + "Data role mismatch, initiating error recovery"); + tcpm_set_state(port, ERROR_RECOVERY, 0); + } else { + 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); + } + } + +done: + mutex_unlock(&port->lock); + kfree(event); +} + +void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg) +{ + struct pd_rx_event *event; + + event = kzalloc(sizeof(*event), GFP_ATOMIC); + if (!event) + return; + + INIT_WORK(&event->work, tcpm_pd_rx_handler); + event->port = port; + memcpy(&event->msg, msg, sizeof(*msg)); + queue_work(port->wq, &event->work); +} +EXPORT_SYMBOL_GPL(tcpm_pd_receive); + +static int tcpm_pd_send_control(struct tcpm_port *port, + enum pd_ctrl_msg_type type) +{ + struct pd_message msg; + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER_LE(type, port->pwr_role, + port->data_role, + port->negotiated_rev, + port->message_id, 0); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +/* + * Send queued message without affecting state. + * Return true if state machine should go back to sleep, + * false otherwise. + */ +static bool tcpm_send_queued_message(struct tcpm_port *port) +{ + enum pd_msg_request queued_message; + + do { + queued_message = port->queued_message; + port->queued_message = PD_MSG_NONE; + + switch (queued_message) { + case PD_MSG_CTRL_WAIT: + tcpm_pd_send_control(port, PD_CTRL_WAIT); + break; + 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; + case PD_MSG_DATA_SOURCE_CAP: + tcpm_pd_send_source_caps(port); + break; + default: + break; + } + } while (port->queued_message != PD_MSG_NONE); + + if (port->delayed_state != INVALID_STATE) { + if (time_is_after_jiffies(port->delayed_runtime)) { + mod_delayed_work(port->wq, &port->state_machine, + port->delayed_runtime - jiffies); + return true; + } + port->delayed_state = INVALID_STATE; + } + return false; +} + +static int tcpm_pd_check_request(struct tcpm_port *port) +{ + u32 pdo, rdo = port->sink_request; + unsigned int max, op, pdo_max, index; + enum pd_pdo_type type; + + index = rdo_index(rdo); + if (!index || index > port->nr_src_pdo) + return -EINVAL; + + pdo = port->src_pdo[index - 1]; + type = pdo_type(pdo); + switch (type) { + case PDO_TYPE_FIXED: + case PDO_TYPE_VAR: + max = rdo_max_current(rdo); + op = rdo_op_current(rdo); + pdo_max = pdo_max_current(pdo); + + if (op > pdo_max) + return -EINVAL; + if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) + return -EINVAL; + + if (type == PDO_TYPE_FIXED) + tcpm_log(port, + "Requested %u mV, %u mA for %u / %u mA", + pdo_fixed_voltage(pdo), pdo_max, op, max); + else + tcpm_log(port, + "Requested %u -> %u mV, %u mA for %u / %u mA", + pdo_min_voltage(pdo), pdo_max_voltage(pdo), + pdo_max, op, max); + break; + case PDO_TYPE_BATT: + max = rdo_max_power(rdo); + op = rdo_op_power(rdo); + pdo_max = pdo_max_power(pdo); + + if (op > pdo_max) + return -EINVAL; + if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH)) + return -EINVAL; + tcpm_log(port, + "Requested %u -> %u mV, %u mW for %u / %u mW", + pdo_min_voltage(pdo), pdo_max_voltage(pdo), + pdo_max, op, max); + break; + default: + return -EINVAL; + } + + port->op_vsafe5v = index == 1; + + return 0; +} + +#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, 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; + + port->pps_data.supported = false; + port->usb_type = POWER_SUPPLY_USB_TYPE_PD; + + /* + * 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); + + switch (type) { + case PDO_TYPE_FIXED: + max_src_mv = pdo_fixed_voltage(pdo); + min_src_mv = max_src_mv; + 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; + port->usb_type = + POWER_SUPPLY_USB_TYPE_PD_PPS; + } + continue; + default: + tcpm_log(port, "Invalid source PDO type, ignoring"); + continue; + } + + 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]; + + switch (pdo_type(pdo)) { + case PDO_TYPE_FIXED: + max_snk_mv = pdo_fixed_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 && + 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; + } + } + } + } + + 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 = 1; 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; + unsigned int max_ma, max_mw; + enum pd_pdo_type type; + 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); + 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); + + switch (type) { + case PDO_TYPE_FIXED: + mv = pdo_fixed_voltage(pdo); + 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) { + mw = min_power(pdo, matching_snk_pdo); + ma = 1000 * mw / mv; + } else { + ma = min_current(pdo, matching_snk_pdo); + mw = ma * mv / 1000; + } + + flags = RDO_USB_COMM | RDO_NO_SUSPEND; + + /* Set mismatch bit if offered power is less than operating power */ + max_ma = ma; + max_mw = mw; + if (mw < port->operating_snk_mw) { + flags |= RDO_CAP_MISMATCH; + 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", + port->cc_req, port->cc1, port->cc2, port->vbus_source, + port->vconn_role == TYPEC_SOURCE ? "source" : "sink", + port->polarity); + + if (type == PDO_TYPE_BATT) { + *rdo = RDO_BATT(src_pdo_index + 1, mw, max_mw, flags); + + tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s", + src_pdo_index, mv, mw, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } else { + *rdo = RDO_FIXED(src_pdo_index + 1, ma, max_ma, flags); + + tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s", + src_pdo_index, mv, ma, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } + + port->current_limit = ma; + port->supply_voltage = mv; + + return 0; +} + +static int tcpm_pd_send_request(struct tcpm_port *port) +{ + struct pd_message msg; + int ret; + u32 rdo; + + ret = tcpm_pd_build_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); + + 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); + + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); +} + +static int tcpm_set_vbus(struct tcpm_port *port, bool enable) +{ + int ret; + + if (enable && port->vbus_charge) + return -EINVAL; + + tcpm_log(port, "vbus:=%d charge=%d", enable, port->vbus_charge); + + ret = port->tcpc->set_vbus(port->tcpc, enable, port->vbus_charge); + if (ret < 0) + return ret; + + port->vbus_source = enable; + return 0; +} + +static int tcpm_set_charge(struct tcpm_port *port, bool charge) +{ + int ret; + + if (charge && port->vbus_source) + return -EINVAL; + + if (charge != port->vbus_charge) { + tcpm_log(port, "vbus=%d charge:=%d", port->vbus_source, charge); + ret = port->tcpc->set_vbus(port->tcpc, port->vbus_source, + charge); + if (ret < 0) + return ret; + } + port->vbus_charge = charge; + return 0; +} + +static bool tcpm_start_drp_toggling(struct tcpm_port *port, + enum typec_cc_status cc) +{ + int ret; + + if (port->tcpc->start_drp_toggling && + port->port_type == TYPEC_PORT_DRP) { + tcpm_log_force(port, "Start DRP toggling"); + ret = port->tcpc->start_drp_toggling(port->tcpc, cc); + if (!ret) + return true; + } + + return false; +} + +static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc) +{ + tcpm_log(port, "cc:=%d", cc); + port->cc_req = cc; + port->tcpc->set_cc(port->tcpc, cc); +} + +static int tcpm_init_vbus(struct tcpm_port *port) +{ + int ret; + + ret = port->tcpc->set_vbus(port->tcpc, false, false); + port->vbus_source = false; + port->vbus_charge = false; + return ret; +} + +static int tcpm_init_vconn(struct tcpm_port *port) +{ + int ret; + + ret = port->tcpc->set_vconn(port->tcpc, false); + port->vconn_role = TYPEC_SINK; + return ret; +} + +static void tcpm_typec_connect(struct tcpm_port *port) +{ + if (!port->connected) { + /* Make sure we don't report stale identity information */ + memset(&port->partner_ident, 0, sizeof(port->partner_ident)); + port->partner_desc.usb_pd = port->pd_capable; + if (tcpm_port_is_debug(port)) + port->partner_desc.accessory = TYPEC_ACCESSORY_DEBUG; + else if (tcpm_port_is_audio(port)) + port->partner_desc.accessory = TYPEC_ACCESSORY_AUDIO; + else + port->partner_desc.accessory = TYPEC_ACCESSORY_NONE; + port->partner = typec_register_partner(port->typec_port, + &port->partner_desc); + port->connected = true; + } +} + +static int tcpm_src_attach(struct tcpm_port *port) +{ + enum typec_cc_polarity polarity = + port->cc2 == TYPEC_CC_RD ? TYPEC_POLARITY_CC2 + : TYPEC_POLARITY_CC1; + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_polarity(port, polarity); + if (ret < 0) + return ret; + + ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); + if (ret < 0) + return ret; + + ret = port->tcpc->set_pd_rx(port->tcpc, true); + if (ret < 0) + goto out_disable_mux; + + /* + * USB Type-C specification, version 1.2, + * chapter 4.5.2.2.8.1 (Attached.SRC Requirements) + * Enable VCONN only if the non-RD port is set to RA. + */ + if ((polarity == TYPEC_POLARITY_CC1 && port->cc2 == TYPEC_CC_RA) || + (polarity == TYPEC_POLARITY_CC2 && port->cc1 == TYPEC_CC_RA)) { + ret = tcpm_set_vconn(port, true); + if (ret < 0) + goto out_disable_pd; + } + + ret = tcpm_set_vbus(port, true); + if (ret < 0) + goto out_disable_vconn; + + port->pd_capable = false; + + port->partner = NULL; + + port->attached = true; + port->send_discover = true; + + return 0; + +out_disable_vconn: + tcpm_set_vconn(port, false); +out_disable_pd: + port->tcpc->set_pd_rx(port->tcpc, false); +out_disable_mux: + tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); + return ret; +} + +static void tcpm_typec_disconnect(struct tcpm_port *port) +{ + if (port->connected) { + typec_unregister_partner(port->partner); + port->partner = NULL; + port->connected = false; + } +} + +static void tcpm_unregister_altmodes(struct tcpm_port *port) +{ + struct pd_mode_data *modep = &port->mode_data; + int i; + + for (i = 0; i < modep->altmodes; i++) { + typec_unregister_altmode(port->partner_altmode[i]); + port->partner_altmode[i] = NULL; + } + + memset(modep, 0, sizeof(*modep)); +} + +static void tcpm_reset_port(struct tcpm_port *port) +{ + tcpm_unregister_altmodes(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 + * we can check tcpm_pd_rx_handler() if we had seen it before. + */ + port->rx_msgid = -1; + + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_init_vbus(port); /* also disables charging */ + tcpm_init_vconn(port); + tcpm_set_current_limit(port, 0, 0); + tcpm_set_polarity(port, TYPEC_POLARITY_CC1); + tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE, + TYPEC_ORIENTATION_NONE); + tcpm_set_attached_state(port, false); + port->try_src_count = 0; + port->try_snk_count = 0; + port->usb_type = POWER_SUPPLY_USB_TYPE_C; + + power_supply_changed(port->psy); +} + +static void tcpm_detach(struct tcpm_port *port) +{ + if (!port->attached) + return; + + if (tcpm_port_is_disconnected(port)) + port->hard_reset_count = 0; + + tcpm_reset_port(port); +} + +static void tcpm_src_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static int tcpm_snk_attach(struct tcpm_port *port) +{ + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ? + TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1); + if (ret < 0) + return ret; + + ret = tcpm_set_roles(port, true, TYPEC_SINK, TYPEC_DEVICE); + if (ret < 0) + return ret; + + port->pd_capable = false; + + port->partner = NULL; + + port->attached = true; + port->send_discover = true; + + return 0; +} + +static void tcpm_snk_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static int tcpm_acc_attach(struct tcpm_port *port) +{ + int ret; + + if (port->attached) + return 0; + + ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST); + if (ret < 0) + return ret; + + port->partner = NULL; + + tcpm_typec_connect(port); + + port->attached = true; + + return 0; +} + +static void tcpm_acc_detach(struct tcpm_port *port) +{ + tcpm_detach(port); +} + +static inline enum tcpm_state hard_reset_state(struct tcpm_port *port) +{ + if (port->hard_reset_count < PD_N_HARD_RESET_COUNT) + return HARD_RESET_SEND; + if (port->pd_capable) + return ERROR_RECOVERY; + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + if (port->state == SNK_WAIT_CAPABILITIES) + return SNK_READY; + return SNK_UNATTACHED; +} + +static inline enum tcpm_state unattached_state(struct tcpm_port *port) +{ + if (port->port_type == TYPEC_PORT_DRP) { + if (port->pwr_role == TYPEC_SOURCE) + return SRC_UNATTACHED; + else + return SNK_UNATTACHED; + } else if (port->port_type == TYPEC_PORT_SRC) { + return SRC_UNATTACHED; + } + + return SNK_UNATTACHED; +} + +static void tcpm_check_send_discover(struct tcpm_port *port) +{ + if (port->data_role == TYPEC_HOST && port->send_discover && + port->pd_capable) { + tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0); + port->send_discover = false; + } +} + +static void tcpm_swap_complete(struct tcpm_port *port, int result) +{ + if (port->swap_pending) { + port->swap_status = result; + port->swap_pending = false; + port->non_pd_role_swap = false; + complete(&port->swap_complete); + } +} + +static enum typec_pwr_opmode tcpm_get_pwr_opmode(enum typec_cc_status cc) +{ + switch (cc) { + case TYPEC_CC_RP_1_5: + return TYPEC_PWR_MODE_1_5A; + case TYPEC_CC_RP_3_0: + return TYPEC_PWR_MODE_3_0A; + case TYPEC_CC_RP_DEF: + default: + return TYPEC_PWR_MODE_USB; + } +} + +static void run_state_machine(struct tcpm_port *port) +{ + int ret; + enum typec_pwr_opmode opmode; + unsigned int msecs; + + port->enter_state = port->state; + switch (port->state) { + case DRP_TOGGLING: + break; + /* SRC states */ + case SRC_UNATTACHED: + if (!port->non_pd_role_swap) + tcpm_swap_complete(port, -ENOTCONN); + tcpm_src_detach(port); + if (tcpm_start_drp_toggling(port, tcpm_rp_cc(port))) { + tcpm_set_state(port, DRP_TOGGLING, 0); + break; + } + tcpm_set_cc(port, tcpm_rp_cc(port)); + if (port->port_type == TYPEC_PORT_DRP) + tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_debug(port)) + tcpm_set_state(port, DEBUG_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_source(port)) + tcpm_set_state(port, + tcpm_try_snk(port) ? SNK_TRY + : SRC_ATTACHED, + PD_T_CC_DEBOUNCE); + break; + + case SNK_TRY: + port->try_snk_count++; + /* + * Requirements: + * - Do not drive vconn or vbus + * - Terminate CC pins (both) to Rd + * Action: + * - Wait for tDRPTry (PD_T_DRP_TRY). + * Until then, ignore any state changes. + */ + tcpm_set_cc(port, TYPEC_CC_RD); + tcpm_set_state(port, SNK_TRY_WAIT, PD_T_DRP_TRY); + break; + case SNK_TRY_WAIT: + if (tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE, 0); + } else { + tcpm_set_state(port, SRC_TRYWAIT, 0); + port->max_wait = 0; + } + break; + case SNK_TRY_WAIT_DEBOUNCE: + tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, + PD_T_PD_DEBOUNCE); + break; + case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS: + if (port->vbus_present && tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_ATTACHED, 0); + } else { + tcpm_set_state(port, SRC_TRYWAIT, 0); + port->max_wait = 0; + } + break; + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + if (port->max_wait == 0) { + port->max_wait = jiffies + + msecs_to_jiffies(PD_T_DRP_TRY); + tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, + PD_T_DRP_TRY); + } else { + if (time_is_after_jiffies(port->max_wait)) + tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, + jiffies_to_msecs(port->max_wait - + jiffies)); + else + tcpm_set_state(port, SNK_UNATTACHED, 0); + } + break; + case SRC_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE); + break; + case SRC_TRYWAIT_UNATTACHED: + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + + case SRC_ATTACHED: + ret = tcpm_src_attach(port); + tcpm_set_state(port, SRC_UNATTACHED, + ret < 0 ? 0 : PD_T_PS_SOURCE_ON); + break; + case SRC_STARTUP: + opmode = tcpm_get_pwr_opmode(tcpm_rp_cc(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; + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + break; + case SRC_SEND_CAPABILITIES: + port->caps_count++; + if (port->caps_count > PD_N_CAPS_COUNT) { + tcpm_set_state(port, SRC_READY, 0); + break; + } + ret = tcpm_pd_send_source_caps(port); + if (ret < 0) { + tcpm_set_state(port, SRC_SEND_CAPABILITIES, + PD_T_SEND_SOURCE_CAP); + } else { + /* + * Per standard, we should clear the reset counter here. + * However, that can result in state machine hang-ups. + * Reset it only in READY state to improve stability. + */ + /* port->hard_reset_count = 0; */ + port->caps_count = 0; + port->pd_capable = true; + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SEND_SOURCE_CAP); + } + break; + case SRC_NEGOTIATE_CAPABILITIES: + ret = tcpm_pd_check_request(port); + if (ret < 0) { + tcpm_pd_send_control(port, PD_CTRL_REJECT); + if (!port->explicit_contract) { + tcpm_set_state(port, + SRC_WAIT_NEW_CAPABILITIES, 0); + } else { + tcpm_set_state(port, SRC_READY, 0); + } + } else { + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state(port, SRC_TRANSITION_SUPPLY, + PD_T_SRC_TRANSITION); + } + break; + case SRC_TRANSITION_SUPPLY: + /* XXX: regulator_set_voltage(vbus, ...) */ + tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + port->explicit_contract = true; + typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD); + port->pwr_opmode = TYPEC_PWR_MODE_PD; + tcpm_set_state_cond(port, SRC_READY, 0); + break; + case SRC_READY: +#if 1 + port->hard_reset_count = 0; +#endif + port->try_src_count = 0; + + tcpm_swap_complete(port, 0); + tcpm_typec_connect(port); + + tcpm_check_send_discover(port); + /* + * 6.3.5 + * Sending ping messages is not necessary if + * - the source operates at vSafe5V + * or + * - The system is not operating in PD mode + * or + * - Both partners are connected using a Type-C connector + * + * There is no actual need to send PD messages since the local + * port type-c and the spec does not clearly say whether PD is + * possible when type-c is connected to Type-A/B + */ + break; + case SRC_WAIT_NEW_CAPABILITIES: + /* Nothing to do... */ + break; + + /* SNK states */ + 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, TYPEC_CC_RD)) { + tcpm_set_state(port, DRP_TOGGLING, 0); + break; + } + tcpm_set_cc(port, TYPEC_CC_RD); + if (port->port_type == TYPEC_PORT_DRP) + tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC); + break; + case SNK_ATTACH_WAIT: + if ((port->cc1 == TYPEC_CC_OPEN && + port->cc2 != TYPEC_CC_OPEN) || + (port->cc1 != TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN)) + tcpm_set_state(port, SNK_DEBOUNCED, + PD_T_CC_DEBOUNCE); + else if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_PD_DEBOUNCE); + break; + case SNK_DEBOUNCED: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_PD_DEBOUNCE); + else if (port->vbus_present) + tcpm_set_state(port, + tcpm_try_src(port) ? SRC_TRY + : SNK_ATTACHED, + 0); + else + /* Wait for VBUS, but not forever */ + tcpm_set_state(port, PORT_RESET, PD_T_PS_SOURCE_ON); + break; + + case SRC_TRY: + port->try_src_count++; + tcpm_set_cc(port, tcpm_rp_cc(port)); + port->max_wait = 0; + tcpm_set_state(port, SRC_TRY_WAIT, 0); + break; + case SRC_TRY_WAIT: + if (port->max_wait == 0) { + port->max_wait = jiffies + + msecs_to_jiffies(PD_T_DRP_TRY); + msecs = PD_T_DRP_TRY; + } else { + if (time_is_after_jiffies(port->max_wait)) + msecs = jiffies_to_msecs(port->max_wait - + jiffies); + else + msecs = 0; + } + tcpm_set_state(port, SNK_TRYWAIT, msecs); + break; + case SRC_TRY_DEBOUNCE: + tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE); + break; + case SNK_TRYWAIT: + tcpm_set_cc(port, TYPEC_CC_RD); + tcpm_set_state(port, SNK_TRYWAIT_VBUS, PD_T_CC_DEBOUNCE); + break; + case SNK_TRYWAIT_VBUS: + /* + * TCPM stays in this state indefinitely until VBUS + * is detected as long as Rp is not detected for + * more than a time period of tPDDebounce. + */ + if (port->vbus_present && tcpm_port_is_sink(port)) { + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + } + if (!tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SNK_UNATTACHED, PD_T_PD_DEBOUNCE); + break; + case SNK_ATTACHED: + ret = tcpm_snk_attach(port); + if (ret < 0) + tcpm_set_state(port, SNK_UNATTACHED, 0); + else + tcpm_set_state(port, SNK_STARTUP, 0); + break; + case SNK_STARTUP: + opmode = tcpm_get_pwr_opmode(port->polarity ? + 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; + tcpm_set_state(port, SNK_DISCOVERY, 0); + break; + case SNK_DISCOVERY: + if (port->vbus_present) { + tcpm_set_current_limit(port, + tcpm_get_current_limit(port), + 5000); + tcpm_set_charge(port, true); + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + break; + } + /* + * For DRP, timeouts differ. Also, handling is supposed to be + * different and much more complex (dead battery detection; + * see USB power delivery specification, section 8.3.3.6.1.5.1). + */ + tcpm_set_state(port, hard_reset_state(port), + port->port_type == TYPEC_PORT_DRP ? + PD_T_DB_DETECT : PD_T_NO_RESPONSE); + break; + case SNK_DISCOVERY_DEBOUNCE: + tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE, + PD_T_CC_DEBOUNCE); + break; + case SNK_DISCOVERY_DEBOUNCE_DONE: + if (!tcpm_port_is_disconnected(port) && + tcpm_port_is_sink(port) && + time_is_after_jiffies(port->delayed_runtime)) { + tcpm_set_state(port, SNK_DISCOVERY, + jiffies_to_msecs(port->delayed_runtime - + jiffies)); + break; + } + tcpm_set_state(port, unattached_state(port), 0); + break; + case SNK_WAIT_CAPABILITIES: + ret = port->tcpc->set_pd_rx(port->tcpc, true); + if (ret < 0) { + tcpm_set_state(port, SNK_READY, 0); + break; + } + /* + * If VBUS has never been low, and we time out waiting + * for source cap, try a soft reset first, in case we + * were already in a stable contract before this boot. + * Do this only once. + */ + if (port->vbus_never_low) { + port->vbus_never_low = false; + tcpm_set_state(port, SOFT_RESET_SEND, + PD_T_SINK_WAIT_CAP); + } else { + tcpm_set_state(port, hard_reset_state(port), + PD_T_SINK_WAIT_CAP); + } + break; + case SNK_NEGOTIATE_CAPABILITIES: + port->pd_capable = true; + port->hard_reset_count = 0; + ret = tcpm_pd_send_request(port); + if (ret < 0) { + /* Let the Source send capabilities again. */ + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + } else { + tcpm_set_state_cond(port, hard_reset_state(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), + PD_T_PS_TRANSITION); + 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); + port->pwr_opmode = TYPEC_PWR_MODE_PD; + } + + tcpm_swap_complete(port, 0); + tcpm_typec_connect(port); + tcpm_check_send_discover(port); + tcpm_pps_complete(port, port->pps_status); + + power_supply_changed(port->psy); + + break; + + /* Accessory states */ + case ACC_UNATTACHED: + tcpm_acc_detach(port); + tcpm_set_state(port, SRC_UNATTACHED, 0); + break; + case DEBUG_ACC_ATTACHED: + case AUDIO_ACC_ATTACHED: + ret = tcpm_acc_attach(port); + if (ret < 0) + tcpm_set_state(port, ACC_UNATTACHED, 0); + break; + case AUDIO_ACC_DEBOUNCE: + tcpm_set_state(port, ACC_UNATTACHED, PD_T_CC_DEBOUNCE); + break; + + /* Hard_Reset states */ + case HARD_RESET_SEND: + tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL); + tcpm_set_state(port, HARD_RESET_START, 0); + break; + case HARD_RESET_START: + port->hard_reset_count++; + port->tcpc->set_pd_rx(port->tcpc, false); + tcpm_unregister_altmodes(port); + port->send_discover = true; + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF, + PD_T_PS_HARD_RESET); + else + tcpm_set_state(port, SNK_HARD_RESET_SINK_OFF, 0); + break; + case SRC_HARD_RESET_VBUS_OFF: + tcpm_set_vconn(port, true); + tcpm_set_vbus(port, false); + tcpm_set_roles(port, false, TYPEC_SOURCE, TYPEC_HOST); + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER); + break; + case SRC_HARD_RESET_VBUS_ON: + tcpm_set_vbus(port, true); + port->tcpc->set_pd_rx(port->tcpc, true); + tcpm_set_attached_state(port, true); + 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); + /* + * VBUS may or may not toggle, depending on the adapter. + * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON + * directly after timeout. + */ + tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V); + break; + case SNK_HARD_RESET_WAIT_VBUS: + /* Assume we're disconnected if VBUS doesn't come back. */ + tcpm_set_state(port, SNK_UNATTACHED, + PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON); + break; + case SNK_HARD_RESET_SINK_ON: + /* Note: There is no guarantee that VBUS is on in this state */ + /* + * XXX: + * The specification suggests that dual mode ports in sink + * mode should transition to state PE_SRC_Transition_to_default. + * See USB power delivery specification chapter 8.3.3.6.1.3. + * This would mean to to + * - turn off VCONN, reset power supply + * - request hardware reset + * - turn on VCONN + * - Transition to state PE_Src_Startup + * SNK only ports shall transition to state Snk_Startup + * (see chapter 8.3.3.3.8). + * Similar, dual-mode ports in source mode should transition + * to PE_SNK_Transition_to_default. + */ + tcpm_set_attached_state(port, true); + tcpm_set_state(port, SNK_STARTUP, 0); + break; + + /* Soft_Reset states */ + case SOFT_RESET: + port->message_id = 0; + port->rx_msgid = -1; + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + else + tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0); + break; + case SOFT_RESET_SEND: + port->message_id = 0; + port->rx_msgid = -1; + if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET)) + tcpm_set_state_cond(port, hard_reset_state(port), 0); + else + tcpm_set_state_cond(port, hard_reset_state(port), + PD_T_SENDER_RESPONSE); + break; + + /* DR_Swap states */ + case DR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_DR_SWAP); + tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case DR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0); + break; + case DR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case DR_SWAP_CHANGE_DR: + if (port->data_role == TYPEC_HOST) { + tcpm_unregister_altmodes(port); + tcpm_set_roles(port, true, port->pwr_role, + TYPEC_DEVICE); + } else { + tcpm_set_roles(port, true, port->pwr_role, + TYPEC_HOST); + port->send_discover = true; + } + tcpm_set_state(port, ready_state(port), 0); + break; + + /* PR_Swap states */ + case PR_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state(port, PR_SWAP_START, 0); + break; + case PR_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_PR_SWAP); + tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case PR_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case PR_SWAP_START: + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, PR_SWAP_SRC_SNK_TRANSITION_OFF, + PD_T_SRC_TRANSITION); + else + tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0); + break; + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + tcpm_set_vbus(port, false); + port->explicit_contract = false; + /* allow time for Vbus discharge, must be < tSrcSwapStdby */ + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, + PD_T_SRCSWAPSTDBY); + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF: + tcpm_set_cc(port, TYPEC_CC_RD); + /* allow CC debounce */ + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED, + PD_T_CC_DEBOUNCE); + break; + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: + /* + * USB-PD standard, 6.2.1.4, Port Power Role: + * "During the Power Role Swap Sequence, for the initial Source + * Port, the Port Power Role field shall be set to Sink in the + * PS_RDY Message indicating that the initial Source’s power + * supply is turned off" + */ + tcpm_set_pwr_role(port, TYPEC_SINK); + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) { + tcpm_set_state(port, ERROR_RECOVERY, 0); + break; + } + tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON); + break; + case PR_SWAP_SRC_SNK_SINK_ON: + tcpm_set_state(port, SNK_STARTUP, 0); + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + tcpm_set_charge(port, false); + tcpm_set_state(port, hard_reset_state(port), + PD_T_PS_SOURCE_OFF); + break; + case PR_SWAP_SNK_SRC_SOURCE_ON: + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_vbus(port, true); + /* + * allow time VBUS ramp-up, must be < tNewSrc + * Also, this window overlaps with CC debounce as well. + * So, Wait for the max of two which is PD_T_NEWSRC + */ + tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP, + PD_T_NEWSRC); + break; + case PR_SWAP_SNK_SRC_SOURCE_ON_VBUS_RAMPED_UP: + /* + * USB PD standard, 6.2.1.4: + * "Subsequent Messages initiated by the Policy Engine, + * such as the PS_RDY Message sent to indicate that Vbus + * is ready, will have the Port Power Role field set to + * Source." + */ + tcpm_set_pwr_role(port, TYPEC_SOURCE); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_set_state(port, SRC_STARTUP, 0); + break; + + case VCONN_SWAP_ACCEPT: + tcpm_pd_send_control(port, PD_CTRL_ACCEPT); + tcpm_set_state(port, VCONN_SWAP_START, 0); + break; + case VCONN_SWAP_SEND: + tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP); + tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT, + PD_T_SENDER_RESPONSE); + break; + case VCONN_SWAP_SEND_TIMEOUT: + tcpm_swap_complete(port, -ETIMEDOUT); + tcpm_set_state(port, ready_state(port), 0); + break; + case VCONN_SWAP_START: + if (port->vconn_role == TYPEC_SOURCE) + tcpm_set_state(port, VCONN_SWAP_WAIT_FOR_VCONN, 0); + else + tcpm_set_state(port, VCONN_SWAP_TURN_ON_VCONN, 0); + break; + case VCONN_SWAP_WAIT_FOR_VCONN: + tcpm_set_state(port, hard_reset_state(port), + PD_T_VCONN_SOURCE_ON); + break; + case VCONN_SWAP_TURN_ON_VCONN: + tcpm_set_vconn(port, true); + tcpm_pd_send_control(port, PD_CTRL_PS_RDY); + tcpm_set_state(port, ready_state(port), 0); + break; + case VCONN_SWAP_TURN_OFF_VCONN: + tcpm_set_vconn(port, false); + tcpm_set_state(port, ready_state(port), 0); + break; + + case DR_SWAP_CANCEL: + case PR_SWAP_CANCEL: + case VCONN_SWAP_CANCEL: + tcpm_swap_complete(port, port->swap_status); + if (port->pwr_role == TYPEC_SOURCE) + tcpm_set_state(port, SRC_READY, 0); + else + tcpm_set_state(port, SNK_READY, 0); + break; + + case BIST_RX: + switch (BDO_MODE_MASK(port->bist_request)) { + case BDO_MODE_CARRIER2: + tcpm_pd_transmit(port, TCPC_TX_BIST_MODE_2, NULL); + break; + default: + break; + } + /* 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); + tcpm_set_state(port, PORT_RESET, 0); + break; + case PORT_RESET: + tcpm_reset_port(port); + tcpm_set_cc(port, TYPEC_CC_OPEN); + tcpm_set_state(port, PORT_RESET_WAIT_OFF, + PD_T_ERROR_RECOVERY); + break; + case PORT_RESET_WAIT_OFF: + tcpm_set_state(port, + tcpm_default_state(port), + port->vbus_present ? PD_T_PS_SOURCE_OFF : 0); + break; + default: + WARN(1, "Unexpected port state %d\n", port->state); + break; + } +} + +static void tcpm_state_machine_work(struct work_struct *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, + state_machine.work); + enum tcpm_state prev_state; + + mutex_lock(&port->lock); + port->state_machine_running = true; + + if (port->queued_message && tcpm_send_queued_message(port)) + goto done; + + /* If we were queued due to a delayed state change, update it now */ + if (port->delayed_state) { + tcpm_log(port, "state change %s -> %s [delayed %ld ms]", + tcpm_states[port->state], + tcpm_states[port->delayed_state], port->delay_ms); + port->prev_state = port->state; + port->state = port->delayed_state; + port->delayed_state = INVALID_STATE; + } + + /* + * Continue running as long as we have (non-delayed) state changes + * to make. + */ + do { + prev_state = port->state; + run_state_machine(port); + if (port->queued_message) + tcpm_send_queued_message(port); + } while (port->state != prev_state && !port->delayed_state); + +done: + port->state_machine_running = false; + mutex_unlock(&port->lock); +} + +static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, + enum typec_cc_status cc2) +{ + enum typec_cc_status old_cc1, old_cc2; + enum tcpm_state new_state; + + old_cc1 = port->cc1; + old_cc2 = port->cc2; + port->cc1 = cc1; + port->cc2 = cc2; + + tcpm_log_force(port, + "CC1: %u -> %u, CC2: %u -> %u [state %s, polarity %d, %s]", + old_cc1, cc1, old_cc2, cc2, tcpm_states[port->state], + port->polarity, + tcpm_port_is_disconnected(port) ? "disconnected" + : "connected"); + + switch (port->state) { + case DRP_TOGGLING: + if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || + tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + else if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SRC_UNATTACHED: + case ACC_UNATTACHED: + if (tcpm_port_is_debug(port) || tcpm_port_is_audio(port) || + tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + break; + case SRC_ATTACH_WAIT: + if (tcpm_port_is_disconnected(port) || + tcpm_port_is_audio_detached(port)) + tcpm_set_state(port, SRC_UNATTACHED, 0); + else if (cc1 != old_cc1 || cc2 != old_cc2) + tcpm_set_state(port, SRC_ATTACH_WAIT, 0); + break; + case SRC_ATTACHED: + case SRC_SEND_CAPABILITIES: + case SRC_READY: + if (tcpm_port_is_disconnected(port) || + !tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_UNATTACHED, 0); + break; + case SNK_UNATTACHED: + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SNK_ATTACH_WAIT: + if ((port->cc1 == TYPEC_CC_OPEN && + port->cc2 != TYPEC_CC_OPEN) || + (port->cc1 != TYPEC_CC_OPEN && + port->cc2 == TYPEC_CC_OPEN)) + new_state = SNK_DEBOUNCED; + else if (tcpm_port_is_disconnected(port)) + new_state = SNK_UNATTACHED; + else + break; + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_ATTACH_WAIT, 0); + break; + case SNK_DEBOUNCED: + if (tcpm_port_is_disconnected(port)) + new_state = SNK_UNATTACHED; + else if (port->vbus_present) + new_state = tcpm_try_src(port) ? SRC_TRY : SNK_ATTACHED; + else + new_state = SNK_UNATTACHED; + if (new_state != port->delayed_state) + tcpm_set_state(port, SNK_DEBOUNCED, 0); + break; + case SNK_READY: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, unattached_state(port), 0); + else if (!port->pd_capable && + (cc1 != old_cc1 || cc2 != old_cc2)) + tcpm_set_current_limit(port, + tcpm_get_current_limit(port), + 5000); + break; + + case AUDIO_ACC_ATTACHED: + if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) + tcpm_set_state(port, AUDIO_ACC_DEBOUNCE, 0); + break; + case AUDIO_ACC_DEBOUNCE: + if (tcpm_port_is_audio(port)) + tcpm_set_state(port, AUDIO_ACC_ATTACHED, 0); + break; + + case DEBUG_ACC_ATTACHED: + if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN) + tcpm_set_state(port, ACC_UNATTACHED, 0); + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + + case SNK_DISCOVERY: + /* CC line is unstable, wait for debounce */ + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE, 0); + break; + case SNK_DISCOVERY_DEBOUNCE: + break; + + case SRC_TRYWAIT: + /* Hand over to state machine if needed */ + if (!port->vbus_present && tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); + break; + case SRC_TRYWAIT_DEBOUNCE: + if (port->vbus_present || !tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE: + if (!tcpm_port_is_sink(port)) { + port->max_wait = 0; + tcpm_set_state(port, SRC_TRYWAIT, 0); + } + break; + case SRC_TRY_WAIT: + if (tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0); + break; + case SRC_TRY_DEBOUNCE: + tcpm_set_state(port, SRC_TRY_WAIT, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0); + break; + case SNK_TRYWAIT_VBUS: + if (!tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0); + break; + case SNK_TRYWAIT: + /* Do nothing, waiting for tCCDebounce */ + break; + case PR_SWAP_SNK_SRC_SINK_OFF: + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + case PR_SWAP_SRC_SNK_SOURCE_OFF: + case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED: + case PR_SWAP_SNK_SRC_SOURCE_ON: + /* + * CC state change is expected in PR_SWAP + * Ignore it. + */ + break; + + default: + if (tcpm_port_is_disconnected(port)) + tcpm_set_state(port, unattached_state(port), 0); + break; + } +} + +static void _tcpm_pd_vbus_on(struct tcpm_port *port) +{ + tcpm_log_force(port, "VBUS on"); + port->vbus_present = true; + switch (port->state) { + case SNK_TRANSITION_SINK_VBUS: + port->explicit_contract = true; + tcpm_set_state(port, SNK_READY, 0); + break; + case SNK_DISCOVERY: + tcpm_set_state(port, SNK_DISCOVERY, 0); + break; + + case SNK_DEBOUNCED: + tcpm_set_state(port, tcpm_try_src(port) ? SRC_TRY + : SNK_ATTACHED, + 0); + break; + case SNK_HARD_RESET_WAIT_VBUS: + tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, 0); + break; + case SRC_ATTACHED: + tcpm_set_state(port, SRC_STARTUP, 0); + break; + case SRC_HARD_RESET_VBUS_ON: + tcpm_set_state(port, SRC_STARTUP, 0); + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + case SRC_TRYWAIT: + /* Do nothing, Waiting for Rd to be detected */ + break; + case SRC_TRYWAIT_DEBOUNCE: + tcpm_set_state(port, SRC_TRYWAIT, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE: + /* Do nothing, waiting for PD_DEBOUNCE to do be done */ + break; + case SNK_TRYWAIT: + /* Do nothing, waiting for tCCDebounce */ + break; + case SNK_TRYWAIT_VBUS: + if (tcpm_port_is_sink(port)) + tcpm_set_state(port, SNK_ATTACHED, 0); + break; + case SNK_TRYWAIT_DEBOUNCE: + /* Do nothing, waiting for Rp */ + break; + case SRC_TRY_WAIT: + case SRC_TRY_DEBOUNCE: + /* Do nothing, waiting for sink detection */ + break; + default: + break; + } +} + +static void _tcpm_pd_vbus_off(struct tcpm_port *port) +{ + tcpm_log_force(port, "VBUS off"); + port->vbus_present = false; + port->vbus_never_low = false; + switch (port->state) { + case SNK_HARD_RESET_SINK_OFF: + tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0); + break; + case SRC_HARD_RESET_VBUS_OFF: + tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, 0); + break; + case HARD_RESET_SEND: + break; + + case SNK_TRY: + /* Do nothing, waiting for timeout */ + break; + case SRC_TRYWAIT: + /* Hand over to state machine if needed */ + if (tcpm_port_is_source(port)) + tcpm_set_state(port, SRC_TRYWAIT_DEBOUNCE, 0); + break; + case SNK_TRY_WAIT_DEBOUNCE: + /* Do nothing, waiting for PD_DEBOUNCE to do be done */ + break; + case SNK_TRYWAIT: + case SNK_TRYWAIT_VBUS: + case SNK_TRYWAIT_DEBOUNCE: + break; + case SNK_ATTACH_WAIT: + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + + case SNK_NEGOTIATE_CAPABILITIES: + break; + + case PR_SWAP_SRC_SNK_TRANSITION_OFF: + tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, 0); + break; + + case PR_SWAP_SNK_SRC_SINK_OFF: + /* Do nothing, expected */ + break; + + case PORT_RESET_WAIT_OFF: + tcpm_set_state(port, tcpm_default_state(port), 0); + break; + case SRC_TRY_WAIT: + case SRC_TRY_DEBOUNCE: + /* Do nothing, waiting for sink detection */ + break; + default: + if (port->pwr_role == TYPEC_SINK && + port->attached) + tcpm_set_state(port, SNK_UNATTACHED, 0); + break; + } +} + +static void _tcpm_pd_hard_reset(struct tcpm_port *port) +{ + tcpm_log_force(port, "Received hard reset"); + /* + * If we keep receiving hard reset requests, executing the hard reset + * must have failed. Revert to error recovery if that happens. + */ + tcpm_set_state(port, + port->hard_reset_count < PD_N_HARD_RESET_COUNT ? + HARD_RESET_START : ERROR_RECOVERY, + 0); +} + +static void tcpm_pd_event_handler(struct work_struct *work) +{ + struct tcpm_port *port = container_of(work, struct tcpm_port, + event_work); + u32 events; + + mutex_lock(&port->lock); + + spin_lock(&port->pd_event_lock); + while (port->pd_events) { + events = port->pd_events; + port->pd_events = 0; + spin_unlock(&port->pd_event_lock); + if (events & TCPM_RESET_EVENT) + _tcpm_pd_hard_reset(port); + if (events & TCPM_VBUS_EVENT) { + bool vbus; + + vbus = port->tcpc->get_vbus(port->tcpc); + if (vbus) + _tcpm_pd_vbus_on(port); + else + _tcpm_pd_vbus_off(port); + } + if (events & TCPM_CC_EVENT) { + enum typec_cc_status cc1, cc2; + + if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) + _tcpm_cc_change(port, cc1, cc2); + } + spin_lock(&port->pd_event_lock); + } + spin_unlock(&port->pd_event_lock); + mutex_unlock(&port->lock); +} + +void tcpm_cc_change(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_CC_EVENT; + spin_unlock(&port->pd_event_lock); + queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_cc_change); + +void tcpm_vbus_change(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events |= TCPM_VBUS_EVENT; + spin_unlock(&port->pd_event_lock); + queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_vbus_change); + +void tcpm_pd_hard_reset(struct tcpm_port *port) +{ + spin_lock(&port->pd_event_lock); + port->pd_events = TCPM_RESET_EVENT; + spin_unlock(&port->pd_event_lock); + queue_work(port->wq, &port->event_work); +} +EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset); + +static int tcpm_dr_set(const struct typec_capability *cap, + enum typec_data_role data) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->port_type != TYPEC_PORT_DRP) { + ret = -EINVAL; + goto port_unlock; + } + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (port->data_role == data) { + ret = 0; + goto port_unlock; + } + + /* + * XXX + * 6.3.9: If an alternate mode is active, a request to swap + * alternate modes shall trigger a port reset. + * Reject data role swap request in this case. + */ + + if (!port->pd_capable) { + /* + * If the partner is not PD capable, reset the port to + * trigger a role change. This can only work if a preferred + * role is configured, and if it matches the requested role. + */ + if (port->try_role == TYPEC_NO_PREFERRED_ROLE || + port->try_role == port->pwr_role) { + ret = -EINVAL; + goto port_unlock; + } + port->non_pd_role_swap = true; + tcpm_set_state(port, PORT_RESET, 0); + } else { + tcpm_set_state(port, DR_SWAP_SEND, 0); + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; + + port->non_pd_role_swap = false; + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_pr_set(const struct typec_capability *cap, + enum typec_role role) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->port_type != TYPEC_PORT_DRP) { + ret = -EINVAL; + goto port_unlock; + } + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (role == port->pwr_role) { + ret = 0; + goto port_unlock; + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + tcpm_set_state(port, PR_SWAP_SEND, 0); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_vconn_set(const struct typec_capability *cap, + enum typec_role role) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + int ret; + + mutex_lock(&port->swap_lock); + mutex_lock(&port->lock); + + if (port->state != SRC_READY && port->state != SNK_READY) { + ret = -EAGAIN; + goto port_unlock; + } + + if (role == port->vconn_role) { + ret = 0; + goto port_unlock; + } + + port->swap_status = 0; + port->swap_pending = true; + reinit_completion(&port->swap_complete); + tcpm_set_state(port, VCONN_SWAP_SEND, 0); + mutex_unlock(&port->lock); + + if (!wait_for_completion_timeout(&port->swap_complete, + msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) + ret = -ETIMEDOUT; + else + ret = port->swap_status; + + goto swap_unlock; + +port_unlock: + mutex_unlock(&port->lock); +swap_unlock: + mutex_unlock(&port->swap_lock); + return ret; +} + +static int tcpm_try_role(const struct typec_capability *cap, int role) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + struct tcpc_dev *tcpc = port->tcpc; + int ret = 0; + + mutex_lock(&port->lock); + if (tcpc->try_role) + ret = tcpc->try_role(tcpc, role); + if (!ret && !tcpc->config->try_role_hw) + port->try_role = role; + port->try_src_count = 0; + port->try_snk_count = 0; + mutex_unlock(&port->lock); + + return ret; +} + +static int 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 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 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; + + port->tcpc->init(port->tcpc); + + tcpm_reset_port(port); + + /* + * XXX + * Should possibly wait for VBUS to settle if it was enabled locally + * since tcpm_reset_port() will disable VBUS. + */ + port->vbus_present = port->tcpc->get_vbus(port->tcpc); + if (port->vbus_present) + port->vbus_never_low = true; + + tcpm_set_state(port, tcpm_default_state(port), 0); + + if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0) + _tcpm_cc_change(port, cc1, cc2); + + /* + * Some adapters need a clean slate at startup, and won't recover + * otherwise. So do not try to be fancy and force a clean disconnect. + */ + tcpm_set_state(port, PORT_RESET, 0); +} + +static int tcpm_port_type_set(const struct typec_capability *cap, + enum typec_port_type type) +{ + struct tcpm_port *port = typec_cap_to_tcpm(cap); + + mutex_lock(&port->lock); + if (type == port->port_type) + goto port_unlock; + + port->port_type = type; + + if (!port->connected) { + tcpm_set_state(port, PORT_RESET, 0); + } else if (type == TYPEC_PORT_SNK) { + if (!(port->pwr_role == TYPEC_SINK && + port->data_role == TYPEC_DEVICE)) + tcpm_set_state(port, PORT_RESET, 0); + } else if (type == TYPEC_PORT_SRC) { + if (!(port->pwr_role == TYPEC_SOURCE && + port->data_role == TYPEC_HOST)) + tcpm_set_state(port, PORT_RESET, 0); + } + +port_unlock: + mutex_unlock(&port->lock); + return 0; +} + +void tcpm_tcpc_reset(struct tcpm_port *port) +{ + mutex_lock(&port->lock); + /* XXX: Maintain PD connection if possible? */ + tcpm_init(port); + mutex_unlock(&port->lock); +} +EXPORT_SYMBOL_GPL(tcpm_tcpc_reset); + +static int tcpm_copy_pdos(u32 *dest_pdo, const u32 *src_pdo, + unsigned int nr_pdo) +{ + unsigned int i; + + if (nr_pdo > PDO_MAX_OBJECTS) + nr_pdo = PDO_MAX_OBJECTS; + + for (i = 0; i < nr_pdo; i++) + dest_pdo[i] = src_pdo[i]; + + return nr_pdo; +} + +static int tcpm_copy_vdos(u32 *dest_vdo, const u32 *src_vdo, + unsigned int nr_vdo) +{ + unsigned int i; + + if (nr_vdo > VDO_MAX_OBJECTS) + nr_vdo = VDO_MAX_OBJECTS; + + for (i = 0; i < nr_vdo; i++) + dest_vdo[i] = src_vdo[i]; + + return nr_vdo; +} + +static int tcpm_fw_get_caps(struct tcpm_port *port, + struct fwnode_handle *fwnode) +{ + const char *cap_str; + int ret; + u32 mw; + + if (!fwnode) + return -EINVAL; + + /* USB data support is optional */ + ret = fwnode_property_read_string(fwnode, "data-role", &cap_str); + if (ret == 0) { + port->typec_caps.data = typec_find_port_data_role(cap_str); + if (port->typec_caps.data < 0) + return -EINVAL; + } + + ret = fwnode_property_read_string(fwnode, "power-role", &cap_str); + if (ret < 0) + return ret; + + port->typec_caps.type = typec_find_port_power_role(cap_str); + if (port->typec_caps.type < 0) + return -EINVAL; + port->port_type = port->typec_caps.type; + + if (port->port_type == TYPEC_PORT_SNK) + goto sink; + + /* Get source pdos */ + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", + NULL, 0); + if (ret <= 0) + return -EINVAL; + + port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "source-pdos", + port->src_pdo, port->nr_src_pdo); + if ((ret < 0) || tcpm_validate_caps(port, port->src_pdo, + port->nr_src_pdo)) + return -EINVAL; + + if (port->port_type == TYPEC_PORT_SRC) + return 0; + + /* Get the preferred power role for DRP */ + ret = fwnode_property_read_string(fwnode, "try-power-role", &cap_str); + if (ret < 0) + return ret; + + port->typec_caps.prefer_role = typec_find_power_role(cap_str); + if (port->typec_caps.prefer_role < 0) + return -EINVAL; +sink: + /* Get sink pdos */ + ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", + NULL, 0); + if (ret <= 0) + return -EINVAL; + + port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS); + ret = fwnode_property_read_u32_array(fwnode, "sink-pdos", + port->snk_pdo, port->nr_snk_pdo); + if ((ret < 0) || tcpm_validate_caps(port, port->snk_pdo, + port->nr_snk_pdo)) + return -EINVAL; + + if (fwnode_property_read_u32(fwnode, "op-sink-microwatt", &mw) < 0) + return -EINVAL; + port->operating_snk_mw = mw / 1000; + + return 0; +} + +int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo, + unsigned int nr_pdo) +{ + if (tcpm_validate_caps(port, pdo, nr_pdo)) + return -EINVAL; + + mutex_lock(&port->lock); + port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo); + switch (port->state) { + case SRC_UNATTACHED: + case SRC_ATTACH_WAIT: + case SRC_TRYWAIT: + tcpm_set_cc(port, tcpm_rp_cc(port)); + break; + case SRC_SEND_CAPABILITIES: + case SRC_NEGOTIATE_CAPABILITIES: + case SRC_READY: + case SRC_WAIT_NEW_CAPABILITIES: + tcpm_set_cc(port, tcpm_rp_cc(port)); + tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0); + break; + default: + break; + } + mutex_unlock(&port->lock); + return 0; +} +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 operating_snk_mw) +{ + if (tcpm_validate_caps(port, pdo, nr_pdo)) + return -EINVAL; + + 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: + 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; + } + mutex_unlock(&port->lock); + return 0; +} +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_cfg.fwnode = dev_fwnode(port->dev); + 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); +} + +static int tcpm_copy_caps(struct tcpm_port *port, + const struct tcpc_config *tcfg) +{ + if (tcpm_validate_caps(port, tcfg->src_pdo, tcfg->nr_src_pdo) || + tcpm_validate_caps(port, tcfg->snk_pdo, tcfg->nr_snk_pdo)) + return -EINVAL; + + port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcfg->src_pdo, + tcfg->nr_src_pdo); + port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcfg->snk_pdo, + tcfg->nr_snk_pdo); + + port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcfg->snk_vdo, + tcfg->nr_snk_vdo); + + port->operating_snk_mw = tcfg->operating_snk_mw; + + port->typec_caps.prefer_role = tcfg->default_role; + port->typec_caps.type = tcfg->type; + port->typec_caps.data = tcfg->data; + + return 0; +} + +struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) +{ + struct tcpm_port *port; + int i, err; + + if (!dev || !tcpc || + !tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc || + !tcpc->set_polarity || !tcpc->set_vconn || !tcpc->set_vbus || + !tcpc->set_pd_rx || !tcpc->set_roles || !tcpc->pd_transmit) + return ERR_PTR(-EINVAL); + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + port->dev = dev; + port->tcpc = tcpc; + + mutex_init(&port->lock); + mutex_init(&port->swap_lock); + + port->wq = create_singlethread_workqueue(dev_name(dev)); + if (!port->wq) + return ERR_PTR(-ENOMEM); + INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work); + INIT_DELAYED_WORK(&port->vdm_state_machine, vdm_state_machine_work); + INIT_WORK(&port->event_work, tcpm_pd_event_handler); + + spin_lock_init(&port->pd_event_lock); + + init_completion(&port->tx_complete); + init_completion(&port->swap_complete); + init_completion(&port->pps_complete); + tcpm_debugfs_init(port); + + err = tcpm_fw_get_caps(port, tcpc->fwnode); + if ((err < 0) && tcpc->config) + err = tcpm_copy_caps(port, tcpc->config); + if (err < 0) + goto out_destroy_wq; + + if (!tcpc->config || !tcpc->config->try_role_hw) + port->try_role = port->typec_caps.prefer_role; + else + port->try_role = TYPEC_NO_PREFERRED_ROLE; + + port->typec_caps.fwnode = tcpc->fwnode; + port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ + port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ + port->typec_caps.dr_set = tcpm_dr_set; + port->typec_caps.pr_set = tcpm_pr_set; + port->typec_caps.vconn_set = tcpm_vconn_set; + port->typec_caps.try_role = tcpm_try_role; + port->typec_caps.port_type_set = tcpm_port_type_set; + + port->partner_desc.identity = &port->partner_ident; + port->port_type = port->typec_caps.type; + + port->role_sw = usb_role_switch_get(port->dev); + if (IS_ERR(port->role_sw)) { + err = PTR_ERR(port->role_sw); + 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); + goto out_destroy_wq; + } + + if (tcpc->config && tcpc->config->alt_modes) { + const struct typec_altmode_desc *paltmode = tcpc->config->alt_modes; + + i = 0; + while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) { + struct typec_altmode *alt; + + alt = typec_port_register_altmode(port->typec_port, + paltmode); + if (IS_ERR(alt)) { + tcpm_log(port, + "%s: failed to register port alternate mode 0x%x", + dev_name(dev), paltmode->svid); + break; + } + typec_altmode_set_drvdata(alt, port); + alt->ops = &tcpm_altmode_ops; + port->port_altmode[i] = alt; + i++; + paltmode++; + } + } + + mutex_lock(&port->lock); + tcpm_init(port); + mutex_unlock(&port->lock); + + tcpm_log(port, "%s: registered", dev_name(dev)); + return port; + +out_destroy_wq: + usb_role_switch_put(port->role_sw); + destroy_workqueue(port->wq); + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(tcpm_register_port); + +void tcpm_unregister_port(struct tcpm_port *port) +{ + int i; + + tcpm_reset_port(port); + for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++) + typec_unregister_altmode(port->port_altmode[i]); + typec_unregister_port(port->typec_port); + usb_role_switch_put(port->role_sw); + tcpm_debugfs_exit(port); + destroy_workqueue(port->wq); +} +EXPORT_SYMBOL_GPL(tcpm_unregister_port); + +MODULE_AUTHOR("Guenter Roeck <groeck@chromium.org>"); +MODULE_DESCRIPTION("USB Type-C Port Manager"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c new file mode 100644 index 000000000000..423208e19383 --- /dev/null +++ b/drivers/usb/typec/tcpm/wcove.c @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * typec_wcove.c - WhiskeyCove PMIC USB Type-C PHY driver + * + * Copyright (C) 2017 Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> + */ + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/usb/tcpm.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mfd/intel_soc_pmic.h> + +/* Register offsets */ +#define WCOVE_CHGRIRQ0 0x4e09 + +#define USBC_CONTROL1 0x7001 +#define USBC_CONTROL2 0x7002 +#define USBC_CONTROL3 0x7003 +#define USBC_CC1_CTRL 0x7004 +#define USBC_CC2_CTRL 0x7005 +#define USBC_STATUS1 0x7007 +#define USBC_STATUS2 0x7008 +#define USBC_STATUS3 0x7009 +#define USBC_CC1 0x700a +#define USBC_CC2 0x700b +#define USBC_CC1_STATUS 0x700c +#define USBC_CC2_STATUS 0x700d +#define USBC_IRQ1 0x7015 +#define USBC_IRQ2 0x7016 +#define USBC_IRQMASK1 0x7017 +#define USBC_IRQMASK2 0x7018 +#define USBC_PDCFG2 0x701a +#define USBC_PDCFG3 0x701b +#define USBC_PDSTATUS 0x701c +#define USBC_RXSTATUS 0x701d +#define USBC_RXINFO 0x701e +#define USBC_TXCMD 0x701f +#define USBC_TXINFO 0x7020 +#define USBC_RX_DATA 0x7028 +#define USBC_TX_DATA 0x7047 + +/* Register bits */ + +#define USBC_CONTROL1_MODE_MASK 0x3 +#define USBC_CONTROL1_MODE_SNK 0 +#define USBC_CONTROL1_MODE_SNKACC 1 +#define USBC_CONTROL1_MODE_SRC 2 +#define USBC_CONTROL1_MODE_SRCACC 3 +#define USBC_CONTROL1_MODE_DRP 4 +#define USBC_CONTROL1_MODE_DRPACC 5 +#define USBC_CONTROL1_MODE_TEST 7 +#define USBC_CONTROL1_CURSRC_MASK 0xc +#define USBC_CONTROL1_CURSRC_UA_0 (0 << 3) +#define USBC_CONTROL1_CURSRC_UA_80 (1 << 3) +#define USBC_CONTROL1_CURSRC_UA_180 (2 << 3) +#define USBC_CONTROL1_CURSRC_UA_330 (3 << 3) +#define USBC_CONTROL1_DRPTOGGLE_RANDOM 0xe0 + +#define USBC_CONTROL2_UNATT_SNK BIT(0) +#define USBC_CONTROL2_UNATT_SRC BIT(1) +#define USBC_CONTROL2_DIS_ST BIT(2) + +#define USBC_CONTROL3_DET_DIS BIT(0) +#define USBC_CONTROL3_PD_DIS BIT(1) +#define USBC_CONTROL3_RESETPHY BIT(2) + +#define USBC_CC_CTRL_PU_EN BIT(0) +#define USBC_CC_CTRL_VCONN_EN BIT(1) +#define USBC_CC_CTRL_TX_EN BIT(2) +#define USBC_CC_CTRL_PD_EN BIT(3) +#define USBC_CC_CTRL_CDET_EN BIT(4) +#define USBC_CC_CTRL_RDET_EN BIT(5) +#define USBC_CC_CTRL_ADC_EN BIT(6) +#define USBC_CC_CTRL_VBUSOK BIT(7) + +#define USBC_STATUS1_DET_ONGOING BIT(6) +#define USBC_STATUS1_RSLT(r) ((r) & 0xf) +#define USBC_RSLT_NOTHING 0 +#define USBC_RSLT_SRC_DEFAULT 1 +#define USBC_RSLT_SRC_1_5A 2 +#define USBC_RSLT_SRC_3_0A 3 +#define USBC_RSLT_SNK 4 +#define USBC_RSLT_DEBUG_ACC 5 +#define USBC_RSLT_AUDIO_ACC 6 +#define USBC_RSLT_UNDEF 15 +#define USBC_STATUS1_ORIENT(r) (((r) >> 4) & 0x3) +#define USBC_ORIENT_NORMAL 1 +#define USBC_ORIENT_REVERSE 2 + +#define USBC_STATUS2_VBUS_REQ BIT(5) + +#define UCSC_CC_STATUS_SNK_RP BIT(0) +#define UCSC_CC_STATUS_PWRDEFSNK BIT(1) +#define UCSC_CC_STATUS_PWR_1P5A_SNK BIT(2) +#define UCSC_CC_STATUS_PWR_3A_SNK BIT(3) +#define UCSC_CC_STATUS_SRC_RP BIT(4) +#define UCSC_CC_STATUS_RX(r) (((r) >> 5) & 0x3) +#define USBC_CC_STATUS_RD 1 +#define USBC_CC_STATUS_RA 2 + +#define USBC_IRQ1_ADCDONE1 BIT(2) +#define USBC_IRQ1_OVERTEMP BIT(1) +#define USBC_IRQ1_SHORT BIT(0) + +#define USBC_IRQ2_CC_CHANGE BIT(7) +#define USBC_IRQ2_RX_PD BIT(6) +#define USBC_IRQ2_RX_HR BIT(5) +#define USBC_IRQ2_RX_CR BIT(4) +#define USBC_IRQ2_TX_SUCCESS BIT(3) +#define USBC_IRQ2_TX_FAIL BIT(2) + +#define USBC_IRQMASK1_ALL (USBC_IRQ1_ADCDONE1 | USBC_IRQ1_OVERTEMP | \ + USBC_IRQ1_SHORT) + +#define USBC_IRQMASK2_ALL (USBC_IRQ2_CC_CHANGE | USBC_IRQ2_RX_PD | \ + USBC_IRQ2_RX_HR | USBC_IRQ2_RX_CR | \ + USBC_IRQ2_TX_SUCCESS | USBC_IRQ2_TX_FAIL) + +#define USBC_PDCFG2_SOP BIT(0) +#define USBC_PDCFG2_SOP_P BIT(1) +#define USBC_PDCFG2_SOP_PP BIT(2) +#define USBC_PDCFG2_SOP_P_DEBUG BIT(3) +#define USBC_PDCFG2_SOP_PP_DEBUG BIT(4) + +#define USBC_PDCFG3_DATAROLE_SHIFT 1 +#define USBC_PDCFG3_SOP_SHIFT 2 + +#define USBC_RXSTATUS_RXCLEAR BIT(0) +#define USBC_RXSTATUS_RXDATA BIT(7) + +#define USBC_RXINFO_RXBYTES(i) (((i) >> 3) & 0x1f) + +#define USBC_TXCMD_BUF_RDY BIT(0) +#define USBC_TXCMD_START BIT(1) +#define USBC_TXCMD_NOP (0 << 5) +#define USBC_TXCMD_MSG (1 << 5) +#define USBC_TXCMD_CR (2 << 5) +#define USBC_TXCMD_HR (3 << 5) +#define USBC_TXCMD_BIST (4 << 5) + +#define USBC_TXINFO_RETRIES(d) (d << 3) + +struct wcove_typec { + struct mutex lock; /* device lock */ + struct device *dev; + struct regmap *regmap; + guid_t guid; + + bool vbus; + + struct tcpc_dev tcpc; + struct tcpm_port *tcpm; +}; + +#define tcpc_to_wcove(_tcpc_) container_of(_tcpc_, struct wcove_typec, tcpc) + +enum wcove_typec_func { + WCOVE_FUNC_DRIVE_VBUS = 1, + WCOVE_FUNC_ORIENTATION, + WCOVE_FUNC_ROLE, + WCOVE_FUNC_DRIVE_VCONN, +}; + +enum wcove_typec_orientation { + WCOVE_ORIENTATION_NORMAL, + WCOVE_ORIENTATION_REVERSE, +}; + +enum wcove_typec_role { + WCOVE_ROLE_HOST, + WCOVE_ROLE_DEVICE, +}; + +#define WCOVE_DSM_UUID "482383f0-2876-4e49-8685-db66211af037" + +static int wcove_typec_func(struct wcove_typec *wcove, + enum wcove_typec_func func, int param) +{ + union acpi_object *obj; + union acpi_object tmp; + union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp); + + tmp.type = ACPI_TYPE_INTEGER; + tmp.integer.value = param; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(wcove->dev), &wcove->guid, 1, func, + &argv4); + if (!obj) { + dev_err(wcove->dev, "%s: failed to evaluate _DSM\n", __func__); + return -EIO; + } + + ACPI_FREE(obj); + return 0; +} + +static int wcove_init(struct tcpc_dev *tcpc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + int ret; + + ret = regmap_write(wcove->regmap, USBC_CONTROL1, 0); + if (ret) + return ret; + + /* Unmask everything */ + ret = regmap_write(wcove->regmap, USBC_IRQMASK1, 0); + if (ret) + return ret; + + return regmap_write(wcove->regmap, USBC_IRQMASK2, 0); +} + +static int wcove_get_vbus(struct tcpc_dev *tcpc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int cc1ctrl; + int ret; + + ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1ctrl); + if (ret) + return ret; + + wcove->vbus = !!(cc1ctrl & USBC_CC_CTRL_VBUSOK); + + return wcove->vbus; +} + +static int wcove_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VBUS, on); +} + +static int wcove_set_vconn(struct tcpc_dev *tcpc, bool on) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, on); +} + +static enum typec_cc_status wcove_to_typec_cc(unsigned int cc) +{ + if (cc & UCSC_CC_STATUS_SNK_RP) { + if (cc & UCSC_CC_STATUS_PWRDEFSNK) + return TYPEC_CC_RP_DEF; + else if (cc & UCSC_CC_STATUS_PWR_1P5A_SNK) + return TYPEC_CC_RP_1_5; + else if (cc & UCSC_CC_STATUS_PWR_3A_SNK) + return TYPEC_CC_RP_3_0; + } else { + switch (UCSC_CC_STATUS_RX(cc)) { + case USBC_CC_STATUS_RD: + return TYPEC_CC_RD; + case USBC_CC_STATUS_RA: + return TYPEC_CC_RA; + default: + break; + } + } + return TYPEC_CC_OPEN; +} + +static int wcove_get_cc(struct tcpc_dev *tcpc, enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int cc1_status; + unsigned int cc2_status; + int ret; + + ret = regmap_read(wcove->regmap, USBC_CC1_STATUS, &cc1_status); + if (ret) + return ret; + + ret = regmap_read(wcove->regmap, USBC_CC2_STATUS, &cc2_status); + if (ret) + return ret; + + *cc1 = wcove_to_typec_cc(cc1_status); + *cc2 = wcove_to_typec_cc(cc2_status); + + return 0; +} + +static int wcove_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int ctrl; + + switch (cc) { + case TYPEC_CC_RD: + ctrl = USBC_CONTROL1_MODE_SNK; + break; + case TYPEC_CC_RP_DEF: + ctrl = USBC_CONTROL1_CURSRC_UA_80 | USBC_CONTROL1_MODE_SRC; + break; + case TYPEC_CC_RP_1_5: + ctrl = USBC_CONTROL1_CURSRC_UA_180 | USBC_CONTROL1_MODE_SRC; + break; + case TYPEC_CC_RP_3_0: + ctrl = USBC_CONTROL1_CURSRC_UA_330 | USBC_CONTROL1_MODE_SRC; + break; + case TYPEC_CC_OPEN: + ctrl = 0; + break; + default: + return -EINVAL; + } + + return regmap_write(wcove->regmap, USBC_CONTROL1, ctrl); +} + +static int wcove_set_polarity(struct tcpc_dev *tcpc, enum typec_cc_polarity pol) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, pol); +} + +static int wcove_set_current_limit(struct tcpc_dev *tcpc, u32 max_ma, u32 mv) +{ + return 0; +} + +static int wcove_set_roles(struct tcpc_dev *tcpc, bool attached, + enum typec_role role, enum typec_data_role data) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int val; + int ret; + + ret = wcove_typec_func(wcove, WCOVE_FUNC_ROLE, data == TYPEC_HOST ? + WCOVE_ROLE_HOST : WCOVE_ROLE_DEVICE); + if (ret) + return ret; + + val = role; + val |= data << USBC_PDCFG3_DATAROLE_SHIFT; + val |= PD_REV20 << USBC_PDCFG3_SOP_SHIFT; + + return regmap_write(wcove->regmap, USBC_PDCFG3, val); +} + +static int wcove_set_pd_rx(struct tcpc_dev *tcpc, bool on) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + + return regmap_write(wcove->regmap, USBC_PDCFG2, + on ? USBC_PDCFG2_SOP : 0); +} + +static int wcove_pd_transmit(struct tcpc_dev *tcpc, + enum tcpm_transmit_type type, + const struct pd_message *msg) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int info = 0; + unsigned int cmd; + int ret; + + ret = regmap_read(wcove->regmap, USBC_TXCMD, &cmd); + if (ret) + return ret; + + if (!(cmd & USBC_TXCMD_BUF_RDY)) { + dev_warn(wcove->dev, "%s: Last transmission still ongoing!", + __func__); + return -EBUSY; + } + + if (msg) { + const u8 *data = (void *)msg; + int i; + + for (i = 0; i < pd_header_cnt(msg->header) * 4 + 2; i++) { + ret = regmap_write(wcove->regmap, USBC_TX_DATA + i, + data[i]); + if (ret) + return ret; + } + } + + switch (type) { + case TCPC_TX_SOP: + case TCPC_TX_SOP_PRIME: + case TCPC_TX_SOP_PRIME_PRIME: + case TCPC_TX_SOP_DEBUG_PRIME: + case TCPC_TX_SOP_DEBUG_PRIME_PRIME: + info = type + 1; + cmd = USBC_TXCMD_MSG; + break; + case TCPC_TX_HARD_RESET: + cmd = USBC_TXCMD_HR; + break; + case TCPC_TX_CABLE_RESET: + cmd = USBC_TXCMD_CR; + break; + case TCPC_TX_BIST_MODE_2: + cmd = USBC_TXCMD_BIST; + break; + default: + return -EINVAL; + } + + /* NOTE Setting maximum number of retries (7) */ + ret = regmap_write(wcove->regmap, USBC_TXINFO, + info | USBC_TXINFO_RETRIES(7)); + if (ret) + return ret; + + return regmap_write(wcove->regmap, USBC_TXCMD, cmd | USBC_TXCMD_START); +} + +static int wcove_start_drp_toggling(struct tcpc_dev *tcpc, + enum typec_cc_status cc) +{ + struct wcove_typec *wcove = tcpc_to_wcove(tcpc); + unsigned int usbc_ctrl; + + usbc_ctrl = USBC_CONTROL1_MODE_DRP | USBC_CONTROL1_DRPTOGGLE_RANDOM; + + switch (cc) { + case TYPEC_CC_RP_1_5: + usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_180; + break; + case TYPEC_CC_RP_3_0: + usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_330; + break; + default: + usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_80; + break; + } + + return regmap_write(wcove->regmap, USBC_CONTROL1, usbc_ctrl); +} + +static int wcove_read_rx_buffer(struct wcove_typec *wcove, void *msg) +{ + unsigned int info; + int ret; + int i; + + ret = regmap_read(wcove->regmap, USBC_RXINFO, &info); + if (ret) + return ret; + + /* FIXME: Check that USBC_RXINFO_RXBYTES(info) matches the header */ + + for (i = 0; i < USBC_RXINFO_RXBYTES(info); i++) { + ret = regmap_read(wcove->regmap, USBC_RX_DATA + i, msg + i); + if (ret) + return ret; + } + + return regmap_write(wcove->regmap, USBC_RXSTATUS, + USBC_RXSTATUS_RXCLEAR); +} + +static irqreturn_t wcove_typec_irq(int irq, void *data) +{ + struct wcove_typec *wcove = data; + unsigned int usbc_irq1 = 0; + unsigned int usbc_irq2 = 0; + unsigned int cc1ctrl; + int ret; + + mutex_lock(&wcove->lock); + + /* Read.. */ + ret = regmap_read(wcove->regmap, USBC_IRQ1, &usbc_irq1); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_IRQ2, &usbc_irq2); + if (ret) + goto err; + + ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1ctrl); + if (ret) + goto err; + + if (!wcove->tcpm) + goto err; + + /* ..check.. */ + if (usbc_irq1 & USBC_IRQ1_OVERTEMP) { + dev_err(wcove->dev, "VCONN Switch Over Temperature!\n"); + wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false); + /* REVISIT: Report an error? */ + } + + if (usbc_irq1 & USBC_IRQ1_SHORT) { + dev_err(wcove->dev, "VCONN Switch Short Circuit!\n"); + wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false); + /* REVISIT: Report an error? */ + } + + if (wcove->vbus != !!(cc1ctrl & USBC_CC_CTRL_VBUSOK)) + tcpm_vbus_change(wcove->tcpm); + + /* REVISIT: See if tcpm code can be made to consider Type-C HW FSMs */ + if (usbc_irq2 & USBC_IRQ2_CC_CHANGE) + tcpm_cc_change(wcove->tcpm); + + if (usbc_irq2 & USBC_IRQ2_RX_PD) { + unsigned int status; + + /* + * FIXME: Need to check if TX is ongoing and report + * TX_DIREGARDED if needed? + */ + + ret = regmap_read(wcove->regmap, USBC_RXSTATUS, &status); + if (ret) + goto err; + + /* Flush all buffers */ + while (status & USBC_RXSTATUS_RXDATA) { + struct pd_message msg; + + ret = wcove_read_rx_buffer(wcove, &msg); + if (ret) { + dev_err(wcove->dev, "%s: RX read failed\n", + __func__); + goto err; + } + + tcpm_pd_receive(wcove->tcpm, &msg); + + ret = regmap_read(wcove->regmap, USBC_RXSTATUS, + &status); + if (ret) + goto err; + } + } + + if (usbc_irq2 & USBC_IRQ2_RX_HR) + tcpm_pd_hard_reset(wcove->tcpm); + + /* REVISIT: if (usbc_irq2 & USBC_IRQ2_RX_CR) */ + + if (usbc_irq2 & USBC_IRQ2_TX_SUCCESS) + tcpm_pd_transmit_complete(wcove->tcpm, TCPC_TX_SUCCESS); + + if (usbc_irq2 & USBC_IRQ2_TX_FAIL) + tcpm_pd_transmit_complete(wcove->tcpm, TCPC_TX_FAILED); + +err: + /* ..and clear. */ + if (usbc_irq1) { + ret = regmap_write(wcove->regmap, USBC_IRQ1, usbc_irq1); + if (ret) + dev_WARN(wcove->dev, "%s failed to clear IRQ1\n", + __func__); + } + + if (usbc_irq2) { + ret = regmap_write(wcove->regmap, USBC_IRQ2, usbc_irq2); + if (ret) + dev_WARN(wcove->dev, "%s failed to clear IRQ2\n", + __func__); + } + + /* REVISIT: Clear WhiskeyCove CHGR Type-C interrupt */ + regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5)); + + mutex_unlock(&wcove->lock); + return IRQ_HANDLED; +} + +/* + * The following power levels should be safe to use with Joule board. + */ +static const u32 src_pdo[] = { + PDO_FIXED(5000, 1500, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | + PDO_FIXED_USB_COMM), +}; + +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 = { + .src_pdo = src_pdo, + .nr_src_pdo = ARRAY_SIZE(src_pdo), + .snk_pdo = snk_pdo, + .nr_snk_pdo = ARRAY_SIZE(snk_pdo), + + .operating_snk_mw = 15000, + + .type = TYPEC_PORT_DRP, + .data = TYPEC_PORT_DRD, + .default_role = TYPEC_SINK, +}; + +static int wcove_typec_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct wcove_typec *wcove; + int irq; + int ret; + + wcove = devm_kzalloc(&pdev->dev, sizeof(*wcove), GFP_KERNEL); + if (!wcove) + return -ENOMEM; + + mutex_init(&wcove->lock); + wcove->dev = &pdev->dev; + wcove->regmap = pmic->regmap; + + irq = regmap_irq_get_virq(pmic->irq_chip_data_chgr, + platform_get_irq(pdev, 0)); + if (irq < 0) + return irq; + + ret = guid_parse(WCOVE_DSM_UUID, &wcove->guid); + if (ret) + return ret; + + if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), &wcove->guid, 0, 0x1f)) { + dev_err(&pdev->dev, "Missing _DSM functions\n"); + return -ENODEV; + } + + wcove->tcpc.init = wcove_init; + wcove->tcpc.get_vbus = wcove_get_vbus; + wcove->tcpc.set_vbus = wcove_set_vbus; + wcove->tcpc.set_cc = wcove_set_cc; + wcove->tcpc.get_cc = wcove_get_cc; + wcove->tcpc.set_polarity = wcove_set_polarity; + wcove->tcpc.set_vconn = wcove_set_vconn; + wcove->tcpc.set_current_limit = wcove_set_current_limit; + wcove->tcpc.start_drp_toggling = wcove_start_drp_toggling; + + wcove->tcpc.set_pd_rx = wcove_set_pd_rx; + wcove->tcpc.set_roles = wcove_set_roles; + wcove->tcpc.pd_transmit = wcove_pd_transmit; + + wcove->tcpc.config = &wcove_typec_config; + + wcove->tcpm = tcpm_register_port(wcove->dev, &wcove->tcpc); + if (IS_ERR(wcove->tcpm)) + return PTR_ERR(wcove->tcpm); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + wcove_typec_irq, IRQF_ONESHOT, + "wcove_typec", wcove); + if (ret) { + tcpm_unregister_port(wcove->tcpm); + return ret; + } + + platform_set_drvdata(pdev, wcove); + return 0; +} + +static int wcove_typec_remove(struct platform_device *pdev) +{ + struct wcove_typec *wcove = platform_get_drvdata(pdev); + unsigned int val; + + /* Mask everything */ + regmap_read(wcove->regmap, USBC_IRQMASK1, &val); + regmap_write(wcove->regmap, USBC_IRQMASK1, val | USBC_IRQMASK1_ALL); + regmap_read(wcove->regmap, USBC_IRQMASK2, &val); + regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL); + + tcpm_unregister_port(wcove->tcpm); + + return 0; +} + +static struct platform_driver wcove_typec_driver = { + .driver = { + .name = "bxt_wcove_usbc", + }, + .probe = wcove_typec_probe, + .remove = wcove_typec_remove, +}; + +module_platform_driver(wcove_typec_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("WhiskeyCove PMIC USB Type-C PHY driver"); +MODULE_ALIAS("platform:bxt_wcove_usbc"); diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c deleted file mode 100644 index 423208e19383..000000000000 --- a/drivers/usb/typec/typec_wcove.c +++ /dev/null @@ -1,693 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/** - * typec_wcove.c - WhiskeyCove PMIC USB Type-C PHY driver - * - * Copyright (C) 2017 Intel Corporation - * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> - */ - -#include <linux/acpi.h> -#include <linux/module.h> -#include <linux/usb/tcpm.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/mfd/intel_soc_pmic.h> - -/* Register offsets */ -#define WCOVE_CHGRIRQ0 0x4e09 - -#define USBC_CONTROL1 0x7001 -#define USBC_CONTROL2 0x7002 -#define USBC_CONTROL3 0x7003 -#define USBC_CC1_CTRL 0x7004 -#define USBC_CC2_CTRL 0x7005 -#define USBC_STATUS1 0x7007 -#define USBC_STATUS2 0x7008 -#define USBC_STATUS3 0x7009 -#define USBC_CC1 0x700a -#define USBC_CC2 0x700b -#define USBC_CC1_STATUS 0x700c -#define USBC_CC2_STATUS 0x700d -#define USBC_IRQ1 0x7015 -#define USBC_IRQ2 0x7016 -#define USBC_IRQMASK1 0x7017 -#define USBC_IRQMASK2 0x7018 -#define USBC_PDCFG2 0x701a -#define USBC_PDCFG3 0x701b -#define USBC_PDSTATUS 0x701c -#define USBC_RXSTATUS 0x701d -#define USBC_RXINFO 0x701e -#define USBC_TXCMD 0x701f -#define USBC_TXINFO 0x7020 -#define USBC_RX_DATA 0x7028 -#define USBC_TX_DATA 0x7047 - -/* Register bits */ - -#define USBC_CONTROL1_MODE_MASK 0x3 -#define USBC_CONTROL1_MODE_SNK 0 -#define USBC_CONTROL1_MODE_SNKACC 1 -#define USBC_CONTROL1_MODE_SRC 2 -#define USBC_CONTROL1_MODE_SRCACC 3 -#define USBC_CONTROL1_MODE_DRP 4 -#define USBC_CONTROL1_MODE_DRPACC 5 -#define USBC_CONTROL1_MODE_TEST 7 -#define USBC_CONTROL1_CURSRC_MASK 0xc -#define USBC_CONTROL1_CURSRC_UA_0 (0 << 3) -#define USBC_CONTROL1_CURSRC_UA_80 (1 << 3) -#define USBC_CONTROL1_CURSRC_UA_180 (2 << 3) -#define USBC_CONTROL1_CURSRC_UA_330 (3 << 3) -#define USBC_CONTROL1_DRPTOGGLE_RANDOM 0xe0 - -#define USBC_CONTROL2_UNATT_SNK BIT(0) -#define USBC_CONTROL2_UNATT_SRC BIT(1) -#define USBC_CONTROL2_DIS_ST BIT(2) - -#define USBC_CONTROL3_DET_DIS BIT(0) -#define USBC_CONTROL3_PD_DIS BIT(1) -#define USBC_CONTROL3_RESETPHY BIT(2) - -#define USBC_CC_CTRL_PU_EN BIT(0) -#define USBC_CC_CTRL_VCONN_EN BIT(1) -#define USBC_CC_CTRL_TX_EN BIT(2) -#define USBC_CC_CTRL_PD_EN BIT(3) -#define USBC_CC_CTRL_CDET_EN BIT(4) -#define USBC_CC_CTRL_RDET_EN BIT(5) -#define USBC_CC_CTRL_ADC_EN BIT(6) -#define USBC_CC_CTRL_VBUSOK BIT(7) - -#define USBC_STATUS1_DET_ONGOING BIT(6) -#define USBC_STATUS1_RSLT(r) ((r) & 0xf) -#define USBC_RSLT_NOTHING 0 -#define USBC_RSLT_SRC_DEFAULT 1 -#define USBC_RSLT_SRC_1_5A 2 -#define USBC_RSLT_SRC_3_0A 3 -#define USBC_RSLT_SNK 4 -#define USBC_RSLT_DEBUG_ACC 5 -#define USBC_RSLT_AUDIO_ACC 6 -#define USBC_RSLT_UNDEF 15 -#define USBC_STATUS1_ORIENT(r) (((r) >> 4) & 0x3) -#define USBC_ORIENT_NORMAL 1 -#define USBC_ORIENT_REVERSE 2 - -#define USBC_STATUS2_VBUS_REQ BIT(5) - -#define UCSC_CC_STATUS_SNK_RP BIT(0) -#define UCSC_CC_STATUS_PWRDEFSNK BIT(1) -#define UCSC_CC_STATUS_PWR_1P5A_SNK BIT(2) -#define UCSC_CC_STATUS_PWR_3A_SNK BIT(3) -#define UCSC_CC_STATUS_SRC_RP BIT(4) -#define UCSC_CC_STATUS_RX(r) (((r) >> 5) & 0x3) -#define USBC_CC_STATUS_RD 1 -#define USBC_CC_STATUS_RA 2 - -#define USBC_IRQ1_ADCDONE1 BIT(2) -#define USBC_IRQ1_OVERTEMP BIT(1) -#define USBC_IRQ1_SHORT BIT(0) - -#define USBC_IRQ2_CC_CHANGE BIT(7) -#define USBC_IRQ2_RX_PD BIT(6) -#define USBC_IRQ2_RX_HR BIT(5) -#define USBC_IRQ2_RX_CR BIT(4) -#define USBC_IRQ2_TX_SUCCESS BIT(3) -#define USBC_IRQ2_TX_FAIL BIT(2) - -#define USBC_IRQMASK1_ALL (USBC_IRQ1_ADCDONE1 | USBC_IRQ1_OVERTEMP | \ - USBC_IRQ1_SHORT) - -#define USBC_IRQMASK2_ALL (USBC_IRQ2_CC_CHANGE | USBC_IRQ2_RX_PD | \ - USBC_IRQ2_RX_HR | USBC_IRQ2_RX_CR | \ - USBC_IRQ2_TX_SUCCESS | USBC_IRQ2_TX_FAIL) - -#define USBC_PDCFG2_SOP BIT(0) -#define USBC_PDCFG2_SOP_P BIT(1) -#define USBC_PDCFG2_SOP_PP BIT(2) -#define USBC_PDCFG2_SOP_P_DEBUG BIT(3) -#define USBC_PDCFG2_SOP_PP_DEBUG BIT(4) - -#define USBC_PDCFG3_DATAROLE_SHIFT 1 -#define USBC_PDCFG3_SOP_SHIFT 2 - -#define USBC_RXSTATUS_RXCLEAR BIT(0) -#define USBC_RXSTATUS_RXDATA BIT(7) - -#define USBC_RXINFO_RXBYTES(i) (((i) >> 3) & 0x1f) - -#define USBC_TXCMD_BUF_RDY BIT(0) -#define USBC_TXCMD_START BIT(1) -#define USBC_TXCMD_NOP (0 << 5) -#define USBC_TXCMD_MSG (1 << 5) -#define USBC_TXCMD_CR (2 << 5) -#define USBC_TXCMD_HR (3 << 5) -#define USBC_TXCMD_BIST (4 << 5) - -#define USBC_TXINFO_RETRIES(d) (d << 3) - -struct wcove_typec { - struct mutex lock; /* device lock */ - struct device *dev; - struct regmap *regmap; - guid_t guid; - - bool vbus; - - struct tcpc_dev tcpc; - struct tcpm_port *tcpm; -}; - -#define tcpc_to_wcove(_tcpc_) container_of(_tcpc_, struct wcove_typec, tcpc) - -enum wcove_typec_func { - WCOVE_FUNC_DRIVE_VBUS = 1, - WCOVE_FUNC_ORIENTATION, - WCOVE_FUNC_ROLE, - WCOVE_FUNC_DRIVE_VCONN, -}; - -enum wcove_typec_orientation { - WCOVE_ORIENTATION_NORMAL, - WCOVE_ORIENTATION_REVERSE, -}; - -enum wcove_typec_role { - WCOVE_ROLE_HOST, - WCOVE_ROLE_DEVICE, -}; - -#define WCOVE_DSM_UUID "482383f0-2876-4e49-8685-db66211af037" - -static int wcove_typec_func(struct wcove_typec *wcove, - enum wcove_typec_func func, int param) -{ - union acpi_object *obj; - union acpi_object tmp; - union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp); - - tmp.type = ACPI_TYPE_INTEGER; - tmp.integer.value = param; - - obj = acpi_evaluate_dsm(ACPI_HANDLE(wcove->dev), &wcove->guid, 1, func, - &argv4); - if (!obj) { - dev_err(wcove->dev, "%s: failed to evaluate _DSM\n", __func__); - return -EIO; - } - - ACPI_FREE(obj); - return 0; -} - -static int wcove_init(struct tcpc_dev *tcpc) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - int ret; - - ret = regmap_write(wcove->regmap, USBC_CONTROL1, 0); - if (ret) - return ret; - - /* Unmask everything */ - ret = regmap_write(wcove->regmap, USBC_IRQMASK1, 0); - if (ret) - return ret; - - return regmap_write(wcove->regmap, USBC_IRQMASK2, 0); -} - -static int wcove_get_vbus(struct tcpc_dev *tcpc) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - unsigned int cc1ctrl; - int ret; - - ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1ctrl); - if (ret) - return ret; - - wcove->vbus = !!(cc1ctrl & USBC_CC_CTRL_VBUSOK); - - return wcove->vbus; -} - -static int wcove_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - - return wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VBUS, on); -} - -static int wcove_set_vconn(struct tcpc_dev *tcpc, bool on) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - - return wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, on); -} - -static enum typec_cc_status wcove_to_typec_cc(unsigned int cc) -{ - if (cc & UCSC_CC_STATUS_SNK_RP) { - if (cc & UCSC_CC_STATUS_PWRDEFSNK) - return TYPEC_CC_RP_DEF; - else if (cc & UCSC_CC_STATUS_PWR_1P5A_SNK) - return TYPEC_CC_RP_1_5; - else if (cc & UCSC_CC_STATUS_PWR_3A_SNK) - return TYPEC_CC_RP_3_0; - } else { - switch (UCSC_CC_STATUS_RX(cc)) { - case USBC_CC_STATUS_RD: - return TYPEC_CC_RD; - case USBC_CC_STATUS_RA: - return TYPEC_CC_RA; - default: - break; - } - } - return TYPEC_CC_OPEN; -} - -static int wcove_get_cc(struct tcpc_dev *tcpc, enum typec_cc_status *cc1, - enum typec_cc_status *cc2) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - unsigned int cc1_status; - unsigned int cc2_status; - int ret; - - ret = regmap_read(wcove->regmap, USBC_CC1_STATUS, &cc1_status); - if (ret) - return ret; - - ret = regmap_read(wcove->regmap, USBC_CC2_STATUS, &cc2_status); - if (ret) - return ret; - - *cc1 = wcove_to_typec_cc(cc1_status); - *cc2 = wcove_to_typec_cc(cc2_status); - - return 0; -} - -static int wcove_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - unsigned int ctrl; - - switch (cc) { - case TYPEC_CC_RD: - ctrl = USBC_CONTROL1_MODE_SNK; - break; - case TYPEC_CC_RP_DEF: - ctrl = USBC_CONTROL1_CURSRC_UA_80 | USBC_CONTROL1_MODE_SRC; - break; - case TYPEC_CC_RP_1_5: - ctrl = USBC_CONTROL1_CURSRC_UA_180 | USBC_CONTROL1_MODE_SRC; - break; - case TYPEC_CC_RP_3_0: - ctrl = USBC_CONTROL1_CURSRC_UA_330 | USBC_CONTROL1_MODE_SRC; - break; - case TYPEC_CC_OPEN: - ctrl = 0; - break; - default: - return -EINVAL; - } - - return regmap_write(wcove->regmap, USBC_CONTROL1, ctrl); -} - -static int wcove_set_polarity(struct tcpc_dev *tcpc, enum typec_cc_polarity pol) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - - return wcove_typec_func(wcove, WCOVE_FUNC_ORIENTATION, pol); -} - -static int wcove_set_current_limit(struct tcpc_dev *tcpc, u32 max_ma, u32 mv) -{ - return 0; -} - -static int wcove_set_roles(struct tcpc_dev *tcpc, bool attached, - enum typec_role role, enum typec_data_role data) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - unsigned int val; - int ret; - - ret = wcove_typec_func(wcove, WCOVE_FUNC_ROLE, data == TYPEC_HOST ? - WCOVE_ROLE_HOST : WCOVE_ROLE_DEVICE); - if (ret) - return ret; - - val = role; - val |= data << USBC_PDCFG3_DATAROLE_SHIFT; - val |= PD_REV20 << USBC_PDCFG3_SOP_SHIFT; - - return regmap_write(wcove->regmap, USBC_PDCFG3, val); -} - -static int wcove_set_pd_rx(struct tcpc_dev *tcpc, bool on) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - - return regmap_write(wcove->regmap, USBC_PDCFG2, - on ? USBC_PDCFG2_SOP : 0); -} - -static int wcove_pd_transmit(struct tcpc_dev *tcpc, - enum tcpm_transmit_type type, - const struct pd_message *msg) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - unsigned int info = 0; - unsigned int cmd; - int ret; - - ret = regmap_read(wcove->regmap, USBC_TXCMD, &cmd); - if (ret) - return ret; - - if (!(cmd & USBC_TXCMD_BUF_RDY)) { - dev_warn(wcove->dev, "%s: Last transmission still ongoing!", - __func__); - return -EBUSY; - } - - if (msg) { - const u8 *data = (void *)msg; - int i; - - for (i = 0; i < pd_header_cnt(msg->header) * 4 + 2; i++) { - ret = regmap_write(wcove->regmap, USBC_TX_DATA + i, - data[i]); - if (ret) - return ret; - } - } - - switch (type) { - case TCPC_TX_SOP: - case TCPC_TX_SOP_PRIME: - case TCPC_TX_SOP_PRIME_PRIME: - case TCPC_TX_SOP_DEBUG_PRIME: - case TCPC_TX_SOP_DEBUG_PRIME_PRIME: - info = type + 1; - cmd = USBC_TXCMD_MSG; - break; - case TCPC_TX_HARD_RESET: - cmd = USBC_TXCMD_HR; - break; - case TCPC_TX_CABLE_RESET: - cmd = USBC_TXCMD_CR; - break; - case TCPC_TX_BIST_MODE_2: - cmd = USBC_TXCMD_BIST; - break; - default: - return -EINVAL; - } - - /* NOTE Setting maximum number of retries (7) */ - ret = regmap_write(wcove->regmap, USBC_TXINFO, - info | USBC_TXINFO_RETRIES(7)); - if (ret) - return ret; - - return regmap_write(wcove->regmap, USBC_TXCMD, cmd | USBC_TXCMD_START); -} - -static int wcove_start_drp_toggling(struct tcpc_dev *tcpc, - enum typec_cc_status cc) -{ - struct wcove_typec *wcove = tcpc_to_wcove(tcpc); - unsigned int usbc_ctrl; - - usbc_ctrl = USBC_CONTROL1_MODE_DRP | USBC_CONTROL1_DRPTOGGLE_RANDOM; - - switch (cc) { - case TYPEC_CC_RP_1_5: - usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_180; - break; - case TYPEC_CC_RP_3_0: - usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_330; - break; - default: - usbc_ctrl |= USBC_CONTROL1_CURSRC_UA_80; - break; - } - - return regmap_write(wcove->regmap, USBC_CONTROL1, usbc_ctrl); -} - -static int wcove_read_rx_buffer(struct wcove_typec *wcove, void *msg) -{ - unsigned int info; - int ret; - int i; - - ret = regmap_read(wcove->regmap, USBC_RXINFO, &info); - if (ret) - return ret; - - /* FIXME: Check that USBC_RXINFO_RXBYTES(info) matches the header */ - - for (i = 0; i < USBC_RXINFO_RXBYTES(info); i++) { - ret = regmap_read(wcove->regmap, USBC_RX_DATA + i, msg + i); - if (ret) - return ret; - } - - return regmap_write(wcove->regmap, USBC_RXSTATUS, - USBC_RXSTATUS_RXCLEAR); -} - -static irqreturn_t wcove_typec_irq(int irq, void *data) -{ - struct wcove_typec *wcove = data; - unsigned int usbc_irq1 = 0; - unsigned int usbc_irq2 = 0; - unsigned int cc1ctrl; - int ret; - - mutex_lock(&wcove->lock); - - /* Read.. */ - ret = regmap_read(wcove->regmap, USBC_IRQ1, &usbc_irq1); - if (ret) - goto err; - - ret = regmap_read(wcove->regmap, USBC_IRQ2, &usbc_irq2); - if (ret) - goto err; - - ret = regmap_read(wcove->regmap, USBC_CC1_CTRL, &cc1ctrl); - if (ret) - goto err; - - if (!wcove->tcpm) - goto err; - - /* ..check.. */ - if (usbc_irq1 & USBC_IRQ1_OVERTEMP) { - dev_err(wcove->dev, "VCONN Switch Over Temperature!\n"); - wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false); - /* REVISIT: Report an error? */ - } - - if (usbc_irq1 & USBC_IRQ1_SHORT) { - dev_err(wcove->dev, "VCONN Switch Short Circuit!\n"); - wcove_typec_func(wcove, WCOVE_FUNC_DRIVE_VCONN, false); - /* REVISIT: Report an error? */ - } - - if (wcove->vbus != !!(cc1ctrl & USBC_CC_CTRL_VBUSOK)) - tcpm_vbus_change(wcove->tcpm); - - /* REVISIT: See if tcpm code can be made to consider Type-C HW FSMs */ - if (usbc_irq2 & USBC_IRQ2_CC_CHANGE) - tcpm_cc_change(wcove->tcpm); - - if (usbc_irq2 & USBC_IRQ2_RX_PD) { - unsigned int status; - - /* - * FIXME: Need to check if TX is ongoing and report - * TX_DIREGARDED if needed? - */ - - ret = regmap_read(wcove->regmap, USBC_RXSTATUS, &status); - if (ret) - goto err; - - /* Flush all buffers */ - while (status & USBC_RXSTATUS_RXDATA) { - struct pd_message msg; - - ret = wcove_read_rx_buffer(wcove, &msg); - if (ret) { - dev_err(wcove->dev, "%s: RX read failed\n", - __func__); - goto err; - } - - tcpm_pd_receive(wcove->tcpm, &msg); - - ret = regmap_read(wcove->regmap, USBC_RXSTATUS, - &status); - if (ret) - goto err; - } - } - - if (usbc_irq2 & USBC_IRQ2_RX_HR) - tcpm_pd_hard_reset(wcove->tcpm); - - /* REVISIT: if (usbc_irq2 & USBC_IRQ2_RX_CR) */ - - if (usbc_irq2 & USBC_IRQ2_TX_SUCCESS) - tcpm_pd_transmit_complete(wcove->tcpm, TCPC_TX_SUCCESS); - - if (usbc_irq2 & USBC_IRQ2_TX_FAIL) - tcpm_pd_transmit_complete(wcove->tcpm, TCPC_TX_FAILED); - -err: - /* ..and clear. */ - if (usbc_irq1) { - ret = regmap_write(wcove->regmap, USBC_IRQ1, usbc_irq1); - if (ret) - dev_WARN(wcove->dev, "%s failed to clear IRQ1\n", - __func__); - } - - if (usbc_irq2) { - ret = regmap_write(wcove->regmap, USBC_IRQ2, usbc_irq2); - if (ret) - dev_WARN(wcove->dev, "%s failed to clear IRQ2\n", - __func__); - } - - /* REVISIT: Clear WhiskeyCove CHGR Type-C interrupt */ - regmap_write(wcove->regmap, WCOVE_CHGRIRQ0, BIT(5)); - - mutex_unlock(&wcove->lock); - return IRQ_HANDLED; -} - -/* - * The following power levels should be safe to use with Joule board. - */ -static const u32 src_pdo[] = { - PDO_FIXED(5000, 1500, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | - PDO_FIXED_USB_COMM), -}; - -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 = { - .src_pdo = src_pdo, - .nr_src_pdo = ARRAY_SIZE(src_pdo), - .snk_pdo = snk_pdo, - .nr_snk_pdo = ARRAY_SIZE(snk_pdo), - - .operating_snk_mw = 15000, - - .type = TYPEC_PORT_DRP, - .data = TYPEC_PORT_DRD, - .default_role = TYPEC_SINK, -}; - -static int wcove_typec_probe(struct platform_device *pdev) -{ - struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); - struct wcove_typec *wcove; - int irq; - int ret; - - wcove = devm_kzalloc(&pdev->dev, sizeof(*wcove), GFP_KERNEL); - if (!wcove) - return -ENOMEM; - - mutex_init(&wcove->lock); - wcove->dev = &pdev->dev; - wcove->regmap = pmic->regmap; - - irq = regmap_irq_get_virq(pmic->irq_chip_data_chgr, - platform_get_irq(pdev, 0)); - if (irq < 0) - return irq; - - ret = guid_parse(WCOVE_DSM_UUID, &wcove->guid); - if (ret) - return ret; - - if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), &wcove->guid, 0, 0x1f)) { - dev_err(&pdev->dev, "Missing _DSM functions\n"); - return -ENODEV; - } - - wcove->tcpc.init = wcove_init; - wcove->tcpc.get_vbus = wcove_get_vbus; - wcove->tcpc.set_vbus = wcove_set_vbus; - wcove->tcpc.set_cc = wcove_set_cc; - wcove->tcpc.get_cc = wcove_get_cc; - wcove->tcpc.set_polarity = wcove_set_polarity; - wcove->tcpc.set_vconn = wcove_set_vconn; - wcove->tcpc.set_current_limit = wcove_set_current_limit; - wcove->tcpc.start_drp_toggling = wcove_start_drp_toggling; - - wcove->tcpc.set_pd_rx = wcove_set_pd_rx; - wcove->tcpc.set_roles = wcove_set_roles; - wcove->tcpc.pd_transmit = wcove_pd_transmit; - - wcove->tcpc.config = &wcove_typec_config; - - wcove->tcpm = tcpm_register_port(wcove->dev, &wcove->tcpc); - if (IS_ERR(wcove->tcpm)) - return PTR_ERR(wcove->tcpm); - - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - wcove_typec_irq, IRQF_ONESHOT, - "wcove_typec", wcove); - if (ret) { - tcpm_unregister_port(wcove->tcpm); - return ret; - } - - platform_set_drvdata(pdev, wcove); - return 0; -} - -static int wcove_typec_remove(struct platform_device *pdev) -{ - struct wcove_typec *wcove = platform_get_drvdata(pdev); - unsigned int val; - - /* Mask everything */ - regmap_read(wcove->regmap, USBC_IRQMASK1, &val); - regmap_write(wcove->regmap, USBC_IRQMASK1, val | USBC_IRQMASK1_ALL); - regmap_read(wcove->regmap, USBC_IRQMASK2, &val); - regmap_write(wcove->regmap, USBC_IRQMASK2, val | USBC_IRQMASK2_ALL); - - tcpm_unregister_port(wcove->tcpm); - - return 0; -} - -static struct platform_driver wcove_typec_driver = { - .driver = { - .name = "bxt_wcove_usbc", - }, - .probe = wcove_typec_probe, - .remove = wcove_typec_remove, -}; - -module_platform_driver(wcove_typec_driver); - -MODULE_AUTHOR("Intel Corporation"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("WhiskeyCove PMIC USB Type-C PHY driver"); -MODULE_ALIAS("platform:bxt_wcove_usbc"); -- cgit v1.2.3-58-ga151 From 1906f64f6458f4f7f7b77268a7280c7689f61163 Mon Sep 17 00:00:00 2001 From: Jagdish Tirumala <t.jag587@gmail.com> Date: Tue, 11 Sep 2018 09:35:00 -0700 Subject: USB: STORAGE: ISD200 Fixed coding style issue "space required in for loop" Fixed errors spaces required around the for loop '=' , ';' and '<' in drivers/usb/storage/isd200.c Signed-off-by: Jagdish Tirumala <t.jag587@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/storage/isd200.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/storage/isd200.c b/drivers/usb/storage/isd200.c index f5e4500d9970..2b474d60b4db 100644 --- a/drivers/usb/storage/isd200.c +++ b/drivers/usb/storage/isd200.c @@ -1153,7 +1153,7 @@ static int isd200_get_inquiry_data( struct us_data *us ) /* Fill in vendor identification fields */ src = (__be16 *)&id[ATA_ID_PROD]; dest = (__u16*)info->InquiryData.VendorId; - for (i=0;i<4;i++) + for (i = 0; i < 4; i++) dest[i] = be16_to_cpu(src[i]); src = (__be16 *)&id[ATA_ID_PROD + 8/2]; -- cgit v1.2.3-58-ga151 From bbc1f57aa87066a188ef34330bc1f091b95b1a6b Mon Sep 17 00:00:00 2001 From: Colin Ian King <colin.king@canonical.com> Date: Mon, 24 Sep 2018 00:04:08 +0100 Subject: USB: serial: cypress_m8: fix spelling mistake "retreiving" -> "retrieving" Trivial fix to spelling mistake in dev_dbg message Signed-off-by: Colin Ian King <colin.king@canonical.com> Signed-off-by: Johan Hovold <johan@kernel.org> --- drivers/usb/serial/cypress_m8.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index e0035c023120..31c6091be46a 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -378,7 +378,7 @@ static int cypress_serial_control(struct tty_struct *tty, retval = -ENOTTY; goto out; } - dev_dbg(dev, "%s - retreiving serial line settings\n", __func__); + dev_dbg(dev, "%s - retrieving serial line settings\n", __func__); do { retval = usb_control_msg(port->serial->dev, usb_rcvctrlpipe(port->serial->dev, 0), -- cgit v1.2.3-58-ga151 From 86f3daed59bceb4fa7981d85e89f63ebbae1d561 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Thu, 2 Aug 2018 00:14:00 +0300 Subject: usb: gadget: uvc: configfs: Drop leaked references to config items Some of the .allow_link() and .drop_link() operations implementations call config_group_find_item() and then leak the reference to the returned item. Fix this by dropping those references where needed. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index dbc95c9558de..8d513cc6fb8c 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -529,6 +529,7 @@ static int uvcg_control_class_allow_link(struct config_item *src, unlock: mutex_unlock(&opts->lock); out: + config_item_put(header); mutex_unlock(su_mutex); return ret; } @@ -564,6 +565,7 @@ static void uvcg_control_class_drop_link(struct config_item *src, unlock: mutex_unlock(&opts->lock); out: + config_item_put(header); mutex_unlock(su_mutex); } @@ -2026,6 +2028,7 @@ static int uvcg_streaming_class_allow_link(struct config_item *src, unlock: mutex_unlock(&opts->lock); out: + config_item_put(header); mutex_unlock(su_mutex); return ret; } @@ -2066,6 +2069,7 @@ static void uvcg_streaming_class_drop_link(struct config_item *src, unlock: mutex_unlock(&opts->lock); out: + config_item_put(header); mutex_unlock(su_mutex); } -- cgit v1.2.3-58-ga151 From efbf0af70b4f7ee6ed1533ed0d905255c0545e08 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Thu, 24 May 2018 17:49:34 +0300 Subject: usb: gadget: uvc: configfs: Allocate groups dynamically The UVC configfs implementation creates all groups as global static variables. This prevents creation of multiple UVC function instances, as they would all require their own configfs group instances. Fix this by allocating all groups dynamically. To avoid duplicating code around, extend the config_item_type structure with group name and children, and implement helper functions to create children automatically for most groups. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/f_uvc.c | 8 +- drivers/usb/gadget/function/uvc_configfs.c | 581 ++++++++++++++++------------- 2 files changed, 338 insertions(+), 251 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index d8ce7868fe22..95cb1b5f5ffe 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -792,6 +792,7 @@ static struct usb_function_instance *uvc_alloc_inst(void) struct uvc_output_terminal_descriptor *od; struct uvc_color_matching_descriptor *md; struct uvc_descriptor_header **ctl_cls; + int ret; opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) @@ -868,7 +869,12 @@ static struct usb_function_instance *uvc_alloc_inst(void) opts->streaming_interval = 1; opts->streaming_maxpacket = 1024; - uvcg_attach_configfs(opts); + ret = uvcg_attach_configfs(opts); + if (ret < 0) { + kfree(opts); + return ERR_PTR(ret); + } + return &opts->func_inst; } diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 8d513cc6fb8c..ae722549eabc 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -41,6 +41,71 @@ static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) func_inst.group); } +struct uvcg_config_group_type { + struct config_item_type type; + const char *name; + const struct uvcg_config_group_type **children; + int (*create_children)(struct config_group *group); +}; + +static void uvcg_config_item_release(struct config_item *item) +{ + struct config_group *group = to_config_group(item); + + kfree(group); +} + +static struct configfs_item_operations uvcg_config_item_ops = { + .release = uvcg_config_item_release, +}; + +static int uvcg_config_create_group(struct config_group *parent, + const struct uvcg_config_group_type *type); + +static int uvcg_config_create_children(struct config_group *group, + const struct uvcg_config_group_type *type) +{ + const struct uvcg_config_group_type **child; + int ret; + + if (type->create_children) + return type->create_children(group); + + for (child = type->children; child && *child; ++child) { + ret = uvcg_config_create_group(group, *child); + if (ret < 0) + return ret; + } + + return 0; +} + +static int uvcg_config_create_group(struct config_group *parent, + const struct uvcg_config_group_type *type) +{ + struct config_group *group; + + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) + return -ENOMEM; + + config_group_init_type_name(group, type->name, &type->type); + configfs_add_default_group(group, parent); + + return uvcg_config_create_children(group, type); +} + +static void uvcg_config_remove_children(struct config_group *group) +{ + struct config_group *child, *n; + + list_for_each_entry_safe(child, n, &group->default_groups, group_entry) { + list_del(&child->group_entry); + uvcg_config_remove_children(child); + config_item_put(&child->cg_item); + } +} + /* ----------------------------------------------------------------------------- * control/header/<NAME> * control/header @@ -137,6 +202,7 @@ static struct configfs_attribute *uvcg_control_header_attrs[] = { }; static const struct config_item_type uvcg_control_header_type = { + .ct_item_ops = &uvcg_config_item_ops, .ct_attrs = uvcg_control_header_attrs, .ct_owner = THIS_MODULE, }; @@ -161,32 +227,23 @@ static struct config_item *uvcg_control_header_make(struct config_group *group, return &h->item; } -static void uvcg_control_header_drop(struct config_group *group, - struct config_item *item) -{ - struct uvcg_control_header *h = to_uvcg_control_header(item); - - kfree(h); -} - -static struct config_group uvcg_control_header_grp; - static struct configfs_group_operations uvcg_control_header_grp_ops = { .make_item = uvcg_control_header_make, - .drop_item = uvcg_control_header_drop, }; -static const struct config_item_type uvcg_control_header_grp_type = { - .ct_group_ops = &uvcg_control_header_grp_ops, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_control_header_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_control_header_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "header", }; /* ----------------------------------------------------------------------------- * control/processing/default */ -static struct config_group uvcg_default_processing_grp; - #define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_processing_##cname##_show( \ struct config_item *item, char *page) \ @@ -265,27 +322,35 @@ static struct configfs_attribute *uvcg_default_processing_attrs[] = { NULL, }; -static const struct config_item_type uvcg_default_processing_type = { - .ct_attrs = uvcg_default_processing_attrs, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_default_processing_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_processing_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "default", }; /* ----------------------------------------------------------------------------- * control/processing */ -static struct config_group uvcg_processing_grp; - -static const struct config_item_type uvcg_processing_grp_type = { - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_processing_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "processing", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_default_processing_type, + NULL, + }, }; /* ----------------------------------------------------------------------------- * control/terminal/camera/default */ -static struct config_group uvcg_default_camera_grp; - #define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_camera_##cname##_show( \ struct config_item *item, char *page) \ @@ -375,27 +440,35 @@ static struct configfs_attribute *uvcg_default_camera_attrs[] = { NULL, }; -static const struct config_item_type uvcg_default_camera_type = { - .ct_attrs = uvcg_default_camera_attrs, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_default_camera_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_camera_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "default", }; /* ----------------------------------------------------------------------------- * control/terminal/camera */ -static struct config_group uvcg_camera_grp; - -static const struct config_item_type uvcg_camera_grp_type = { - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_camera_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "camera", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_default_camera_type, + NULL, + }, }; /* ----------------------------------------------------------------------------- * control/terminal/output/default */ -static struct config_group uvcg_default_output_grp; - #define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_output_##cname##_show( \ struct config_item *item, char *page) \ @@ -446,47 +519,68 @@ static struct configfs_attribute *uvcg_default_output_attrs[] = { NULL, }; -static const struct config_item_type uvcg_default_output_type = { - .ct_attrs = uvcg_default_output_attrs, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_default_output_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_output_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "default", }; /* ----------------------------------------------------------------------------- * control/terminal/output */ -static struct config_group uvcg_output_grp; - -static const struct config_item_type uvcg_output_grp_type = { - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_output_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "output", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_default_output_type, + NULL, + }, }; /* ----------------------------------------------------------------------------- * control/terminal */ -static struct config_group uvcg_terminal_grp; - -static const struct config_item_type uvcg_terminal_grp_type = { - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_terminal_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "terminal", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_camera_grp_type, + &uvcg_output_grp_type, + NULL, + }, }; /* ----------------------------------------------------------------------------- * control/class/{fs|ss} */ -static struct config_group uvcg_control_class_fs_grp; -static struct config_group uvcg_control_class_ss_grp; +struct uvcg_control_class_group { + struct config_group group; + const char *name; +}; static inline struct uvc_descriptor_header **uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o) { - struct config_group *group = to_config_group(i); + struct uvcg_control_class_group *group = + container_of(i, struct uvcg_control_class_group, + group.cg_item); - if (group == &uvcg_control_class_fs_grp) + if (!strcmp(group->name, "fs")) return o->uvc_fs_control_cls; - if (group == &uvcg_control_class_ss_grp) + if (!strcmp(group->name, "ss")) return o->uvc_ss_control_cls; return NULL; @@ -570,6 +664,7 @@ out: } static struct configfs_item_operations uvcg_control_class_item_ops = { + .release = uvcg_config_item_release, .allow_link = uvcg_control_class_allow_link, .drop_link = uvcg_control_class_drop_link, }; @@ -583,20 +678,54 @@ static const struct config_item_type uvcg_control_class_type = { * control/class */ -static struct config_group uvcg_control_class_grp; +static int uvcg_control_class_create_children(struct config_group *parent) +{ + static const char * const names[] = { "fs", "ss" }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + struct uvcg_control_class_group *group; -static const struct config_item_type uvcg_control_class_grp_type = { - .ct_owner = THIS_MODULE, + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) + return -ENOMEM; + + group->name = names[i]; + + config_group_init_type_name(&group->group, group->name, + &uvcg_control_class_type); + configfs_add_default_group(&group->group, parent); + } + + return 0; +} + +static const struct uvcg_config_group_type uvcg_control_class_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "class", + .create_children = uvcg_control_class_create_children, }; /* ----------------------------------------------------------------------------- * control */ -static struct config_group uvcg_control_grp; - -static const struct config_item_type uvcg_control_grp_type = { - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_control_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "control", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_control_header_grp_type, + &uvcg_processing_grp_type, + &uvcg_terminal_grp_type, + &uvcg_control_class_grp_type, + NULL, + }, }; /* ----------------------------------------------------------------------------- @@ -604,12 +733,9 @@ static const struct config_item_type uvcg_control_grp_type = { * streaming/mjpeg */ -static struct config_group uvcg_uncompressed_grp; -static struct config_group uvcg_mjpeg_grp; - -static struct config_item *fmt_parent[] = { - &uvcg_uncompressed_grp.cg_item, - &uvcg_mjpeg_grp.cg_item, +static const char * const uvcg_format_names[] = { + "uncompressed", + "mjpeg", }; enum uvcg_format_type { @@ -735,10 +861,22 @@ static int uvcg_streaming_header_allow_link(struct config_item *src, goto out; } - for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i) - if (target->ci_parent == fmt_parent[i]) + /* + * Linking is only allowed to direct children of the format nodes + * (streaming/uncompressed or streaming/mjpeg nodes). First check that + * the grand-parent of the target matches the grand-parent of the source + * (the streaming node), and then verify that the target parent is a + * format node. + */ + if (src->ci_parent->ci_parent != target->ci_parent->ci_parent) + goto out; + + for (i = 0; i < ARRAY_SIZE(uvcg_format_names); ++i) { + if (!strcmp(target->ci_parent->ci_name, uvcg_format_names[i])) break; - if (i == ARRAY_SIZE(fmt_parent)) + } + + if (i == ARRAY_SIZE(uvcg_format_names)) goto out; target_fmt = container_of(to_config_group(target), struct uvcg_format, @@ -798,8 +936,9 @@ out: } static struct configfs_item_operations uvcg_streaming_header_item_ops = { - .allow_link = uvcg_streaming_header_allow_link, - .drop_link = uvcg_streaming_header_drop_link, + .release = uvcg_config_item_release, + .allow_link = uvcg_streaming_header_allow_link, + .drop_link = uvcg_streaming_header_drop_link, }; #define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv) \ @@ -875,24 +1014,17 @@ static struct config_item return &h->item; } -static void uvcg_streaming_header_drop(struct config_group *group, - struct config_item *item) -{ - struct uvcg_streaming_header *h = to_uvcg_streaming_header(item); - - kfree(h); -} - -static struct config_group uvcg_streaming_header_grp; - static struct configfs_group_operations uvcg_streaming_header_grp_ops = { .make_item = uvcg_streaming_header_make, - .drop_item = uvcg_streaming_header_drop, }; -static const struct config_item_type uvcg_streaming_header_grp_type = { - .ct_group_ops = &uvcg_streaming_header_grp_ops, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_streaming_header_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_streaming_header_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "header", }; /* ----------------------------------------------------------------------------- @@ -900,6 +1032,8 @@ static const struct config_item_type uvcg_streaming_header_grp_type = { */ struct uvcg_frame { + struct config_item item; + enum uvcg_format_type fmt_type; struct { u8 b_length; u8 b_descriptor_type; @@ -915,8 +1049,6 @@ struct uvcg_frame { u8 b_frame_interval_type; } __attribute__((packed)) frame; u32 *dw_frame_interval; - enum uvcg_format_type fmt_type; - struct config_item item; }; static struct uvcg_frame *to_uvcg_frame(struct config_item *item) @@ -1143,6 +1275,7 @@ static struct configfs_attribute *uvcg_frame_attrs[] = { }; static const struct config_item_type uvcg_frame_type = { + .ct_item_ops = &uvcg_config_item_ops, .ct_attrs = uvcg_frame_attrs, .ct_owner = THIS_MODULE, }; @@ -1194,7 +1327,6 @@ static struct config_item *uvcg_frame_make(struct config_group *group, static void uvcg_frame_drop(struct config_group *group, struct config_item *item) { - struct uvcg_frame *h = to_uvcg_frame(item); struct uvcg_format *fmt; struct f_uvc_opts *opts; struct config_item *opts_item; @@ -1205,8 +1337,9 @@ static void uvcg_frame_drop(struct config_group *group, struct config_item *item mutex_lock(&opts->lock); fmt = to_uvcg_format(&group->cg_item); --fmt->num_frames; - kfree(h); mutex_unlock(&opts->lock); + + config_item_put(item); } /* ----------------------------------------------------------------------------- @@ -1415,6 +1548,7 @@ static struct configfs_attribute *uvcg_uncompressed_attrs[] = { }; static const struct config_item_type uvcg_uncompressed_type = { + .ct_item_ops = &uvcg_config_item_ops, .ct_group_ops = &uvcg_uncompressed_group_ops, .ct_attrs = uvcg_uncompressed_attrs, .ct_owner = THIS_MODULE, @@ -1451,22 +1585,17 @@ static struct config_group *uvcg_uncompressed_make(struct config_group *group, return &h->fmt.group; } -static void uvcg_uncompressed_drop(struct config_group *group, - struct config_item *item) -{ - struct uvcg_uncompressed *h = to_uvcg_uncompressed(item); - - kfree(h); -} - static struct configfs_group_operations uvcg_uncompressed_grp_ops = { .make_group = uvcg_uncompressed_make, - .drop_item = uvcg_uncompressed_drop, }; -static const struct config_item_type uvcg_uncompressed_grp_type = { - .ct_group_ops = &uvcg_uncompressed_grp_ops, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_uncompressed_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_uncompressed_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "uncompressed", }; /* ----------------------------------------------------------------------------- @@ -1618,6 +1747,7 @@ static struct configfs_attribute *uvcg_mjpeg_attrs[] = { }; static const struct config_item_type uvcg_mjpeg_type = { + .ct_item_ops = &uvcg_config_item_ops, .ct_group_ops = &uvcg_mjpeg_group_ops, .ct_attrs = uvcg_mjpeg_attrs, .ct_owner = THIS_MODULE, @@ -1648,30 +1778,23 @@ static struct config_group *uvcg_mjpeg_make(struct config_group *group, return &h->fmt.group; } -static void uvcg_mjpeg_drop(struct config_group *group, - struct config_item *item) -{ - struct uvcg_mjpeg *h = to_uvcg_mjpeg(item); - - kfree(h); -} - static struct configfs_group_operations uvcg_mjpeg_grp_ops = { .make_group = uvcg_mjpeg_make, - .drop_item = uvcg_mjpeg_drop, }; -static const struct config_item_type uvcg_mjpeg_grp_type = { - .ct_group_ops = &uvcg_mjpeg_grp_ops, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_group_ops = &uvcg_mjpeg_grp_ops, + .ct_owner = THIS_MODULE, + }, + .name = "mjpeg", }; /* ----------------------------------------------------------------------------- * streaming/color_matching/default */ -static struct config_group uvcg_default_color_matching_grp; - #define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv) \ static ssize_t uvcg_default_color_matching_##cname##_show( \ struct config_item *item, char *page) \ @@ -1719,41 +1842,54 @@ static struct configfs_attribute *uvcg_default_color_matching_attrs[] = { NULL, }; -static const struct config_item_type uvcg_default_color_matching_type = { - .ct_attrs = uvcg_default_color_matching_attrs, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_default_color_matching_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_color_matching_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "default", }; /* ----------------------------------------------------------------------------- * streaming/color_matching */ -static struct config_group uvcg_color_matching_grp; - -static const struct config_item_type uvcg_color_matching_grp_type = { - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_color_matching_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "color_matching", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_default_color_matching_type, + NULL, + }, }; /* ----------------------------------------------------------------------------- * streaming/class/{fs|hs|ss} */ -static struct config_group uvcg_streaming_class_fs_grp; -static struct config_group uvcg_streaming_class_hs_grp; -static struct config_group uvcg_streaming_class_ss_grp; +struct uvcg_streaming_class_group { + struct config_group group; + const char *name; +}; static inline struct uvc_descriptor_header ***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o) { - struct config_group *group = to_config_group(i); + struct uvcg_streaming_class_group *group = + container_of(i, struct uvcg_streaming_class_group, + group.cg_item); - if (group == &uvcg_streaming_class_fs_grp) + if (!strcmp(group->name, "fs")) return &o->uvc_fs_streaming_cls; - if (group == &uvcg_streaming_class_hs_grp) + if (!strcmp(group->name, "hs")) return &o->uvc_hs_streaming_cls; - if (group == &uvcg_streaming_class_ss_grp) + if (!strcmp(group->name, "ss")) return &o->uvc_ss_streaming_cls; return NULL; @@ -2074,6 +2210,7 @@ out: } static struct configfs_item_operations uvcg_streaming_class_item_ops = { + .release = uvcg_config_item_release, .allow_link = uvcg_streaming_class_allow_link, .drop_link = uvcg_streaming_class_drop_link, }; @@ -2087,35 +2224,71 @@ static const struct config_item_type uvcg_streaming_class_type = { * streaming/class */ -static struct config_group uvcg_streaming_class_grp; +static int uvcg_streaming_class_create_children(struct config_group *parent) +{ + static const char * const names[] = { "fs", "hs", "ss" }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + struct uvcg_streaming_class_group *group; + + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) + return -ENOMEM; + + group->name = names[i]; -static const struct config_item_type uvcg_streaming_class_grp_type = { - .ct_owner = THIS_MODULE, + config_group_init_type_name(&group->group, group->name, + &uvcg_streaming_class_type); + configfs_add_default_group(&group->group, parent); + } + + return 0; +} + +static const struct uvcg_config_group_type uvcg_streaming_class_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "class", + .create_children = uvcg_streaming_class_create_children, }; /* ----------------------------------------------------------------------------- * streaming */ -static struct config_group uvcg_streaming_grp; - -static const struct config_item_type uvcg_streaming_grp_type = { - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvcg_streaming_grp_type = { + .type = { + .ct_item_ops = &uvcg_config_item_ops, + .ct_owner = THIS_MODULE, + }, + .name = "streaming", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_streaming_header_grp_type, + &uvcg_uncompressed_grp_type, + &uvcg_mjpeg_grp_type, + &uvcg_color_matching_grp_type, + &uvcg_streaming_class_grp_type, + NULL, + }, }; /* ----------------------------------------------------------------------------- * UVC function */ -static void uvc_attr_release(struct config_item *item) +static void uvc_func_item_release(struct config_item *item) { struct f_uvc_opts *opts = to_f_uvc_opts(item); + uvcg_config_remove_children(to_config_group(item)); usb_put_function_instance(&opts->func_inst); } -static struct configfs_item_operations uvc_item_ops = { - .release = uvc_attr_release, +static struct configfs_item_operations uvc_func_item_ops = { + .release = uvc_func_item_release, }; #define UVCG_OPTS_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ @@ -2183,123 +2356,31 @@ static struct configfs_attribute *uvc_attrs[] = { NULL, }; -static const struct config_item_type uvc_func_type = { - .ct_item_ops = &uvc_item_ops, - .ct_attrs = uvc_attrs, - .ct_owner = THIS_MODULE, +static const struct uvcg_config_group_type uvc_func_type = { + .type = { + .ct_item_ops = &uvc_func_item_ops, + .ct_attrs = uvc_attrs, + .ct_owner = THIS_MODULE, + }, + .name = "", + .children = (const struct uvcg_config_group_type*[]) { + &uvcg_control_grp_type, + &uvcg_streaming_grp_type, + NULL, + }, }; int uvcg_attach_configfs(struct f_uvc_opts *opts) { - config_group_init_type_name(&uvcg_control_header_grp, - "header", - &uvcg_control_header_grp_type); - - config_group_init_type_name(&uvcg_default_processing_grp, - "default", &uvcg_default_processing_type); - config_group_init_type_name(&uvcg_processing_grp, - "processing", &uvcg_processing_grp_type); - configfs_add_default_group(&uvcg_default_processing_grp, - &uvcg_processing_grp); - - config_group_init_type_name(&uvcg_default_camera_grp, - "default", &uvcg_default_camera_type); - config_group_init_type_name(&uvcg_camera_grp, - "camera", &uvcg_camera_grp_type); - configfs_add_default_group(&uvcg_default_camera_grp, - &uvcg_camera_grp); - - config_group_init_type_name(&uvcg_default_output_grp, - "default", &uvcg_default_output_type); - config_group_init_type_name(&uvcg_output_grp, - "output", &uvcg_output_grp_type); - configfs_add_default_group(&uvcg_default_output_grp, - &uvcg_output_grp); - - config_group_init_type_name(&uvcg_terminal_grp, - "terminal", &uvcg_terminal_grp_type); - configfs_add_default_group(&uvcg_camera_grp, - &uvcg_terminal_grp); - configfs_add_default_group(&uvcg_output_grp, - &uvcg_terminal_grp); - - config_group_init_type_name(&uvcg_control_class_fs_grp, - "fs", &uvcg_control_class_type); - config_group_init_type_name(&uvcg_control_class_ss_grp, - "ss", &uvcg_control_class_type); - config_group_init_type_name(&uvcg_control_class_grp, - "class", - &uvcg_control_class_grp_type); - configfs_add_default_group(&uvcg_control_class_fs_grp, - &uvcg_control_class_grp); - configfs_add_default_group(&uvcg_control_class_ss_grp, - &uvcg_control_class_grp); - - config_group_init_type_name(&uvcg_control_grp, - "control", - &uvcg_control_grp_type); - configfs_add_default_group(&uvcg_control_header_grp, - &uvcg_control_grp); - configfs_add_default_group(&uvcg_processing_grp, - &uvcg_control_grp); - configfs_add_default_group(&uvcg_terminal_grp, - &uvcg_control_grp); - configfs_add_default_group(&uvcg_control_class_grp, - &uvcg_control_grp); - - config_group_init_type_name(&uvcg_streaming_header_grp, - "header", - &uvcg_streaming_header_grp_type); - config_group_init_type_name(&uvcg_uncompressed_grp, - "uncompressed", - &uvcg_uncompressed_grp_type); - config_group_init_type_name(&uvcg_mjpeg_grp, - "mjpeg", - &uvcg_mjpeg_grp_type); - config_group_init_type_name(&uvcg_default_color_matching_grp, - "default", - &uvcg_default_color_matching_type); - config_group_init_type_name(&uvcg_color_matching_grp, - "color_matching", - &uvcg_color_matching_grp_type); - configfs_add_default_group(&uvcg_default_color_matching_grp, - &uvcg_color_matching_grp); - - config_group_init_type_name(&uvcg_streaming_class_fs_grp, - "fs", &uvcg_streaming_class_type); - config_group_init_type_name(&uvcg_streaming_class_hs_grp, - "hs", &uvcg_streaming_class_type); - config_group_init_type_name(&uvcg_streaming_class_ss_grp, - "ss", &uvcg_streaming_class_type); - config_group_init_type_name(&uvcg_streaming_class_grp, - "class", &uvcg_streaming_class_grp_type); - configfs_add_default_group(&uvcg_streaming_class_fs_grp, - &uvcg_streaming_class_grp); - configfs_add_default_group(&uvcg_streaming_class_hs_grp, - &uvcg_streaming_class_grp); - configfs_add_default_group(&uvcg_streaming_class_ss_grp, - &uvcg_streaming_class_grp); - - config_group_init_type_name(&uvcg_streaming_grp, - "streaming", &uvcg_streaming_grp_type); - configfs_add_default_group(&uvcg_streaming_header_grp, - &uvcg_streaming_grp); - configfs_add_default_group(&uvcg_uncompressed_grp, - &uvcg_streaming_grp); - configfs_add_default_group(&uvcg_mjpeg_grp, - &uvcg_streaming_grp); - configfs_add_default_group(&uvcg_color_matching_grp, - &uvcg_streaming_grp); - configfs_add_default_group(&uvcg_streaming_class_grp, - &uvcg_streaming_grp); - - config_group_init_type_name(&opts->func_inst.group, - "", - &uvc_func_type); - configfs_add_default_group(&uvcg_control_grp, - &opts->func_inst.group); - configfs_add_default_group(&uvcg_streaming_grp, - &opts->func_inst.group); + int ret; - return 0; + config_group_init_type_name(&opts->func_inst.group, uvc_func_type.name, + &uvc_func_type.type); + + ret = uvcg_config_create_children(&opts->func_inst.group, + &uvc_func_type); + if (ret < 0) + config_group_put(&opts->func_inst.group); + + return ret; } -- cgit v1.2.3-58-ga151 From bf71544883a1ccb20021eb5139475496dbd8abd9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Wed, 23 May 2018 18:47:56 +0300 Subject: usb: gadget: uvc: configfs: Add interface number attributes The video control and video streaming interface numbers are needed in the UVC gadget userspace stack to reply to UVC requests. They are hardcoded to fixed values at the moment, preventing configurations with multiple functions. To fix this, make them dynamically discoverable by userspace through read-only configfs attributes in <function>/control/bInterfaceNumber and <function>/streaming/bInterfaceNumber respectively. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 8 +++ drivers/usb/gadget/function/f_uvc.c | 2 + drivers/usb/gadget/function/u_uvc.h | 3 ++ drivers/usb/gadget/function/uvc_configfs.c | 62 +++++++++++++++++++++++ 4 files changed, 75 insertions(+) (limited to 'drivers/usb') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index 9281e2aa38df..490a0136fb02 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -12,6 +12,10 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Control descriptors + All attributes read only: + bInterfaceNumber - USB interface number for this + streaming interface + What: /config/usb-gadget/gadget/functions/uvc.name/control/class Date: Dec 2014 KernelVersion: 4.0 @@ -109,6 +113,10 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Streaming descriptors + All attributes read only: + bInterfaceNumber - USB interface number for this + streaming interface + What: /config/usb-gadget/gadget/functions/uvc.name/streaming/class Date: Dec 2014 KernelVersion: 4.0 diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 95cb1b5f5ffe..4ea987741e6e 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -699,12 +699,14 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvc_iad.bFirstInterface = ret; uvc_control_intf.bInterfaceNumber = ret; uvc->control_intf = ret; + opts->control_interface = ret; if ((ret = usb_interface_id(c, f)) < 0) goto error; uvc_streaming_intf_alt0.bInterfaceNumber = ret; uvc_streaming_intf_alt1.bInterfaceNumber = ret; uvc->streaming_intf = ret; + opts->streaming_interface = ret; /* Copy descriptors */ f->fs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_FULL); diff --git a/drivers/usb/gadget/function/u_uvc.h b/drivers/usb/gadget/function/u_uvc.h index 2ed292e94fbc..5242d489e20a 100644 --- a/drivers/usb/gadget/function/u_uvc.h +++ b/drivers/usb/gadget/function/u_uvc.h @@ -25,6 +25,9 @@ struct f_uvc_opts { unsigned int streaming_maxpacket; unsigned int streaming_maxburst; + unsigned int control_interface; + unsigned int streaming_interface; + /* * Control descriptors array pointers for full-/high-speed and * super-speed. They point by default to the uvc_fs_control_cls and diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index ae722549eabc..fa8d2e1f54ba 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -713,9 +713,40 @@ static const struct uvcg_config_group_type uvcg_control_class_grp_type = { * control */ +static ssize_t uvcg_default_control_b_interface_number_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + int result = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result += sprintf(page, "%u\n", opts->control_interface); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +UVC_ATTR_RO(uvcg_default_control_, b_interface_number, bInterfaceNumber); + +static struct configfs_attribute *uvcg_default_control_attrs[] = { + &uvcg_default_control_attr_b_interface_number, + NULL, +}; + static const struct uvcg_config_group_type uvcg_control_grp_type = { .type = { .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_control_attrs, .ct_owner = THIS_MODULE, }, .name = "control", @@ -2259,9 +2290,40 @@ static const struct uvcg_config_group_type uvcg_streaming_class_grp_type = { * streaming */ +static ssize_t uvcg_default_streaming_b_interface_number_show( + struct config_item *item, char *page) +{ + struct config_group *group = to_config_group(item); + struct mutex *su_mutex = &group->cg_subsys->su_mutex; + struct config_item *opts_item; + struct f_uvc_opts *opts; + int result = 0; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + opts_item = item->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result += sprintf(page, "%u\n", opts->streaming_interface); + mutex_unlock(&opts->lock); + + mutex_unlock(su_mutex); + + return result; +} + +UVC_ATTR_RO(uvcg_default_streaming_, b_interface_number, bInterfaceNumber); + +static struct configfs_attribute *uvcg_default_streaming_attrs[] = { + &uvcg_default_streaming_attr_b_interface_number, + NULL, +}; + static const struct uvcg_config_group_type uvcg_streaming_grp_type = { .type = { .ct_item_ops = &uvcg_config_item_ops, + .ct_attrs = uvcg_default_streaming_attrs, .ct_owner = THIS_MODULE, }, .name = "streaming", -- cgit v1.2.3-58-ga151 From 61ff10e0ea0cb39c737eab7e4fc5f0ae4d0fff33 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Sun, 27 May 2018 00:51:57 +0300 Subject: usb: gadget: uvc: configfs: Add bFormatIndex attributes The UVC format description are numbered using the descriptor's bFormatIndex field. The index is used in UVC requests, and is thus needed to handle requests in userspace. Make it dynamically discoverable by exposing it in a bFormatIndex configfs attribute of the uncompressed and mjpeg format config items. The bFormatIndex value exposed through the attribute is stored in the config item private data. However, that value is never set: the driver instead computes the bFormatIndex value when linking the stream class header in the configfs hierarchy and stores it directly in the class descriptors in a separate structure. In order to expose the value through the configfs attribute, store it in the config item private data as well. This results in a small code simplification. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 8 ++++++++ drivers/usb/gadget/function/uvc_configfs.c | 14 ++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'drivers/usb') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index 490a0136fb02..a6cc8d6d398e 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -168,6 +168,10 @@ Description: Specific MJPEG format descriptors All attributes read only, except bmaControls and bDefaultFrameIndex: + bFormatIndex - unique id for this format descriptor; + only defined after parent header is + linked into the streaming class; + read-only bmaControls - this format's data for bmaControls in the streaming header bmInterfaceFlags - specifies interlace information, @@ -212,6 +216,10 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Specific uncompressed format descriptors + bFormatIndex - unique id for this format descriptor; + only defined after parent header is + linked into the streaming class; + read-only bmaControls - this format's data for bmaControls in the streaming header bmInterfaceFlags - specifies interlace information, diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index fa8d2e1f54ba..5cee8aca3734 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1538,6 +1538,7 @@ UVC_ATTR(uvcg_uncompressed_, cname, aname); #define identity_conv(x) (x) +UVCG_UNCOMPRESSED_ATTR_RO(b_format_index, bFormatIndex, identity_conv); UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, identity_conv); UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, identity_conv); @@ -1568,6 +1569,7 @@ uvcg_uncompressed_bma_controls_store(struct config_item *item, UVC_ATTR(uvcg_uncompressed_, bma_controls, bmaControls); static struct configfs_attribute *uvcg_uncompressed_attrs[] = { + &uvcg_uncompressed_attr_b_format_index, &uvcg_uncompressed_attr_guid_format, &uvcg_uncompressed_attr_b_bits_per_pixel, &uvcg_uncompressed_attr_b_default_frame_index, @@ -1738,6 +1740,7 @@ UVC_ATTR(uvcg_mjpeg_, cname, aname) #define identity_conv(x) (x) +UVCG_MJPEG_ATTR_RO(b_format_index, bFormatIndex, identity_conv); UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, identity_conv); UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, identity_conv); @@ -1768,6 +1771,7 @@ uvcg_mjpeg_bma_controls_store(struct config_item *item, UVC_ATTR(uvcg_mjpeg_, bma_controls, bmaControls); static struct configfs_attribute *uvcg_mjpeg_attrs[] = { + &uvcg_mjpeg_attr_b_format_index, &uvcg_mjpeg_attr_b_default_frame_index, &uvcg_mjpeg_attr_bm_flags, &uvcg_mjpeg_attr_b_aspect_ratio_x, @@ -2079,24 +2083,22 @@ static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n, struct uvcg_format *fmt = priv1; if (fmt->type == UVCG_UNCOMPRESSED) { - struct uvc_format_uncompressed *unc = *dest; struct uvcg_uncompressed *u = container_of(fmt, struct uvcg_uncompressed, fmt); + u->desc.bFormatIndex = n + 1; + u->desc.bNumFrameDescriptors = fmt->num_frames; memcpy(*dest, &u->desc, sizeof(u->desc)); *dest += sizeof(u->desc); - unc->bNumFrameDescriptors = fmt->num_frames; - unc->bFormatIndex = n + 1; } else if (fmt->type == UVCG_MJPEG) { - struct uvc_format_mjpeg *mjp = *dest; struct uvcg_mjpeg *m = container_of(fmt, struct uvcg_mjpeg, fmt); + m->desc.bFormatIndex = n + 1; + m->desc.bNumFrameDescriptors = fmt->num_frames; memcpy(*dest, &m->desc, sizeof(m->desc)); *dest += sizeof(m->desc); - mjp->bNumFrameDescriptors = fmt->num_frames; - mjp->bFormatIndex = n + 1; } else { return -EINVAL; } -- cgit v1.2.3-58-ga151 From b206548be6459ea5ffa82b9f1175915b225a89a1 Mon Sep 17 00:00:00 2001 From: Joel Pepper <joel.pepper@rwth-aachen.de> Date: Tue, 29 May 2018 21:02:13 +0200 Subject: usb: gadget: uvc: configfs: Add bFrameIndex attributes - Add bFrameIndex as a UVCG_FRAME_ATTR_RO for each frame size. - Automatically assign ascending bFrameIndex to each frame in a format. Before all "bFrameindex" attributes were set to "1" with no way to configure the gadget otherwise. This resulted in the host always negotiating for bFrameIndex 1 (i.e. the first frame size of the gadget). After the negotiation the host driver will set the user or application selected frame size, while the gadget is actually set to the first frame size. Now, when the containing format is linked into the streaming header, iterate over all child frame descriptors and assign ascending indices. The automatically assigned indices can be read from the new read only bFrameIndex configfs attribute in each frame descriptor item. Signed-off-by: Joel Pepper <joel.pepper@rwth-aachen.de> [Simplified documentation, renamed function, blank space update] Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- Documentation/ABI/testing/configfs-usb-gadget-uvc | 8 ++++ drivers/usb/gadget/function/uvc_configfs.c | 56 +++++++++++++++++++++++ 2 files changed, 64 insertions(+) (limited to 'drivers/usb') diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uvc b/Documentation/ABI/testing/configfs-usb-gadget-uvc index a6cc8d6d398e..809765bd9573 100644 --- a/Documentation/ABI/testing/configfs-usb-gadget-uvc +++ b/Documentation/ABI/testing/configfs-usb-gadget-uvc @@ -189,6 +189,10 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Specific MJPEG frame descriptors + bFrameIndex - unique id for this framedescriptor; + only defined after parent format is + linked into the streaming header; + read-only dwFrameInterval - indicates how frame interval can be programmed; a number of values separated by newline can be specified @@ -240,6 +244,10 @@ Date: Dec 2014 KernelVersion: 4.0 Description: Specific uncompressed frame descriptors + bFrameIndex - unique id for this framedescriptor; + only defined after parent format is + linked into the streaming header; + read-only dwFrameInterval - indicates how frame interval can be programmed; a number of values separated by newline can be specified diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 5cee8aca3734..b8763343dcae 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -868,6 +868,8 @@ static struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item return container_of(item, struct uvcg_streaming_header, item); } +static void uvcg_format_set_indices(struct config_group *fmt); + static int uvcg_streaming_header_allow_link(struct config_item *src, struct config_item *target) { @@ -915,6 +917,8 @@ static int uvcg_streaming_header_allow_link(struct config_item *src, if (!target_fmt) goto out; + uvcg_format_set_indices(to_config_group(target)); + format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL); if (!format_ptr) { ret = -ENOMEM; @@ -1146,6 +1150,41 @@ end: \ \ UVC_ATTR(uvcg_frame_, cname, aname); +static ssize_t uvcg_frame_b_frame_index_show(struct config_item *item, + char *page) +{ + struct uvcg_frame *f = to_uvcg_frame(item); + struct uvcg_format *fmt; + struct f_uvc_opts *opts; + struct config_item *opts_item; + struct config_item *fmt_item; + struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex; + int result; + + mutex_lock(su_mutex); /* for navigating configfs hierarchy */ + + fmt_item = f->item.ci_parent; + fmt = to_uvcg_format(fmt_item); + + if (!fmt->linked) { + result = -EBUSY; + goto out; + } + + opts_item = fmt_item->ci_parent->ci_parent->ci_parent; + opts = to_f_uvc_opts(opts_item); + + mutex_lock(&opts->lock); + result = sprintf(page, "%d\n", f->frame.b_frame_index); + mutex_unlock(&opts->lock); + +out: + mutex_unlock(su_mutex); + return result; +} + +UVC_ATTR_RO(uvcg_frame_, b_frame_index, bFrameIndex); + #define noop_conversion(x) (x) UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, noop_conversion, @@ -1294,6 +1333,7 @@ end: UVC_ATTR(uvcg_frame_, dw_frame_interval, dwFrameInterval); static struct configfs_attribute *uvcg_frame_attrs[] = { + &uvcg_frame_attr_b_frame_index, &uvcg_frame_attr_bm_capabilities, &uvcg_frame_attr_w_width, &uvcg_frame_attr_w_height, @@ -1373,6 +1413,22 @@ static void uvcg_frame_drop(struct config_group *group, struct config_item *item config_item_put(item); } +static void uvcg_format_set_indices(struct config_group *fmt) +{ + struct config_item *ci; + unsigned int i = 1; + + list_for_each_entry(ci, &fmt->cg_children, ci_entry) { + struct uvcg_frame *frm; + + if (ci->ci_type != &uvcg_frame_type) + continue; + + frm = to_uvcg_frame(ci); + frm->frame.b_frame_index = i++; + } +} + /* ----------------------------------------------------------------------------- * streaming/uncompressed/<NAME> */ -- cgit v1.2.3-58-ga151 From cb2200f7af8341aaf0c6abd7ba37e4c667c41639 Mon Sep 17 00:00:00 2001 From: Joel Pepper <joel.pepper@rwth-aachen.de> Date: Tue, 29 May 2018 21:02:12 +0200 Subject: usb: gadget: uvc: configfs: Prevent format changes after linking header While checks are in place to avoid attributes and children of a format being manipulated after the format is linked into the streaming header, the linked flag was never actually set, invalidating the protections. Update the flag as appropriate in the header link calls. Signed-off-by: Joel Pepper <joel.pepper@rwth-aachen.de> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index b8763343dcae..799dc32c5bc7 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -928,6 +928,7 @@ static int uvcg_streaming_header_allow_link(struct config_item *src, format_ptr->fmt = target_fmt; list_add_tail(&format_ptr->entry, &src_hdr->formats); ++src_hdr->num_fmt; + ++target_fmt->linked; out: mutex_unlock(&opts->lock); @@ -965,6 +966,8 @@ static void uvcg_streaming_header_drop_link(struct config_item *src, break; } + --target_fmt->linked; + out: mutex_unlock(&opts->lock); mutex_unlock(su_mutex); -- cgit v1.2.3-58-ga151 From 89969a842e72b1b653140a4bbddd927b242736d0 Mon Sep 17 00:00:00 2001 From: Paul Elder <paul.elder@ideasonboard.com> Date: Sun, 2 Sep 2018 19:46:03 -0400 Subject: usb: gadget: uvc: configfs: Sort frame intervals upon writing There is an issue where the host is unable to tell the gadget what frame rate it wants if the dwFrameIntervals in the interface descriptors are not in ascending order. This means that when instantiating a uvc gadget via configfs the user must make sure the dwFrameIntervals are in ascending order. Instead of silently failing the breaking of this rule, we sort the dwFrameIntervals upon writing to configfs. Signed-off-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 799dc32c5bc7..6031467f1868 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -9,6 +9,9 @@ * * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com> */ + +#include <linux/sort.h> + #include "u_uvc.h" #include "uvc_configfs.h" @@ -35,6 +38,14 @@ static struct configfs_attribute prefix##attr_##cname = { \ .show = prefix##cname##_show, \ } +static int uvcg_config_compare_u32(const void *l, const void *r) +{ + u32 li = *(const u32 *)l; + u32 ri = *(const u32 *)r; + + return li < ri ? -1 : li == ri ? 0 : 1; +} + static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item) { return container_of(to_config_group(item), struct f_uvc_opts, @@ -1325,6 +1336,8 @@ static ssize_t uvcg_frame_dw_frame_interval_store(struct config_item *item, kfree(ch->dw_frame_interval); ch->dw_frame_interval = frm_intrv; ch->frame.b_frame_interval_type = n; + sort(ch->dw_frame_interval, n, sizeof(*ch->dw_frame_interval), + uvcg_config_compare_u32, NULL); ret = len; end: -- cgit v1.2.3-58-ga151 From 9d1ff5dcb3cd3390b1e56f1c24ae42c72257c4a3 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Fri, 10 Aug 2018 15:42:03 +0300 Subject: usb: gadget: uvc: Factor out video USB request queueing USB requests for video data are queued from two different locations in the driver, with the same code block occurring twice. Factor it out to a function. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Tested-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/uvc_video.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index d3567b90343a..a95c8e2364ed 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -125,6 +125,19 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video, * Request handling */ +static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req) +{ + int ret; + + ret = usb_ep_queue(video->ep, req, GFP_ATOMIC); + if (ret < 0) { + printk(KERN_INFO "Failed to queue request (%d).\n", ret); + usb_ep_set_halt(video->ep); + } + + return ret; +} + /* * I somehow feel that synchronisation won't be easy to achieve here. We have * three events that control USB requests submission: @@ -189,14 +202,13 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) video->encode(req, video, buf); - if ((ret = usb_ep_queue(ep, req, GFP_ATOMIC)) < 0) { - printk(KERN_INFO "Failed to queue request (%d).\n", ret); - usb_ep_set_halt(ep); - spin_unlock_irqrestore(&video->queue.irqlock, flags); + ret = uvcg_video_ep_queue(video, req); + spin_unlock_irqrestore(&video->queue.irqlock, flags); + + if (ret < 0) { uvcg_queue_cancel(queue, 0); goto requeue; } - spin_unlock_irqrestore(&video->queue.irqlock, flags); return; @@ -316,15 +328,13 @@ int uvcg_video_pump(struct uvc_video *video) video->encode(req, video, buf); /* Queue the USB request */ - ret = usb_ep_queue(video->ep, req, GFP_ATOMIC); + ret = uvcg_video_ep_queue(video, req); + spin_unlock_irqrestore(&queue->irqlock, flags); + if (ret < 0) { - printk(KERN_INFO "Failed to queue request (%d)\n", ret); - usb_ep_set_halt(video->ep); - spin_unlock_irqrestore(&queue->irqlock, flags); uvcg_queue_cancel(queue, 0); break; } - spin_unlock_irqrestore(&queue->irqlock, flags); } spin_lock_irqsave(&video->req_lock, flags); -- cgit v1.2.3-58-ga151 From 8dbf9c7abefd5c1434a956d5c6b25e11183061a3 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Fri, 10 Aug 2018 15:44:57 +0300 Subject: usb: gadget: uvc: Only halt video streaming endpoint in bulk mode When USB requests for video data fail to be submitted, the driver signals a problem to the host by halting the video streaming endpoint. This is only valid in bulk mode, as isochronous transfers have no handshake phase and can't thus report a stall. The usb_ep_set_halt() call returns an error when using isochronous endpoints, which we happily ignore, but some UDCs complain in the kernel log. Fix this by only trying to halt the endpoint in bulk mode. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Tested-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/uvc_video.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index a95c8e2364ed..2c9821ec836e 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -132,7 +132,9 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req) ret = usb_ep_queue(video->ep, req, GFP_ATOMIC); if (ret < 0) { printk(KERN_INFO "Failed to queue request (%d).\n", ret); - usb_ep_set_halt(video->ep); + /* Isochronous endpoints can't be halted. */ + if (usb_endpoint_xfer_bulk(video->ep->desc)) + usb_ep_set_halt(video->ep); } return ret; -- cgit v1.2.3-58-ga151 From dc0f755b421d5aac9052f43c9d0e7285607d446c Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Fri, 10 Aug 2018 15:48:02 +0300 Subject: usb: gadget: uvc: Replace plain printk() with dev_*() Adding device context to the kernel log messages make them more useful. Add new uvcg_* macros based on dev_*() that print both the gadget device name and the function name. While at it, remove a commented out printk statement and an unused printk-based macro. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Tested-by: Paul Elder <paul.elder@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/f_uvc.c | 41 ++++++++++++++------------------- drivers/usb/gadget/function/uvc.h | 16 ++++++------- drivers/usb/gadget/function/uvc_v4l2.c | 4 ++-- drivers/usb/gadget/function/uvc_video.c | 18 +++++++++------ drivers/usb/gadget/function/uvc_video.h | 2 +- 5 files changed, 39 insertions(+), 42 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 4ea987741e6e..0cc4a6220050 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -232,13 +232,8 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) struct v4l2_event v4l2_event; struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; - /* printk(KERN_INFO "setup request %02x %02x value %04x index %04x %04x\n", - * ctrl->bRequestType, ctrl->bRequest, le16_to_cpu(ctrl->wValue), - * le16_to_cpu(ctrl->wIndex), le16_to_cpu(ctrl->wLength)); - */ - if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) { - INFO(f->config->cdev, "invalid request type\n"); + uvcg_info(f, "invalid request type\n"); return -EINVAL; } @@ -272,7 +267,7 @@ uvc_function_get_alt(struct usb_function *f, unsigned interface) { struct uvc_device *uvc = to_uvc(f); - INFO(f->config->cdev, "uvc_function_get_alt(%u)\n", interface); + uvcg_info(f, "%s(%u)\n", __func__, interface); if (interface == uvc->control_intf) return 0; @@ -291,13 +286,13 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; int ret; - INFO(cdev, "uvc_function_set_alt(%u, %u)\n", interface, alt); + uvcg_info(f, "%s(%u, %u)\n", __func__, interface, alt); if (interface == uvc->control_intf) { if (alt) return -EINVAL; - INFO(cdev, "reset UVC Control\n"); + uvcg_info(f, "reset UVC Control\n"); usb_ep_disable(uvc->control_ep); if (!uvc->control_ep->desc) @@ -348,7 +343,7 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt) if (!uvc->video.ep) return -EINVAL; - INFO(cdev, "reset UVC\n"); + uvcg_info(f, "reset UVC\n"); usb_ep_disable(uvc->video.ep); ret = config_ep_by_speed(f->config->cdev->gadget, @@ -373,7 +368,7 @@ uvc_function_disable(struct usb_function *f) struct uvc_device *uvc = to_uvc(f); struct v4l2_event v4l2_event; - INFO(f->config->cdev, "uvc_function_disable\n"); + uvcg_info(f, "%s()\n", __func__); memset(&v4l2_event, 0, sizeof(v4l2_event)); v4l2_event.type = UVC_EVENT_DISCONNECT; @@ -392,21 +387,19 @@ uvc_function_disable(struct usb_function *f) void uvc_function_connect(struct uvc_device *uvc) { - struct usb_composite_dev *cdev = uvc->func.config->cdev; int ret; if ((ret = usb_function_activate(&uvc->func)) < 0) - INFO(cdev, "UVC connect failed with %d\n", ret); + uvcg_info(&uvc->func, "UVC connect failed with %d\n", ret); } void uvc_function_disconnect(struct uvc_device *uvc) { - struct usb_composite_dev *cdev = uvc->func.config->cdev; int ret; if ((ret = usb_function_deactivate(&uvc->func)) < 0) - INFO(cdev, "UVC disconnect failed with %d\n", ret); + uvcg_info(&uvc->func, "UVC disconnect failed with %d\n", ret); } /* -------------------------------------------------------------------------- @@ -605,7 +598,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) struct f_uvc_opts *opts; int ret = -EINVAL; - INFO(cdev, "uvc_function_bind\n"); + uvcg_info(f, "%s()\n", __func__); opts = fi_to_f_uvc_opts(f->fi); /* Sanity check the streaming endpoint module parameters. @@ -618,8 +611,8 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) if (opts->streaming_maxburst && (opts->streaming_maxpacket % 1024) != 0) { opts->streaming_maxpacket = roundup(opts->streaming_maxpacket, 1024); - INFO(cdev, "overriding streaming_maxpacket to %d\n", - opts->streaming_maxpacket); + uvcg_info(f, "overriding streaming_maxpacket to %d\n", + opts->streaming_maxpacket); } /* Fill in the FS/HS/SS Video Streaming specific descriptors from the @@ -658,7 +651,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) /* Allocate endpoints. */ ep = usb_ep_autoconfig(cdev->gadget, &uvc_control_ep); if (!ep) { - INFO(cdev, "Unable to allocate control EP\n"); + uvcg_info(f, "Unable to allocate control EP\n"); goto error; } uvc->control_ep = ep; @@ -672,7 +665,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) ep = usb_ep_autoconfig(cdev->gadget, &uvc_fs_streaming_ep); if (!ep) { - INFO(cdev, "Unable to allocate streaming EP\n"); + uvcg_info(f, "Unable to allocate streaming EP\n"); goto error; } uvc->video.ep = ep; @@ -745,19 +738,19 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvc->control_req->context = uvc; if (v4l2_device_register(&cdev->gadget->dev, &uvc->v4l2_dev)) { - printk(KERN_INFO "v4l2_device_register failed\n"); + uvcg_err(f, "failed to register V4L2 device\n"); goto error; } /* Initialise video. */ - ret = uvcg_video_init(&uvc->video); + ret = uvcg_video_init(&uvc->video, uvc); if (ret < 0) goto error; /* Register a V4L2 device. */ ret = uvc_register_video(uvc); if (ret < 0) { - printk(KERN_INFO "Unable to register video device\n"); + uvcg_err(f, "failed to register video device\n"); goto error; } @@ -894,7 +887,7 @@ static void uvc_unbind(struct usb_configuration *c, struct usb_function *f) struct usb_composite_dev *cdev = c->cdev; struct uvc_device *uvc = to_uvc(f); - INFO(cdev, "%s\n", __func__); + uvcg_info(f, "%s\n", __func__); device_remove_file(&uvc->vdev.dev, &dev_attr_function_name); video_unregister_device(&uvc->vdev); diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h index 93cf78b420fe..099d650082e5 100644 --- a/drivers/usb/gadget/function/uvc.h +++ b/drivers/usb/gadget/function/uvc.h @@ -24,6 +24,7 @@ struct usb_ep; struct usb_request; struct uvc_descriptor_header; +struct uvc_device; /* ------------------------------------------------------------------------ * Debugging, printing and logging @@ -51,14 +52,12 @@ extern unsigned int uvc_gadget_trace_param; printk(KERN_DEBUG "uvcvideo: " msg); \ } while (0) -#define uvc_warn_once(dev, warn, msg...) \ - do { \ - if (!test_and_set_bit(warn, &dev->warnings)) \ - printk(KERN_INFO "uvcvideo: " msg); \ - } while (0) - -#define uvc_printk(level, msg...) \ - printk(level "uvcvideo: " msg) +#define uvcg_dbg(f, fmt, args...) \ + dev_dbg(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args) +#define uvcg_info(f, fmt, args...) \ + dev_info(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args) +#define uvcg_err(f, fmt, args...) \ + dev_err(&(f)->config->cdev->gadget->dev, "%s: " fmt, (f)->name, ##args) /* ------------------------------------------------------------------------ * Driver specific constants @@ -73,6 +72,7 @@ extern unsigned int uvc_gadget_trace_param; */ struct uvc_video { + struct uvc_device *uvc; struct usb_ep *ep; /* Frame parameters */ diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c index 7f1ca3b57823..a1183eccee22 100644 --- a/drivers/usb/gadget/function/uvc_v4l2.c +++ b/drivers/usb/gadget/function/uvc_v4l2.c @@ -115,8 +115,8 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt) } if (i == ARRAY_SIZE(uvc_formats)) { - printk(KERN_INFO "Unsupported format 0x%08x.\n", - fmt->fmt.pix.pixelformat); + uvcg_info(&uvc->func, "Unsupported format 0x%08x.\n", + fmt->fmt.pix.pixelformat); return -EINVAL; } diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c index 2c9821ec836e..5c042f380708 100644 --- a/drivers/usb/gadget/function/uvc_video.c +++ b/drivers/usb/gadget/function/uvc_video.c @@ -131,7 +131,9 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req) ret = usb_ep_queue(video->ep, req, GFP_ATOMIC); if (ret < 0) { - printk(KERN_INFO "Failed to queue request (%d).\n", ret); + uvcg_err(&video->uvc->func, "Failed to queue request (%d).\n", + ret); + /* Isochronous endpoints can't be halted. */ if (usb_endpoint_xfer_bulk(video->ep->desc)) usb_ep_set_halt(video->ep); @@ -184,13 +186,14 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req) break; case -ESHUTDOWN: /* disconnect from host. */ - printk(KERN_DEBUG "VS request cancelled.\n"); + uvcg_dbg(&video->uvc->func, "VS request cancelled.\n"); uvcg_queue_cancel(queue, 1); goto requeue; default: - printk(KERN_INFO "VS request completed with status %d.\n", - req->status); + uvcg_info(&video->uvc->func, + "VS request completed with status %d.\n", + req->status); uvcg_queue_cancel(queue, 0); goto requeue; } @@ -354,8 +357,8 @@ int uvcg_video_enable(struct uvc_video *video, int enable) int ret; if (video->ep == NULL) { - printk(KERN_INFO "Video enable failed, device is " - "uninitialized.\n"); + uvcg_info(&video->uvc->func, + "Video enable failed, device is uninitialized.\n"); return -ENODEV; } @@ -387,11 +390,12 @@ int uvcg_video_enable(struct uvc_video *video, int enable) /* * Initialize the UVC video stream. */ -int uvcg_video_init(struct uvc_video *video) +int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc) { INIT_LIST_HEAD(&video->req_free); spin_lock_init(&video->req_lock); + video->uvc = uvc; video->fcc = V4L2_PIX_FMT_YUYV; video->bpp = 16; video->width = 320; diff --git a/drivers/usb/gadget/function/uvc_video.h b/drivers/usb/gadget/function/uvc_video.h index 7d77122b0ff9..278dc52c7604 100644 --- a/drivers/usb/gadget/function/uvc_video.h +++ b/drivers/usb/gadget/function/uvc_video.h @@ -18,6 +18,6 @@ int uvcg_video_pump(struct uvc_video *video); int uvcg_video_enable(struct uvc_video *video, int enable); -int uvcg_video_init(struct uvc_video *video); +int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc); #endif /* __UVC_VIDEO_H__ */ -- cgit v1.2.3-58-ga151 From d865d00db9e69eb2e6f7c4166e33e5047de497bc Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Tue, 18 Sep 2018 14:26:47 +0300 Subject: usb: gadget: uvc: Remove uvc_set_trace_param() function The function is never called, remove it. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> --- drivers/usb/gadget/function/f_uvc.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 0cc4a6220050..8c99392df593 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -197,12 +197,6 @@ static const struct usb_descriptor_header * const uvc_ss_streaming[] = { NULL, }; -void uvc_set_trace_param(unsigned int trace) -{ - uvc_gadget_trace_param = trace; -} -EXPORT_SYMBOL(uvc_set_trace_param); - /* -------------------------------------------------------------------------- * Control requests */ -- cgit v1.2.3-58-ga151 From 78c9e7ce00c3fabf5c6dd3a6eab3a3dcf21dd213 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Tue, 18 Sep 2018 14:40:58 +0300 Subject: usb: gadget: uvc: configfs: Fix operation on big endian platforms USB descriptors are stored in little endian, requiring the use of conversion macros. Those macros are incorrectly used for values stored in native endian structures within the driver. Operation on big endian platforms is thus broken. Fix it by removing the conversion macros where they're not needed. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Tested-by: Paul Elder <paul.elder@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 59 ++++++++++++------------------ 1 file changed, 24 insertions(+), 35 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 6031467f1868..522cb7be9850 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -1105,7 +1105,7 @@ static struct uvcg_frame *to_uvcg_frame(struct config_item *item) return container_of(item, struct uvcg_frame, item); } -#define UVCG_FRAME_ATTR(cname, aname, to_cpu_endian, to_little_endian, bits) \ +#define UVCG_FRAME_ATTR(cname, aname, bits) \ static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\ { \ struct uvcg_frame *f = to_uvcg_frame(item); \ @@ -1120,7 +1120,7 @@ static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", to_cpu_endian(f->frame.cname)); \ + result = sprintf(page, "%d\n", f->frame.cname); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1154,7 +1154,7 @@ static ssize_t uvcg_frame_##cname##_store(struct config_item *item, \ goto end; \ } \ \ - f->frame.cname = to_little_endian(num); \ + f->frame.cname = num; \ ret = len; \ end: \ mutex_unlock(&opts->lock); \ @@ -1199,20 +1199,13 @@ out: UVC_ATTR_RO(uvcg_frame_, b_frame_index, bFrameIndex); -#define noop_conversion(x) (x) - -UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, noop_conversion, - noop_conversion, 8); -UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, cpu_to_le16, 16); -UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, cpu_to_le16, 16); -UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, cpu_to_le32, 32); -UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, cpu_to_le32, 32); -UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, - le32_to_cpu, cpu_to_le32, 32); -UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, - le32_to_cpu, cpu_to_le32, 32); - -#undef noop_conversion +UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, 8); +UVCG_FRAME_ATTR(w_width, wWidth, 16); +UVCG_FRAME_ATTR(w_height, wHeight, 16); +UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, 32); +UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, 32); +UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize, 32); +UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval, 32); #undef UVCG_FRAME_ATTR @@ -1233,8 +1226,7 @@ static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item, mutex_lock(&opts->lock); for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) { - result += sprintf(pg, "%d\n", - le32_to_cpu(frm->dw_frame_interval[i])); + result += sprintf(pg, "%d\n", frm->dw_frame_interval[i]); pg = page + result; } mutex_unlock(&opts->lock); @@ -1259,7 +1251,7 @@ static inline int __uvcg_fill_frm_intrv(char *buf, void *priv) return ret; interv = priv; - **interv = cpu_to_le32(num); + **interv = num; ++*interv; return 0; @@ -1381,12 +1373,12 @@ static struct config_item *uvcg_frame_make(struct config_group *group, h->frame.b_descriptor_type = USB_DT_CS_INTERFACE; h->frame.b_frame_index = 1; - h->frame.w_width = cpu_to_le16(640); - h->frame.w_height = cpu_to_le16(360); - h->frame.dw_min_bit_rate = cpu_to_le32(18432000); - h->frame.dw_max_bit_rate = cpu_to_le32(55296000); - h->frame.dw_max_video_frame_buffer_size = cpu_to_le32(460800); - h->frame.dw_default_frame_interval = cpu_to_le32(666666); + h->frame.w_width = 640; + h->frame.w_height = 360; + h->frame.dw_min_bit_rate = 18432000; + h->frame.dw_max_bit_rate = 55296000; + h->frame.dw_max_video_frame_buffer_size = 460800; + h->frame.dw_default_frame_interval = 666666; opts_item = group->cg_item.ci_parent->ci_parent->ci_parent; opts = to_f_uvc_opts(opts_item); @@ -2427,7 +2419,7 @@ static struct configfs_item_operations uvc_func_item_ops = { .release = uvc_func_item_release, }; -#define UVCG_OPTS_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +#define UVCG_OPTS_ATTR(cname, aname, str2u, uxx, limit) \ static ssize_t f_uvc_opts_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -2435,7 +2427,7 @@ static ssize_t f_uvc_opts_##cname##_show( \ int result; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(opts->cname)); \ + result = sprintf(page, "%d\n", opts->cname); \ mutex_unlock(&opts->lock); \ \ return result; \ @@ -2463,7 +2455,7 @@ f_uvc_opts_##cname##_store(struct config_item *item, \ ret = -EINVAL; \ goto end; \ } \ - opts->cname = vnoc(num); \ + opts->cname = num; \ ret = len; \ end: \ mutex_unlock(&opts->lock); \ @@ -2474,12 +2466,9 @@ UVC_ATTR(f_uvc_opts_, cname, cname) #define identity_conv(x) (x) -UVCG_OPTS_ATTR(streaming_interval, streaming_interval, identity_conv, - kstrtou8, u8, identity_conv, 16); -UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, le16_to_cpu, - kstrtou16, u16, le16_to_cpu, 3072); -UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, identity_conv, - kstrtou8, u8, identity_conv, 15); +UVCG_OPTS_ATTR(streaming_interval, streaming_interval, kstrtou8, u8, 16); +UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, kstrtou16, u16, 3072); +UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, kstrtou8, u8, 15); #undef identity_conv -- cgit v1.2.3-58-ga151 From 4f2a6552c2888b317984e64b292f0f1ca1832d44 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Tue, 18 Sep 2018 15:04:16 +0300 Subject: usb: gadget: uvc: configfs: Simplify attributes macros Several macros used to define attributes and their access functions take multiple arguments to specify endianness and string conversion functions, based on the size of the attribute. This can be simplified by passing the number of bits explicitly, and constructing the name of the functions internally. The UVCG_OPTS_ATTR macro can be simplified further as all fields it deals with are unsigned int. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Tested-by: Paul Elder <paul.elder@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 184 ++++++++++++----------------- 1 file changed, 74 insertions(+), 110 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 522cb7be9850..36f8f03e25e4 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -38,6 +38,9 @@ static struct configfs_attribute prefix##attr_##cname = { \ .show = prefix##cname##_show, \ } +#define le8_to_cpu(x) (x) +#define cpu_to_le8(x) (x) + static int uvcg_config_compare_u32(const void *l, const void *r) { u32 li = *(const u32 *)l; @@ -135,9 +138,9 @@ static struct uvcg_control_header *to_uvcg_control_header(struct config_item *it return container_of(item, struct uvcg_control_header, item); } -#define UVCG_CTRL_HDR_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit) \ +#define UVCG_CTRL_HDR_ATTR(cname, aname, bits, limit) \ static ssize_t uvcg_control_header_##cname##_show( \ - struct config_item *item, char *page) \ + struct config_item *item, char *page) \ { \ struct uvcg_control_header *ch = to_uvcg_control_header(item); \ struct f_uvc_opts *opts; \ @@ -151,7 +154,7 @@ static ssize_t uvcg_control_header_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(ch->desc.aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(ch->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -167,7 +170,7 @@ uvcg_control_header_##cname##_store(struct config_item *item, \ struct config_item *opts_item; \ struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\ int ret; \ - uxx num; \ + u##bits num; \ \ mutex_lock(su_mutex); /* for navigating configfs hierarchy */ \ \ @@ -180,7 +183,7 @@ uvcg_control_header_##cname##_store(struct config_item *item, \ goto end; \ } \ \ - ret = str2u(page, 0, &num); \ + ret = kstrtou##bits(page, 0, &num); \ if (ret) \ goto end; \ \ @@ -188,7 +191,7 @@ uvcg_control_header_##cname##_store(struct config_item *item, \ ret = -EINVAL; \ goto end; \ } \ - ch->desc.aname = vnoc(num); \ + ch->desc.aname = cpu_to_le##bits(num); \ ret = len; \ end: \ mutex_unlock(&opts->lock); \ @@ -198,11 +201,9 @@ end: \ \ UVC_ATTR(uvcg_control_header_, cname, aname) -UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, le16_to_cpu, kstrtou16, u16, cpu_to_le16, - 0xffff); +UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, 16, 0xffff); -UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, le32_to_cpu, kstrtou32, - u32, cpu_to_le32, 0x7fffffff); +UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, 32, 0x7fffffff); #undef UVCG_CTRL_HDR_ATTR @@ -255,7 +256,7 @@ static const struct uvcg_config_group_type uvcg_control_header_grp_type = { * control/processing/default */ -#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv) \ +#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, bits) \ static ssize_t uvcg_default_processing_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -273,7 +274,7 @@ static ssize_t uvcg_default_processing_##cname##_show( \ pd = &opts->uvc_processing; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(pd->aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(pd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -282,14 +283,10 @@ static ssize_t uvcg_default_processing_##cname##_show( \ \ UVC_ATTR_RO(uvcg_default_processing_, cname, aname) -#define identity_conv(x) (x) - -UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, identity_conv); -UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, identity_conv); -UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, le16_to_cpu); -UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, identity_conv); - -#undef identity_conv +UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, 8); +UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, 8); +UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, 16); +UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, 8); #undef UVCG_DEFAULT_PROCESSING_ATTR @@ -362,7 +359,7 @@ static const struct uvcg_config_group_type uvcg_processing_grp_type = { * control/terminal/camera/default */ -#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv) \ +#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, bits) \ static ssize_t uvcg_default_camera_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -381,7 +378,7 @@ static ssize_t uvcg_default_camera_##cname##_show( \ cd = &opts->uvc_camera_terminal; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(cd->aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(cd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -391,20 +388,16 @@ static ssize_t uvcg_default_camera_##cname##_show( \ \ UVC_ATTR_RO(uvcg_default_camera_, cname, aname) -#define identity_conv(x) (x) - -UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, identity_conv); -UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, le16_to_cpu); -UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv); -UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, identity_conv); +UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, 8); +UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, 16); +UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, 8); +UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, 8); UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin, - le16_to_cpu); + 16); UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax, - le16_to_cpu); + 16); UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength, - le16_to_cpu); - -#undef identity_conv + 16); #undef UVCG_DEFAULT_CAMERA_ATTR @@ -480,7 +473,7 @@ static const struct uvcg_config_group_type uvcg_camera_grp_type = { * control/terminal/output/default */ -#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv) \ +#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, bits) \ static ssize_t uvcg_default_output_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -499,7 +492,7 @@ static ssize_t uvcg_default_output_##cname##_show( \ cd = &opts->uvc_output_terminal; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(cd->aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(cd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -509,15 +502,11 @@ static ssize_t uvcg_default_output_##cname##_show( \ \ UVC_ATTR_RO(uvcg_default_output_, cname, aname) -#define identity_conv(x) (x) - -UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, identity_conv); -UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, le16_to_cpu); -UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv); -UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, identity_conv); -UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, identity_conv); - -#undef identity_conv +UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, 8); +UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, 16); +UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, 8); +UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, 8); +UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, 8); #undef UVCG_DEFAULT_OUTPUT_ATTR @@ -990,9 +979,9 @@ static struct configfs_item_operations uvcg_streaming_header_item_ops = { .drop_link = uvcg_streaming_header_drop_link, }; -#define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv) \ +#define UVCG_STREAMING_HEADER_ATTR(cname, aname, bits) \ static ssize_t uvcg_streaming_header_##cname##_show( \ - struct config_item *item, char *page) \ + struct config_item *item, char *page) \ { \ struct uvcg_streaming_header *sh = to_uvcg_streaming_header(item); \ struct f_uvc_opts *opts; \ @@ -1006,7 +995,7 @@ static ssize_t uvcg_streaming_header_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(sh->desc.aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(sh->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1015,16 +1004,11 @@ static ssize_t uvcg_streaming_header_##cname##_show( \ \ UVC_ATTR_RO(uvcg_streaming_header_, cname, aname) -#define identity_conv(x) (x) - -UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, identity_conv); -UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, identity_conv); -UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod, - identity_conv); -UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, identity_conv); -UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, identity_conv); - -#undef identity_conv +UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, 8); +UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, 8); +UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod, 8); +UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, 8); +UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, 8); #undef UVCG_STREAMING_HEADER_ATTR @@ -1135,8 +1119,8 @@ static ssize_t uvcg_frame_##cname##_store(struct config_item *item, \ struct config_item *opts_item; \ struct uvcg_format *fmt; \ struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\ + typeof(f->frame.cname) num; \ int ret; \ - u##bits num; \ \ ret = kstrtou##bits(page, 0, &num); \ if (ret) \ @@ -1512,7 +1496,7 @@ end: UVC_ATTR(uvcg_uncompressed_, guid_format, guidFormat); -#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, conv) \ +#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, bits) \ static ssize_t uvcg_uncompressed_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -1528,7 +1512,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1537,7 +1521,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \ \ UVC_ATTR_RO(uvcg_uncompressed_, cname, aname); -#define UVCG_UNCOMPRESSED_ATTR(cname, aname, conv) \ +#define UVCG_UNCOMPRESSED_ATTR(cname, aname, bits) \ static ssize_t uvcg_uncompressed_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -1553,7 +1537,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1600,17 +1584,12 @@ end: \ \ UVC_ATTR(uvcg_uncompressed_, cname, aname); -#define identity_conv(x) (x) - -UVCG_UNCOMPRESSED_ATTR_RO(b_format_index, bFormatIndex, identity_conv); -UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, identity_conv); -UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, - identity_conv); -UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv); -UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv); -UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv); - -#undef identity_conv +UVCG_UNCOMPRESSED_ATTR_RO(b_format_index, bFormatIndex, 8); +UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, 8); +UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex, 8); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8); +UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8); +UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8); #undef UVCG_UNCOMPRESSED_ATTR #undef UVCG_UNCOMPRESSED_ATTR_RO @@ -1716,7 +1695,7 @@ static struct configfs_group_operations uvcg_mjpeg_group_ops = { .drop_item = uvcg_frame_drop, }; -#define UVCG_MJPEG_ATTR_RO(cname, aname, conv) \ +#define UVCG_MJPEG_ATTR_RO(cname, aname, bits) \ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ { \ struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); \ @@ -1731,7 +1710,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1740,7 +1719,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ \ UVC_ATTR_RO(uvcg_mjpeg_, cname, aname) -#define UVCG_MJPEG_ATTR(cname, aname, conv) \ +#define UVCG_MJPEG_ATTR(cname, aname, bits) \ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ { \ struct uvcg_mjpeg *u = to_uvcg_mjpeg(item); \ @@ -1755,7 +1734,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(u->desc.aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1802,17 +1781,12 @@ end: \ \ UVC_ATTR(uvcg_mjpeg_, cname, aname) -#define identity_conv(x) (x) - -UVCG_MJPEG_ATTR_RO(b_format_index, bFormatIndex, identity_conv); -UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, - identity_conv); -UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, identity_conv); -UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv); -UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv); -UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv); - -#undef identity_conv +UVCG_MJPEG_ATTR_RO(b_format_index, bFormatIndex, 8); +UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex, 8); +UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, 8); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, 8); +UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, 8); +UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, 8); #undef UVCG_MJPEG_ATTR #undef UVCG_MJPEG_ATTR_RO @@ -1894,9 +1868,9 @@ static const struct uvcg_config_group_type uvcg_mjpeg_grp_type = { * streaming/color_matching/default */ -#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv) \ +#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, bits) \ static ssize_t uvcg_default_color_matching_##cname##_show( \ - struct config_item *item, char *page) \ + struct config_item *item, char *page) \ { \ struct config_group *group = to_config_group(item); \ struct f_uvc_opts *opts; \ @@ -1912,7 +1886,7 @@ static ssize_t uvcg_default_color_matching_##cname##_show( \ cd = &opts->uvc_color_matching; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", conv(cd->aname)); \ + result = sprintf(page, "%d\n", le##bits##_to_cpu(cd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1921,16 +1895,10 @@ static ssize_t uvcg_default_color_matching_##cname##_show( \ \ UVC_ATTR_RO(uvcg_default_color_matching_, cname, aname) -#define identity_conv(x) (x) - -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, - identity_conv); +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries, 8); UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics, - bTransferCharacteristics, identity_conv); -UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, - identity_conv); - -#undef identity_conv + bTransferCharacteristics, 8); +UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients, 8); #undef UVCG_DEFAULT_COLOR_MATCHING_ATTR @@ -2419,7 +2387,7 @@ static struct configfs_item_operations uvc_func_item_ops = { .release = uvc_func_item_release, }; -#define UVCG_OPTS_ATTR(cname, aname, str2u, uxx, limit) \ +#define UVCG_OPTS_ATTR(cname, aname, limit) \ static ssize_t f_uvc_opts_##cname##_show( \ struct config_item *item, char *page) \ { \ @@ -2438,8 +2406,8 @@ f_uvc_opts_##cname##_store(struct config_item *item, \ const char *page, size_t len) \ { \ struct f_uvc_opts *opts = to_f_uvc_opts(item); \ + unsigned int num; \ int ret; \ - uxx num; \ \ mutex_lock(&opts->lock); \ if (opts->refcnt) { \ @@ -2447,7 +2415,7 @@ f_uvc_opts_##cname##_store(struct config_item *item, \ goto end; \ } \ \ - ret = str2u(page, 0, &num); \ + ret = kstrtouint(page, 0, &num); \ if (ret) \ goto end; \ \ @@ -2464,13 +2432,9 @@ end: \ \ UVC_ATTR(f_uvc_opts_, cname, cname) -#define identity_conv(x) (x) - -UVCG_OPTS_ATTR(streaming_interval, streaming_interval, kstrtou8, u8, 16); -UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, kstrtou16, u16, 3072); -UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, kstrtou8, u8, 15); - -#undef identity_conv +UVCG_OPTS_ATTR(streaming_interval, streaming_interval, 16); +UVCG_OPTS_ATTR(streaming_maxpacket, streaming_maxpacket, 3072); +UVCG_OPTS_ATTR(streaming_maxburst, streaming_maxburst, 15); #undef UVCG_OPTS_ATTR -- cgit v1.2.3-58-ga151 From 3fb2fd76eda265ce5421318de38dd9b9f7c54737 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Date: Tue, 18 Sep 2018 15:04:16 +0300 Subject: usb: gadget: uvc: configfs: Use %u to print unsigned int values The driver uses the %d format to print unsigned int values. The correct format is %u. Fix it. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> Tested-by: Paul Elder <paul.elder@ideasonboard.com> --- drivers/usb/gadget/function/uvc_configfs.c | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/uvc_configfs.c b/drivers/usb/gadget/function/uvc_configfs.c index 36f8f03e25e4..bc1e2af566c3 100644 --- a/drivers/usb/gadget/function/uvc_configfs.c +++ b/drivers/usb/gadget/function/uvc_configfs.c @@ -154,7 +154,7 @@ static ssize_t uvcg_control_header_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(ch->desc.aname));\ + result = sprintf(page, "%u\n", le##bits##_to_cpu(ch->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -274,7 +274,7 @@ static ssize_t uvcg_default_processing_##cname##_show( \ pd = &opts->uvc_processing; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(pd->aname)); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(pd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -309,7 +309,7 @@ static ssize_t uvcg_default_processing_bm_controls_show( mutex_lock(&opts->lock); for (result = 0, i = 0; i < pd->bControlSize; ++i) { - result += sprintf(pg, "%d\n", pd->bmControls[i]); + result += sprintf(pg, "%u\n", pd->bmControls[i]); pg = page + result; } mutex_unlock(&opts->lock); @@ -378,7 +378,7 @@ static ssize_t uvcg_default_camera_##cname##_show( \ cd = &opts->uvc_camera_terminal; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(cd->aname)); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -421,7 +421,7 @@ static ssize_t uvcg_default_camera_bm_controls_show( mutex_lock(&opts->lock); for (result = 0, i = 0; i < cd->bControlSize; ++i) { - result += sprintf(pg, "%d\n", cd->bmControls[i]); + result += sprintf(pg, "%u\n", cd->bmControls[i]); pg = page + result; } mutex_unlock(&opts->lock); @@ -492,7 +492,7 @@ static ssize_t uvcg_default_output_##cname##_show( \ cd = &opts->uvc_output_terminal; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(cd->aname)); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -995,7 +995,7 @@ static ssize_t uvcg_streaming_header_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(sh->desc.aname));\ + result = sprintf(page, "%u\n", le##bits##_to_cpu(sh->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1104,7 +1104,7 @@ static ssize_t uvcg_frame_##cname##_show(struct config_item *item, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", f->frame.cname); \ + result = sprintf(page, "%u\n", f->frame.cname); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1173,7 +1173,7 @@ static ssize_t uvcg_frame_b_frame_index_show(struct config_item *item, opts = to_f_uvc_opts(opts_item); mutex_lock(&opts->lock); - result = sprintf(page, "%d\n", f->frame.b_frame_index); + result = sprintf(page, "%u\n", f->frame.b_frame_index); mutex_unlock(&opts->lock); out: @@ -1210,7 +1210,7 @@ static ssize_t uvcg_frame_dw_frame_interval_show(struct config_item *item, mutex_lock(&opts->lock); for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) { - result += sprintf(pg, "%d\n", frm->dw_frame_interval[i]); + result += sprintf(pg, "%u\n", frm->dw_frame_interval[i]); pg = page + result; } mutex_unlock(&opts->lock); @@ -1512,7 +1512,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1537,7 +1537,7 @@ static ssize_t uvcg_uncompressed_##cname##_show( \ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1710,7 +1710,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1734,7 +1734,7 @@ static ssize_t uvcg_mjpeg_##cname##_show(struct config_item *item, char *page)\ opts = to_f_uvc_opts(opts_item); \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(u->desc.aname));\ + result = sprintf(page, "%u\n", le##bits##_to_cpu(u->desc.aname));\ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -1886,7 +1886,7 @@ static ssize_t uvcg_default_color_matching_##cname##_show( \ cd = &opts->uvc_color_matching; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", le##bits##_to_cpu(cd->aname)); \ + result = sprintf(page, "%u\n", le##bits##_to_cpu(cd->aname)); \ mutex_unlock(&opts->lock); \ \ mutex_unlock(su_mutex); \ @@ -2395,7 +2395,7 @@ static ssize_t f_uvc_opts_##cname##_show( \ int result; \ \ mutex_lock(&opts->lock); \ - result = sprintf(page, "%d\n", opts->cname); \ + result = sprintf(page, "%u\n", opts->cname); \ mutex_unlock(&opts->lock); \ \ return result; \ -- cgit v1.2.3-58-ga151 From ba93cc7da8965bd513a7393db0f030e51bff4b60 Mon Sep 17 00:00:00 2001 From: Karoly Pados <pados@pados.hu> Date: Tue, 25 Sep 2018 15:59:11 +0200 Subject: USB: serial: ftdi_sio: implement GPIO support for FT-X devices This patch allows using the CBUS pins of FT-X devices as GPIO in CBUS bitbanging mode. There is no conflict between the GPIO and VCP functionality in this mode. Tested on FT230X and FT231X. As there is no way to request the current CBUS register configuration from the device, all CBUS pins are set to a known state when the first GPIO is requested. This allows using libftdi to set the GPIO pins before loading this module for UART functionality, a behavior that existing applications might be relying upon (though no specific case is known to the authors of this patch). Signed-off-by: Karoly Pados <pados@pados.hu> [ johan: minor style changes ] Signed-off-by: Johan Hovold <johan@kernel.org> --- drivers/usb/serial/ftdi_sio.c | 360 +++++++++++++++++++++++++++++++++++++++++- drivers/usb/serial/ftdi_sio.h | 27 +++- 2 files changed, 385 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index b5cef322826f..6b727ada20cf 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -39,6 +39,7 @@ #include <linux/uaccess.h> #include <linux/usb.h> #include <linux/serial.h> +#include <linux/gpio/driver.h> #include <linux/usb/serial.h> #include "ftdi_sio.h" #include "ftdi_sio_ids.h" @@ -72,6 +73,15 @@ struct ftdi_private { unsigned int latency; /* latency setting in use */ unsigned short max_packet_size; struct mutex cfg_lock; /* Avoid mess by parallel calls of config ioctl() and change_speed() */ +#ifdef CONFIG_GPIOLIB + struct gpio_chip gc; + struct mutex gpio_lock; /* protects GPIO state */ + bool gpio_registered; /* is the gpiochip in kernel registered */ + bool gpio_used; /* true if the user requested a gpio */ + u8 gpio_altfunc; /* which pins are in gpio mode */ + u8 gpio_output; /* pin directions cache */ + u8 gpio_value; /* pin value for outputs */ +#endif }; /* struct ftdi_sio_quirk is used by devices requiring special attention. */ @@ -1766,6 +1776,344 @@ static void remove_sysfs_attrs(struct usb_serial_port *port) } +#ifdef CONFIG_GPIOLIB + +static const char * const ftdi_ftx_gpio_names[] = { + "CBUS0", "CBUS1", "CBUS2", "CBUS3" +}; + +static int ftdi_set_bitmode(struct usb_serial_port *port, u8 mode) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int result; + u16 val; + + val = (mode << 8) | (priv->gpio_output << 4) | priv->gpio_value; + result = usb_control_msg(serial->dev, + usb_sndctrlpipe(serial->dev, 0), + FTDI_SIO_SET_BITMODE_REQUEST, + FTDI_SIO_SET_BITMODE_REQUEST_TYPE, val, + priv->interface, NULL, 0, WDR_TIMEOUT); + if (result < 0) { + dev_err(&serial->interface->dev, + "bitmode request failed for value 0x%04x: %d\n", + val, result); + } + + return result; +} + +static int ftdi_set_cbus_pins(struct usb_serial_port *port) +{ + return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_CBUS); +} + +static int ftdi_exit_cbus_mode(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + priv->gpio_output = 0; + priv->gpio_value = 0; + return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_RESET); +} + +static int ftdi_gpio_request(struct gpio_chip *gc, unsigned int offset) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + if (priv->gpio_altfunc & BIT(offset)) + return -ENODEV; + + mutex_lock(&priv->gpio_lock); + if (!priv->gpio_used) { + /* Set default pin states, as we cannot get them from device */ + priv->gpio_output = 0x00; + priv->gpio_value = 0x00; + result = ftdi_set_cbus_pins(port); + if (result) { + mutex_unlock(&priv->gpio_lock); + return result; + } + + priv->gpio_used = true; + } + mutex_unlock(&priv->gpio_lock); + + return 0; +} + +static int ftdi_read_cbus_pins(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + unsigned char *buf; + int result; + + buf = kmalloc(1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + result = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + FTDI_SIO_READ_PINS_REQUEST, + FTDI_SIO_READ_PINS_REQUEST_TYPE, 0, + priv->interface, buf, 1, WDR_TIMEOUT); + if (result < 1) { + if (result >= 0) + result = -EIO; + } else { + result = buf[0]; + } + + kfree(buf); + + return result; +} + +static int ftdi_gpio_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + int result; + + result = ftdi_read_cbus_pins(port); + if (result < 0) + return result; + + return !!(result & BIT(gpio)); +} + +static void ftdi_gpio_set(struct gpio_chip *gc, unsigned int gpio, int value) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + + mutex_lock(&priv->gpio_lock); + + if (value) + priv->gpio_value |= BIT(gpio); + else + priv->gpio_value &= ~BIT(gpio); + + ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); +} + +static int ftdi_gpio_get_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + int result; + + result = ftdi_read_cbus_pins(port); + if (result < 0) + return result; + + *bits = result & *mask; + + return 0; +} + +static void ftdi_gpio_set_multiple(struct gpio_chip *gc, unsigned long *mask, + unsigned long *bits) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + + mutex_lock(&priv->gpio_lock); + + priv->gpio_value &= ~(*mask); + priv->gpio_value |= *bits & *mask; + ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); +} + +static int ftdi_gpio_direction_get(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + + return !(priv->gpio_output & BIT(gpio)); +} + +static int ftdi_gpio_direction_input(struct gpio_chip *gc, unsigned int gpio) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + mutex_lock(&priv->gpio_lock); + + priv->gpio_output &= ~BIT(gpio); + result = ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); + + return result; +} + +static int ftdi_gpio_direction_output(struct gpio_chip *gc, unsigned int gpio, + int value) +{ + struct usb_serial_port *port = gpiochip_get_data(gc); + struct ftdi_private *priv = usb_get_serial_port_data(port); + int result; + + mutex_lock(&priv->gpio_lock); + + priv->gpio_output |= BIT(gpio); + if (value) + priv->gpio_value |= BIT(gpio); + else + priv->gpio_value &= ~BIT(gpio); + + result = ftdi_set_cbus_pins(port); + + mutex_unlock(&priv->gpio_lock); + + return result; +} + +static int ftdi_read_eeprom(struct usb_serial *serial, void *dst, u16 addr, + u16 nbytes) +{ + int read = 0; + + if (addr % 2 != 0) + return -EINVAL; + if (nbytes % 2 != 0) + return -EINVAL; + + /* Read EEPROM two bytes at a time */ + while (read < nbytes) { + int rv; + + rv = usb_control_msg(serial->dev, + usb_rcvctrlpipe(serial->dev, 0), + FTDI_SIO_READ_EEPROM_REQUEST, + FTDI_SIO_READ_EEPROM_REQUEST_TYPE, + 0, (addr + read) / 2, dst + read, 2, + WDR_TIMEOUT); + if (rv < 2) { + if (rv >= 0) + return -EIO; + else + return rv; + } + + read += rv; + } + + return 0; +} + +static int ftx_gpioconf_init(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + const u16 cbus_cfg_addr = 0x1a; + const u16 cbus_cfg_size = 4; + u8 *cbus_cfg_buf; + int result; + u8 i; + + cbus_cfg_buf = kmalloc(cbus_cfg_size, GFP_KERNEL); + if (!cbus_cfg_buf) + return -ENOMEM; + + result = ftdi_read_eeprom(serial, cbus_cfg_buf, + cbus_cfg_addr, cbus_cfg_size); + if (result < 0) + goto out_free; + + /* FIXME: FT234XD alone has 1 GPIO, but how to recognize this IC? */ + priv->gc.ngpio = 4; + priv->gc.names = ftdi_ftx_gpio_names; + + /* Determine which pins are configured for CBUS bitbanging */ + priv->gpio_altfunc = 0xff; + for (i = 0; i < priv->gc.ngpio; ++i) { + if (cbus_cfg_buf[i] == FTDI_FTX_CBUS_MUX_GPIO) + priv->gpio_altfunc &= ~BIT(i); + } + +out_free: + kfree(cbus_cfg_buf); + + return result; +} + +static int ftdi_gpio_init(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + struct usb_serial *serial = port->serial; + int result; + + switch (priv->chip_type) { + case FTX: + result = ftx_gpioconf_init(port); + break; + default: + return 0; + } + + if (result < 0) + return result; + + mutex_init(&priv->gpio_lock); + + priv->gc.label = "ftdi-cbus"; + priv->gc.request = ftdi_gpio_request; + priv->gc.get_direction = ftdi_gpio_direction_get; + priv->gc.direction_input = ftdi_gpio_direction_input; + priv->gc.direction_output = ftdi_gpio_direction_output; + priv->gc.get = ftdi_gpio_get; + priv->gc.set = ftdi_gpio_set; + priv->gc.get_multiple = ftdi_gpio_get_multiple; + priv->gc.set_multiple = ftdi_gpio_set_multiple; + priv->gc.owner = THIS_MODULE; + priv->gc.parent = &serial->interface->dev; + priv->gc.base = -1; + priv->gc.can_sleep = true; + + result = gpiochip_add_data(&priv->gc, port); + if (!result) + priv->gpio_registered = true; + + return result; +} + +static void ftdi_gpio_remove(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + + if (priv->gpio_registered) { + gpiochip_remove(&priv->gc); + priv->gpio_registered = false; + } + + if (priv->gpio_used) { + /* Exiting CBUS-mode does not reset pin states. */ + ftdi_exit_cbus_mode(port); + priv->gpio_used = false; + } +} + +#else + +static int ftdi_gpio_init(struct usb_serial_port *port) +{ + return 0; +} + +static void ftdi_gpio_remove(struct usb_serial_port *port) { } + +#endif /* CONFIG_GPIOLIB */ + /* * *************************************************************************** * FTDI driver specific functions @@ -1794,7 +2142,7 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port) { struct ftdi_private *priv; const struct ftdi_sio_quirk *quirk = usb_get_serial_data(port->serial); - + int result; priv = kzalloc(sizeof(struct ftdi_private), GFP_KERNEL); if (!priv) @@ -1813,6 +2161,14 @@ static int ftdi_sio_port_probe(struct usb_serial_port *port) priv->latency = 16; write_latency_timer(port); create_sysfs_attrs(port); + + result = ftdi_gpio_init(port); + if (result < 0) { + dev_err(&port->serial->interface->dev, + "GPIO initialisation failed: %d\n", + result); + } + return 0; } @@ -1930,6 +2286,8 @@ static int ftdi_sio_port_remove(struct usb_serial_port *port) { struct ftdi_private *priv = usb_get_serial_port_data(port); + ftdi_gpio_remove(port); + remove_sysfs_attrs(port); kfree(priv); diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index dcd0b6e05baf..6cfe682f8348 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -35,7 +35,10 @@ #define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ #define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ #define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ -#define FTDI_SIO_GET_LATENCY_TIMER 10 /* Get the latency timer */ +#define FTDI_SIO_GET_LATENCY_TIMER 0x0a /* Get the latency timer */ +#define FTDI_SIO_SET_BITMODE 0x0b /* Set bitbang mode */ +#define FTDI_SIO_READ_PINS 0x0c /* Read immediate value of pins */ +#define FTDI_SIO_READ_EEPROM 0x90 /* Read EEPROM */ /* Interface indices for FT2232, FT2232H and FT4232H devices */ #define INTERFACE_A 1 @@ -433,6 +436,28 @@ enum ftdi_sio_baudrate { * 1 = active */ +/* FTDI_SIO_SET_BITMODE */ +#define FTDI_SIO_SET_BITMODE_REQUEST_TYPE 0x40 +#define FTDI_SIO_SET_BITMODE_REQUEST FTDI_SIO_SET_BITMODE + +/* Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST */ +#define FTDI_SIO_BITMODE_RESET 0x00 +#define FTDI_SIO_BITMODE_CBUS 0x20 + +/* FTDI_SIO_READ_PINS */ +#define FTDI_SIO_READ_PINS_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_PINS_REQUEST FTDI_SIO_READ_PINS + +/* + * FTDI_SIO_READ_EEPROM + * + * EEPROM format found in FTDI AN_201, "FT-X MTP memory Configuration", + * http://www.ftdichip.com/Support/Documents/AppNotes/AN_201_FT-X%20MTP%20Memory%20Configuration.pdf + */ +#define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 +#define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM + +#define FTDI_FTX_CBUS_MUX_GPIO 8 /* Descriptors returned by the device -- cgit v1.2.3-58-ga151 From a0ef2bdfa3b1497ac3d0cb348102c87c51f041a9 Mon Sep 17 00:00:00 2001 From: Corentin Labbe <clabbe@baylibre.com> Date: Wed, 19 Sep 2018 19:48:53 +0000 Subject: usb: host: Replace empty define with do while MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's dangerous to use empty code define. Furthermore it lead to the following warning: "suggest braces around empty body in an « else » statement" So let's replace emptyness by "do {} while(0)" Furthermore, as suggested by Joe Perches, rename the macro to INCR. Signed-off-by: Corentin Labbe <clabbe@baylibre.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/ehci-hcd.c | 6 +++--- drivers/usb/host/ehci-q.c | 4 ++-- drivers/usb/host/ehci-timer.c | 2 +- drivers/usb/host/ehci.h | 4 ++-- drivers/usb/host/fotg210-hcd.c | 12 ++++++------ drivers/usb/host/fotg210.h | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index e8d7667828eb..cdafa97f632d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -730,9 +730,9 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) /* normal [4.15.1.2] or error [4.15.1.1] completion */ if (likely ((status & (STS_INT|STS_ERR)) != 0)) { if (likely ((status & STS_ERR) == 0)) - COUNT (ehci->stats.normal); + INCR(ehci->stats.normal); else - COUNT (ehci->stats.error); + INCR(ehci->stats.error); bh = 1; } @@ -756,7 +756,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) if (cmd & CMD_IAAD) ehci_dbg(ehci, "IAA with IAAD still set?\n"); if (ehci->iaa_in_progress) - COUNT(ehci->stats.iaa); + INCR(ehci->stats.iaa); end_iaa_cycle(ehci); } diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 327630405695..aa2f77f1506d 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -245,12 +245,12 @@ ehci_urb_done(struct ehci_hcd *ehci, struct urb *urb, int status) } if (unlikely(urb->unlinked)) { - COUNT(ehci->stats.unlink); + INCR(ehci->stats.unlink); } else { /* report non-error and short read status as zero */ if (status == -EINPROGRESS || status == -EREMOTEIO) status = 0; - COUNT(ehci->stats.complete); + INCR(ehci->stats.complete); } #ifdef EHCI_URB_TRACE diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 4fcebda4b79d..a79c8ac0a55f 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -347,7 +347,7 @@ static void ehci_iaa_watchdog(struct ehci_hcd *ehci) */ status = ehci_readl(ehci, &ehci->regs->status); if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { - COUNT(ehci->stats.lost_iaa); + INCR(ehci->stats.lost_iaa); ehci_writel(ehci, STS_IAA, &ehci->regs->status); } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index c8e9a48e1d51..ac5e967907d1 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -235,9 +235,9 @@ struct ehci_hcd { /* one per controller */ /* irq statistics */ #ifdef EHCI_STATS struct ehci_stats stats; -# define COUNT(x) ((x)++) +# define INCR(x) ((x)++) #else -# define COUNT(x) +# define INCR(x) do {} while (0) #endif /* debug files */ diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index bbcc68179bfc..0da68df259c8 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -1286,7 +1286,7 @@ static void fotg210_iaa_watchdog(struct fotg210_hcd *fotg210) */ status = fotg210_readl(fotg210, &fotg210->regs->status); if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { - COUNT(fotg210->stats.lost_iaa); + INCR(fotg210->stats.lost_iaa); fotg210_writel(fotg210, STS_IAA, &fotg210->regs->status); } @@ -2205,12 +2205,12 @@ __acquires(fotg210->lock) } if (unlikely(urb->unlinked)) { - COUNT(fotg210->stats.unlink); + INCR(fotg210->stats.unlink); } else { /* report non-error and short read status as zero */ if (status == -EINPROGRESS || status == -EREMOTEIO) status = 0; - COUNT(fotg210->stats.complete); + INCR(fotg210->stats.complete); } #ifdef FOTG210_URB_TRACE @@ -5154,9 +5154,9 @@ static irqreturn_t fotg210_irq(struct usb_hcd *hcd) /* normal [4.15.1.2] or error [4.15.1.1] completion */ if (likely((status & (STS_INT|STS_ERR)) != 0)) { if (likely((status & STS_ERR) == 0)) - COUNT(fotg210->stats.normal); + INCR(fotg210->stats.normal); else - COUNT(fotg210->stats.error); + INCR(fotg210->stats.error); bh = 1; } @@ -5181,7 +5181,7 @@ static irqreturn_t fotg210_irq(struct usb_hcd *hcd) if (cmd & CMD_IAAD) fotg210_dbg(fotg210, "IAA with IAAD still set?\n"); if (fotg210->async_iaa) { - COUNT(fotg210->stats.iaa); + INCR(fotg210->stats.iaa); end_unlink_async(fotg210); } else fotg210_dbg(fotg210, "IAA with nothing unlinked?\n"); diff --git a/drivers/usb/host/fotg210.h b/drivers/usb/host/fotg210.h index 28f6467c0cbf..1b4db95e5c43 100644 --- a/drivers/usb/host/fotg210.h +++ b/drivers/usb/host/fotg210.h @@ -177,9 +177,9 @@ struct fotg210_hcd { /* one per controller */ /* irq statistics */ #ifdef FOTG210_STATS struct fotg210_stats stats; -# define COUNT(x) ((x)++) +# define INCR(x) ((x)++) #else -# define COUNT(x) +# define INCR(x) do {} while (0) #endif /* silicon clock */ -- cgit v1.2.3-58-ga151 From 7aae9990de20445e31f463836f26fdd02571740b Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Thu, 20 Sep 2018 19:13:31 +0300 Subject: usb: xhci-mtk: use maximum ESIT payload of endpiont context Make use of maximum ESIT payload of endpoint context to calculate the number of packets to send in each ESIT Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-mtk-sch.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index fa33d6e5b1cb..46fe0a200ca7 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -73,12 +73,17 @@ static void setup_sch_info(struct usb_device *udev, u32 max_burst; u32 mult; u32 esit_pkts; + u32 max_esit_payload; ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2)); ep_interval = CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info)); max_packet_size = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); max_burst = CTX_TO_MAX_BURST(le32_to_cpu(ep_ctx->ep_info2)); mult = CTX_TO_EP_MULT(le32_to_cpu(ep_ctx->ep_info)); + max_esit_payload = + (CTX_TO_MAX_ESIT_PAYLOAD_HI( + le32_to_cpu(ep_ctx->ep_info)) << 16) | + CTX_TO_MAX_ESIT_PAYLOAD(le32_to_cpu(ep_ctx->tx_info)); sch_ep->esit = 1 << ep_interval; sch_ep->offset = 0; @@ -105,7 +110,15 @@ static void setup_sch_info(struct usb_device *udev, } else if (udev->speed == USB_SPEED_SUPER) { /* usb3_r1 spec section4.4.7 & 4.4.8 */ sch_ep->cs_count = 0; - esit_pkts = (mult + 1) * (max_burst + 1); + /* + * some device's (d)wBytesPerInterval is set as 0, + * then max_esit_payload is 0, so evaluate esit_pkts from + * mult and burst + */ + esit_pkts = DIV_ROUND_UP(max_esit_payload, max_packet_size); + if (esit_pkts == 0) + esit_pkts = (mult + 1) * (max_burst + 1); + if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { sch_ep->pkts = esit_pkts; sch_ep->num_budget_microframes = 1; -- cgit v1.2.3-58-ga151 From 87173acc0d8f0987bda8827da35fff67f52ad15d Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Thu, 20 Sep 2018 19:13:32 +0300 Subject: usb: xhci-mtk: fix ISOC error when interval is zero If the interval equal zero, needn't round up to power of two for the number of packets in each ESIT, so fix it. Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-mtk-sch.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 46fe0a200ca7..057f453e06c5 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -126,7 +126,9 @@ static void setup_sch_info(struct usb_device *udev, } if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) { - if (esit_pkts <= sch_ep->esit) + if (sch_ep->esit == 1) + sch_ep->pkts = esit_pkts; + else if (esit_pkts <= sch_ep->esit) sch_ep->pkts = 1; else sch_ep->pkts = roundup_pow_of_two(esit_pkts) -- cgit v1.2.3-58-ga151 From 95b516c18621d1626662bc161cbbf6281fd8d767 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Thu, 20 Sep 2018 19:13:33 +0300 Subject: usb: xhci-mtk: improve bandwidth scheduling Mainly improve SuperSpeed ISOC bandwidth in last microframe, and LowSpeed/FullSpeed IN INT/ISOC bandwidth in split and idle microframes by introduing a bandwidth budget table; Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-mtk-sch.c | 162 +++++++++++++++++++++++++--------------- drivers/usb/host/xhci-mtk.h | 2 + 2 files changed, 104 insertions(+), 60 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 057f453e06c5..7efd8901b2db 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -18,6 +18,11 @@ #define HS_BW_BOUNDARY 6144 /* usb2 spec section11.18.1: at most 188 FS bytes per microframe */ #define FS_PAYLOAD_MAX 188 +/* + * max number of microframes for split transfer, + * for fs isoc in : 1 ss + 1 idle + 7 cs + */ +#define TT_MICROFRAMES_MAX 9 /* mtk scheduler bitmasks */ #define EP_BPKTS(p) ((p) & 0x3f) @@ -64,20 +69,57 @@ static int get_bw_index(struct xhci_hcd *xhci, struct usb_device *udev, return bw_index; } +static u32 get_esit(struct xhci_ep_ctx *ep_ctx) +{ + u32 esit; + + esit = 1 << CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info)); + if (esit > XHCI_MTK_MAX_ESIT) + esit = XHCI_MTK_MAX_ESIT; + + return esit; +} + +static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, + struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx) +{ + struct mu3h_sch_ep_info *sch_ep; + u32 len_bw_budget_table; + size_t mem_size; + + if (is_fs_or_ls(udev->speed)) + len_bw_budget_table = TT_MICROFRAMES_MAX; + else if ((udev->speed == USB_SPEED_SUPER) + && usb_endpoint_xfer_isoc(&ep->desc)) + len_bw_budget_table = get_esit(ep_ctx); + else + len_bw_budget_table = 1; + + mem_size = sizeof(struct mu3h_sch_ep_info) + + len_bw_budget_table * sizeof(u32); + sch_ep = kzalloc(mem_size, GFP_KERNEL); + if (!sch_ep) + return ERR_PTR(-ENOMEM); + + sch_ep->ep = ep; + + return sch_ep; +} + static void setup_sch_info(struct usb_device *udev, struct xhci_ep_ctx *ep_ctx, struct mu3h_sch_ep_info *sch_ep) { u32 ep_type; - u32 ep_interval; - u32 max_packet_size; + u32 maxpkt; u32 max_burst; u32 mult; u32 esit_pkts; u32 max_esit_payload; + u32 *bwb_table = sch_ep->bw_budget_table; + int i; ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2)); - ep_interval = CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info)); - max_packet_size = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); + maxpkt = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2)); max_burst = CTX_TO_MAX_BURST(le32_to_cpu(ep_ctx->ep_info2)); mult = CTX_TO_EP_MULT(le32_to_cpu(ep_ctx->ep_info)); max_esit_payload = @@ -85,9 +127,10 @@ static void setup_sch_info(struct usb_device *udev, le32_to_cpu(ep_ctx->ep_info)) << 16) | CTX_TO_MAX_ESIT_PAYLOAD(le32_to_cpu(ep_ctx->tx_info)); - sch_ep->esit = 1 << ep_interval; + sch_ep->esit = get_esit(ep_ctx); sch_ep->offset = 0; sch_ep->burst_mode = 0; + sch_ep->repeat = 0; if (udev->speed == USB_SPEED_HIGH) { sch_ep->cs_count = 0; @@ -98,7 +141,6 @@ static void setup_sch_info(struct usb_device *udev, * in a interval */ sch_ep->num_budget_microframes = 1; - sch_ep->repeat = 0; /* * xHCI spec section6.2.3.4 @@ -106,26 +148,30 @@ static void setup_sch_info(struct usb_device *udev, * opportunities per microframe */ sch_ep->pkts = max_burst + 1; - sch_ep->bw_cost_per_microframe = max_packet_size * sch_ep->pkts; + sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts; + bwb_table[0] = sch_ep->bw_cost_per_microframe; } else if (udev->speed == USB_SPEED_SUPER) { /* usb3_r1 spec section4.4.7 & 4.4.8 */ sch_ep->cs_count = 0; + sch_ep->burst_mode = 1; /* * some device's (d)wBytesPerInterval is set as 0, * then max_esit_payload is 0, so evaluate esit_pkts from * mult and burst */ - esit_pkts = DIV_ROUND_UP(max_esit_payload, max_packet_size); + esit_pkts = DIV_ROUND_UP(max_esit_payload, maxpkt); if (esit_pkts == 0) esit_pkts = (mult + 1) * (max_burst + 1); if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { sch_ep->pkts = esit_pkts; sch_ep->num_budget_microframes = 1; - sch_ep->repeat = 0; + bwb_table[0] = maxpkt * sch_ep->pkts; } if (ep_type == ISOC_IN_EP || ep_type == ISOC_OUT_EP) { + u32 remainder; + if (sch_ep->esit == 1) sch_ep->pkts = esit_pkts; else if (esit_pkts <= sch_ep->esit) @@ -137,43 +183,37 @@ static void setup_sch_info(struct usb_device *udev, sch_ep->num_budget_microframes = DIV_ROUND_UP(esit_pkts, sch_ep->pkts); - if (sch_ep->num_budget_microframes > 1) - sch_ep->repeat = 1; - else - sch_ep->repeat = 0; + sch_ep->repeat = !!(sch_ep->num_budget_microframes > 1); + sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts; + + remainder = sch_ep->bw_cost_per_microframe; + remainder *= sch_ep->num_budget_microframes; + remainder -= (maxpkt * esit_pkts); + for (i = 0; i < sch_ep->num_budget_microframes - 1; i++) + bwb_table[i] = sch_ep->bw_cost_per_microframe; + + /* last one <= bw_cost_per_microframe */ + bwb_table[i] = remainder; } - sch_ep->bw_cost_per_microframe = max_packet_size * sch_ep->pkts; } else if (is_fs_or_ls(udev->speed)) { - - /* - * usb_20 spec section11.18.4 - * assume worst cases - */ - sch_ep->repeat = 0; sch_ep->pkts = 1; /* at most one packet for each microframe */ - if (ep_type == INT_IN_EP || ep_type == INT_OUT_EP) { - sch_ep->cs_count = 3; /* at most need 3 CS*/ - /* one for SS and one for budgeted transaction */ - sch_ep->num_budget_microframes = sch_ep->cs_count + 2; - sch_ep->bw_cost_per_microframe = max_packet_size; - } - if (ep_type == ISOC_OUT_EP) { + sch_ep->cs_count = DIV_ROUND_UP(maxpkt, FS_PAYLOAD_MAX); + sch_ep->num_budget_microframes = sch_ep->cs_count + 2; + sch_ep->bw_cost_per_microframe = + (maxpkt < FS_PAYLOAD_MAX) ? maxpkt : FS_PAYLOAD_MAX; - /* - * the best case FS budget assumes that 188 FS bytes - * occur in each microframe - */ - sch_ep->num_budget_microframes = DIV_ROUND_UP( - max_packet_size, FS_PAYLOAD_MAX); - sch_ep->bw_cost_per_microframe = FS_PAYLOAD_MAX; - sch_ep->cs_count = sch_ep->num_budget_microframes; - } - if (ep_type == ISOC_IN_EP) { - /* at most need additional two CS. */ - sch_ep->cs_count = DIV_ROUND_UP( - max_packet_size, FS_PAYLOAD_MAX) + 2; - sch_ep->num_budget_microframes = sch_ep->cs_count + 2; - sch_ep->bw_cost_per_microframe = FS_PAYLOAD_MAX; + /* init budget table */ + if (ep_type == ISOC_OUT_EP) { + for (i = 0; i < sch_ep->num_budget_microframes; i++) + bwb_table[i] = sch_ep->bw_cost_per_microframe; + } else if (ep_type == INT_OUT_EP) { + /* only first one consumes bandwidth, others as zero */ + bwb_table[0] = sch_ep->bw_cost_per_microframe; + } else { /* INT_IN_EP or ISOC_IN_EP */ + bwb_table[0] = 0; /* start split */ + bwb_table[1] = 0; /* idle */ + for (i = 2; i < sch_ep->num_budget_microframes; i++) + bwb_table[i] = sch_ep->bw_cost_per_microframe; } } } @@ -184,6 +224,7 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, { u32 num_esit; u32 max_bw = 0; + u32 bw; int i; int j; @@ -192,15 +233,17 @@ static u32 get_max_bw(struct mu3h_sch_bw_info *sch_bw, u32 base = offset + i * sch_ep->esit; for (j = 0; j < sch_ep->num_budget_microframes; j++) { - if (sch_bw->bus_bw[base + j] > max_bw) - max_bw = sch_bw->bus_bw[base + j]; + bw = sch_bw->bus_bw[base + j] + + sch_ep->bw_budget_table[j]; + if (bw > max_bw) + max_bw = bw; } } return max_bw; } static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, - struct mu3h_sch_ep_info *sch_ep, int bw_cost) + struct mu3h_sch_ep_info *sch_ep, bool used) { u32 num_esit; u32 base; @@ -210,8 +253,14 @@ static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; for (i = 0; i < num_esit; i++) { base = sch_ep->offset + i * sch_ep->esit; - for (j = 0; j < sch_ep->num_budget_microframes; j++) - sch_bw->bus_bw[base + j] += bw_cost; + for (j = 0; j < sch_ep->num_budget_microframes; j++) { + if (used) + sch_bw->bus_bw[base + j] += + sch_ep->bw_budget_table[j]; + else + sch_bw->bus_bw[base + j] -= + sch_ep->bw_budget_table[j]; + } } } @@ -220,17 +269,12 @@ static int check_sch_bw(struct usb_device *udev, { u32 offset; u32 esit; - u32 num_budget_microframes; u32 min_bw; u32 min_index; u32 worst_bw; u32 bw_boundary; - if (sch_ep->esit > XHCI_MTK_MAX_ESIT) - sch_ep->esit = XHCI_MTK_MAX_ESIT; - esit = sch_ep->esit; - num_budget_microframes = sch_ep->num_budget_microframes; /* * Search through all possible schedule microframes. @@ -239,7 +283,7 @@ static int check_sch_bw(struct usb_device *udev, min_bw = ~0; min_index = 0; for (offset = 0; offset < esit; offset++) { - if ((offset + num_budget_microframes) > sch_ep->esit) + if ((offset + sch_ep->num_budget_microframes) > sch_ep->esit) break; /* @@ -263,11 +307,11 @@ static int check_sch_bw(struct usb_device *udev, ? SS_BW_BOUNDARY : HS_BW_BOUNDARY; /* check bandwidth */ - if (min_bw + sch_ep->bw_cost_per_microframe > bw_boundary) + if (min_bw > bw_boundary) return -ERANGE; /* update bus bandwidth info */ - update_bus_bw(sch_bw, sch_ep, sch_ep->bw_cost_per_microframe); + update_bus_bw(sch_bw, sch_ep, 1); return 0; } @@ -362,8 +406,8 @@ int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, bw_index = get_bw_index(xhci, udev, ep); sch_bw = &sch_array[bw_index]; - sch_ep = kzalloc(sizeof(struct mu3h_sch_ep_info), GFP_NOIO); - if (!sch_ep) + sch_ep = create_sch_ep(udev, ep, ep_ctx); + if (IS_ERR_OR_NULL(sch_ep)) return -ENOMEM; setup_sch_info(udev, ep_ctx, sch_ep); @@ -376,7 +420,6 @@ int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, } list_add_tail(&sch_ep->endpoint, &sch_bw->bw_ep_list); - sch_ep->ep = ep; ep_ctx->reserved[0] |= cpu_to_le32(EP_BPKTS(sch_ep->pkts) | EP_BCSCOUNT(sch_ep->cs_count) | EP_BBM(sch_ep->burst_mode)); @@ -421,8 +464,7 @@ void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, list_for_each_entry(sch_ep, &sch_bw->bw_ep_list, endpoint) { if (sch_ep->ep == ep) { - update_bus_bw(sch_bw, sch_ep, - -sch_ep->bw_cost_per_microframe); + update_bus_bw(sch_bw, sch_ep, 0); list_del(&sch_ep->endpoint); kfree(sch_ep); break; diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index cc59d80b663b..f8864fcbd461 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -57,6 +57,7 @@ struct mu3h_sch_bw_info { * times; 1: distribute the (bMaxBurst+1)*(Mult+1) packets * according to @pkts and @repeat. normal mode is used by * default + * @bw_budget_table: table to record bandwidth budget per microframe */ struct mu3h_sch_ep_info { u32 esit; @@ -73,6 +74,7 @@ struct mu3h_sch_ep_info { u32 pkts; u32 cs_count; u32 burst_mode; + u32 bw_budget_table[0]; }; #define MU3C_U3_PORT_MAX 4 -- cgit v1.2.3-58-ga151 From 08e469de87a2534fda7a4605d33a2f287bd74684 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Thu, 20 Sep 2018 19:13:34 +0300 Subject: usb: xhci-mtk: supports bandwidth scheduling with multi-TT Supports LowSpeed and FullSpeed INT/ISOC bandwidth scheduling with USB multi-TT Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-mtk-sch.c | 247 ++++++++++++++++++++++++++++++++++++++-- drivers/usb/host/xhci-mtk.h | 21 ++++ 2 files changed, 258 insertions(+), 10 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 7efd8901b2db..36050a1f8037 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -80,10 +80,98 @@ static u32 get_esit(struct xhci_ep_ctx *ep_ctx) return esit; } +static struct mu3h_sch_tt *find_tt(struct usb_device *udev) +{ + struct usb_tt *utt = udev->tt; + struct mu3h_sch_tt *tt, **tt_index, **ptt; + unsigned int port; + bool allocated_index = false; + + if (!utt) + return NULL; /* Not below a TT */ + + /* + * Find/create our data structure. + * For hubs with a single TT, we get it directly. + * For hubs with multiple TTs, there's an extra level of pointers. + */ + tt_index = NULL; + if (utt->multi) { + tt_index = utt->hcpriv; + if (!tt_index) { /* Create the index array */ + tt_index = kcalloc(utt->hub->maxchild, + sizeof(*tt_index), GFP_KERNEL); + if (!tt_index) + return ERR_PTR(-ENOMEM); + utt->hcpriv = tt_index; + allocated_index = true; + } + port = udev->ttport - 1; + ptt = &tt_index[port]; + } else { + port = 0; + ptt = (struct mu3h_sch_tt **) &utt->hcpriv; + } + + tt = *ptt; + if (!tt) { /* Create the mu3h_sch_tt */ + tt = kzalloc(sizeof(*tt), GFP_KERNEL); + if (!tt) { + if (allocated_index) { + utt->hcpriv = NULL; + kfree(tt_index); + } + return ERR_PTR(-ENOMEM); + } + INIT_LIST_HEAD(&tt->ep_list); + tt->usb_tt = utt; + tt->tt_port = port; + *ptt = tt; + } + + return tt; +} + +/* Release the TT above udev, if it's not in use */ +static void drop_tt(struct usb_device *udev) +{ + struct usb_tt *utt = udev->tt; + struct mu3h_sch_tt *tt, **tt_index, **ptt; + int i, cnt; + + if (!utt || !utt->hcpriv) + return; /* Not below a TT, or never allocated */ + + cnt = 0; + if (utt->multi) { + tt_index = utt->hcpriv; + ptt = &tt_index[udev->ttport - 1]; + /* How many entries are left in tt_index? */ + for (i = 0; i < utt->hub->maxchild; ++i) + cnt += !!tt_index[i]; + } else { + tt_index = NULL; + ptt = (struct mu3h_sch_tt **)&utt->hcpriv; + } + + tt = *ptt; + if (!tt || !list_empty(&tt->ep_list)) + return; /* never allocated , or still in use*/ + + *ptt = NULL; + kfree(tt); + + if (cnt == 1) { + utt->hcpriv = NULL; + kfree(tt_index); + } +} + static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, struct usb_host_endpoint *ep, struct xhci_ep_ctx *ep_ctx) { struct mu3h_sch_ep_info *sch_ep; + struct mu3h_sch_tt *tt = NULL; u32 len_bw_budget_table; size_t mem_size; @@ -101,6 +189,15 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, if (!sch_ep) return ERR_PTR(-ENOMEM); + if (is_fs_or_ls(udev->speed)) { + tt = find_tt(udev); + if (IS_ERR(tt)) { + kfree(sch_ep); + return ERR_PTR(-ENOMEM); + } + } + + sch_ep->sch_tt = tt; sch_ep->ep = ep; return sch_ep; @@ -128,6 +225,8 @@ static void setup_sch_info(struct usb_device *udev, CTX_TO_MAX_ESIT_PAYLOAD(le32_to_cpu(ep_ctx->tx_info)); sch_ep->esit = get_esit(ep_ctx); + sch_ep->ep_type = ep_type; + sch_ep->maxpkt = maxpkt; sch_ep->offset = 0; sch_ep->burst_mode = 0; sch_ep->repeat = 0; @@ -197,8 +296,13 @@ static void setup_sch_info(struct usb_device *udev, } } else if (is_fs_or_ls(udev->speed)) { sch_ep->pkts = 1; /* at most one packet for each microframe */ + + /* + * num_budget_microframes and cs_count will be updated when + * check TT for INT_OUT_EP, ISOC/INT_IN_EP type + */ sch_ep->cs_count = DIV_ROUND_UP(maxpkt, FS_PAYLOAD_MAX); - sch_ep->num_budget_microframes = sch_ep->cs_count + 2; + sch_ep->num_budget_microframes = sch_ep->cs_count; sch_ep->bw_cost_per_microframe = (maxpkt < FS_PAYLOAD_MAX) ? maxpkt : FS_PAYLOAD_MAX; @@ -212,7 +316,13 @@ static void setup_sch_info(struct usb_device *udev, } else { /* INT_IN_EP or ISOC_IN_EP */ bwb_table[0] = 0; /* start split */ bwb_table[1] = 0; /* idle */ - for (i = 2; i < sch_ep->num_budget_microframes; i++) + /* + * due to cs_count will be updated according to cs + * position, assign all remainder budget array + * elements as @bw_cost_per_microframe, but only first + * @num_budget_microframes elements will be used later + */ + for (i = 2; i < TT_MICROFRAMES_MAX; i++) bwb_table[i] = sch_ep->bw_cost_per_microframe; } } @@ -264,6 +374,96 @@ static void update_bus_bw(struct mu3h_sch_bw_info *sch_bw, } } +static int check_sch_tt(struct usb_device *udev, + struct mu3h_sch_ep_info *sch_ep, u32 offset) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + u32 extra_cs_count; + u32 fs_budget_start; + u32 start_ss, last_ss; + u32 start_cs, last_cs; + int i; + + start_ss = offset % 8; + fs_budget_start = (start_ss + 1) % 8; + + if (sch_ep->ep_type == ISOC_OUT_EP) { + last_ss = start_ss + sch_ep->cs_count - 1; + + /* + * usb_20 spec section11.18: + * must never schedule Start-Split in Y6 + */ + if (!(start_ss == 7 || last_ss < 6)) + return -ERANGE; + + for (i = 0; i < sch_ep->cs_count; i++) + if (test_bit(offset + i, tt->split_bit_map)) + return -ERANGE; + + } else { + u32 cs_count = DIV_ROUND_UP(sch_ep->maxpkt, FS_PAYLOAD_MAX); + + /* + * usb_20 spec section11.18: + * must never schedule Start-Split in Y6 + */ + if (start_ss == 6) + return -ERANGE; + + /* one uframe for ss + one uframe for idle */ + start_cs = (start_ss + 2) % 8; + last_cs = start_cs + cs_count - 1; + + if (last_cs > 7) + return -ERANGE; + + if (sch_ep->ep_type == ISOC_IN_EP) + extra_cs_count = (last_cs == 7) ? 1 : 2; + else /* ep_type : INTR IN / INTR OUT */ + extra_cs_count = (fs_budget_start == 6) ? 1 : 2; + + cs_count += extra_cs_count; + if (cs_count > 7) + cs_count = 7; /* HW limit */ + + for (i = 0; i < cs_count + 2; i++) { + if (test_bit(offset + i, tt->split_bit_map)) + return -ERANGE; + } + + sch_ep->cs_count = cs_count; + /* one for ss, the other for idle */ + sch_ep->num_budget_microframes = cs_count + 2; + + /* + * if interval=1, maxp >752, num_budge_micoframe is larger + * than sch_ep->esit, will overstep boundary + */ + if (sch_ep->num_budget_microframes > sch_ep->esit) + sch_ep->num_budget_microframes = sch_ep->esit; + } + + return 0; +} + +static void update_sch_tt(struct usb_device *udev, + struct mu3h_sch_ep_info *sch_ep) +{ + struct mu3h_sch_tt *tt = sch_ep->sch_tt; + u32 base, num_esit; + int i, j; + + num_esit = XHCI_MTK_MAX_ESIT / sch_ep->esit; + for (i = 0; i < num_esit; i++) { + base = sch_ep->offset + i * sch_ep->esit; + for (j = 0; j < sch_ep->num_budget_microframes; j++) + set_bit(base + j, tt->split_bit_map); + } + + list_add_tail(&sch_ep->tt_endpoint, &tt->ep_list); +} + static int check_sch_bw(struct usb_device *udev, struct mu3h_sch_bw_info *sch_bw, struct mu3h_sch_ep_info *sch_ep) { @@ -273,6 +473,10 @@ static int check_sch_bw(struct usb_device *udev, u32 min_index; u32 worst_bw; u32 bw_boundary; + u32 min_num_budget; + u32 min_cs_count; + bool tt_offset_ok = false; + int ret; esit = sch_ep->esit; @@ -282,26 +486,30 @@ static int check_sch_bw(struct usb_device *udev, */ min_bw = ~0; min_index = 0; + min_cs_count = sch_ep->cs_count; + min_num_budget = sch_ep->num_budget_microframes; for (offset = 0; offset < esit; offset++) { + if (is_fs_or_ls(udev->speed)) { + ret = check_sch_tt(udev, sch_ep, offset); + if (ret) + continue; + else + tt_offset_ok = true; + } + if ((offset + sch_ep->num_budget_microframes) > sch_ep->esit) break; - /* - * usb_20 spec section11.18: - * must never schedule Start-Split in Y6 - */ - if (is_fs_or_ls(udev->speed) && (offset % 8 == 6)) - continue; - worst_bw = get_max_bw(sch_bw, sch_ep, offset); if (min_bw > worst_bw) { min_bw = worst_bw; min_index = offset; + min_cs_count = sch_ep->cs_count; + min_num_budget = sch_ep->num_budget_microframes; } if (min_bw == 0) break; } - sch_ep->offset = min_index; bw_boundary = (udev->speed == USB_SPEED_SUPER) ? SS_BW_BOUNDARY : HS_BW_BOUNDARY; @@ -310,6 +518,18 @@ static int check_sch_bw(struct usb_device *udev, if (min_bw > bw_boundary) return -ERANGE; + sch_ep->offset = min_index; + sch_ep->cs_count = min_cs_count; + sch_ep->num_budget_microframes = min_num_budget; + + if (is_fs_or_ls(udev->speed)) { + /* all offset for tt is not ok*/ + if (!tt_offset_ok) + return -ERANGE; + + update_sch_tt(udev, sch_ep); + } + /* update bus bandwidth info */ update_bus_bw(sch_bw, sch_ep, 1); @@ -415,6 +635,9 @@ int xhci_mtk_add_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, ret = check_sch_bw(udev, sch_bw, sch_ep); if (ret) { xhci_err(xhci, "Not enough bandwidth!\n"); + if (is_fs_or_ls(udev->speed)) + drop_tt(udev); + kfree(sch_ep); return -ENOSPC; } @@ -466,6 +689,10 @@ void xhci_mtk_drop_ep_quirk(struct usb_hcd *hcd, struct usb_device *udev, if (sch_ep->ep == ep) { update_bus_bw(sch_bw, sch_ep, 0); list_del(&sch_ep->endpoint); + if (is_fs_or_ls(udev->speed)) { + list_del(&sch_ep->tt_endpoint); + drop_tt(udev); + } kfree(sch_ep); break; } diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index f8864fcbd461..8be8c5f7ff62 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -19,6 +19,19 @@ */ #define XHCI_MTK_MAX_ESIT 64 +/** + * @split_bit_map: used to avoid split microframes overlay + * @ep_list: Endpoints using this TT + * @usb_tt: usb TT related + * @tt_port: TT port number + */ +struct mu3h_sch_tt { + DECLARE_BITMAP(split_bit_map, XHCI_MTK_MAX_ESIT); + struct list_head ep_list; + struct usb_tt *usb_tt; + int tt_port; +}; + /** * struct mu3h_sch_bw_info: schedule information for bandwidth domain * @@ -41,6 +54,10 @@ struct mu3h_sch_bw_info { * (@repeat==1) scheduled within the interval * @bw_cost_per_microframe: bandwidth cost per microframe * @endpoint: linked into bandwidth domain which it belongs to + * @tt_endpoint: linked into mu3h_sch_tt's list which it belongs to + * @sch_tt: mu3h_sch_tt linked into + * @ep_type: endpoint type + * @maxpkt: max packet size of endpoint * @ep: address of usb_host_endpoint struct * @offset: which uframe of the interval that transfer should be * scheduled first time within the interval @@ -64,6 +81,10 @@ struct mu3h_sch_ep_info { u32 num_budget_microframes; u32 bw_cost_per_microframe; struct list_head endpoint; + struct list_head tt_endpoint; + struct mu3h_sch_tt *sch_tt; + u32 ep_type; + u32 maxpkt; void *ep; /* * mtk xHCI scheduling information put into reserved DWs -- cgit v1.2.3-58-ga151 From e995dccadaf99e216cf5410463941d978c455c58 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Thu, 20 Sep 2018 19:13:35 +0300 Subject: usb: xhci-mtk: supports SSP without external USB3 gen2 hub Supports SSP scheduling only for SSP device directly connected to root hub but not through external USB3 gen2 hub which need use a new scheduling way. Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-mtk-sch.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-mtk-sch.c b/drivers/usb/host/xhci-mtk-sch.c index 36050a1f8037..fea555570ad4 100644 --- a/drivers/usb/host/xhci-mtk-sch.c +++ b/drivers/usb/host/xhci-mtk-sch.c @@ -13,6 +13,7 @@ #include "xhci.h" #include "xhci-mtk.h" +#define SSP_BW_BOUNDARY 130000 #define SS_BW_BOUNDARY 51000 /* table 5-5. High-speed Isoc Transaction Limits in usb_20 spec */ #define HS_BW_BOUNDARY 6144 @@ -25,7 +26,7 @@ #define TT_MICROFRAMES_MAX 9 /* mtk scheduler bitmasks */ -#define EP_BPKTS(p) ((p) & 0x3f) +#define EP_BPKTS(p) ((p) & 0x7f) #define EP_BCSCOUNT(p) (((p) & 0x7) << 8) #define EP_BBM(p) ((p) << 11) #define EP_BOFFSET(p) ((p) & 0x3fff) @@ -56,7 +57,7 @@ static int get_bw_index(struct xhci_hcd *xhci, struct usb_device *udev, virt_dev = xhci->devs[udev->slot_id]; - if (udev->speed == USB_SPEED_SUPER) { + if (udev->speed >= USB_SPEED_SUPER) { if (usb_endpoint_dir_out(&ep->desc)) bw_index = (virt_dev->real_port - 1) * 2; else @@ -177,7 +178,7 @@ static struct mu3h_sch_ep_info *create_sch_ep(struct usb_device *udev, if (is_fs_or_ls(udev->speed)) len_bw_budget_table = TT_MICROFRAMES_MAX; - else if ((udev->speed == USB_SPEED_SUPER) + else if ((udev->speed >= USB_SPEED_SUPER) && usb_endpoint_xfer_isoc(&ep->desc)) len_bw_budget_table = get_esit(ep_ctx); else @@ -249,7 +250,7 @@ static void setup_sch_info(struct usb_device *udev, sch_ep->pkts = max_burst + 1; sch_ep->bw_cost_per_microframe = maxpkt * sch_ep->pkts; bwb_table[0] = sch_ep->bw_cost_per_microframe; - } else if (udev->speed == USB_SPEED_SUPER) { + } else if (udev->speed >= USB_SPEED_SUPER) { /* usb3_r1 spec section4.4.7 & 4.4.8 */ sch_ep->cs_count = 0; sch_ep->burst_mode = 1; @@ -511,8 +512,12 @@ static int check_sch_bw(struct usb_device *udev, break; } - bw_boundary = (udev->speed == USB_SPEED_SUPER) - ? SS_BW_BOUNDARY : HS_BW_BOUNDARY; + if (udev->speed == USB_SPEED_SUPER_PLUS) + bw_boundary = SSP_BW_BOUNDARY; + else if (udev->speed == USB_SPEED_SUPER) + bw_boundary = SS_BW_BOUNDARY; + else + bw_boundary = HS_BW_BOUNDARY; /* check bandwidth */ if (min_bw > bw_boundary) -- cgit v1.2.3-58-ga151 From c94d41e9dd1ba3f95ca958ff04adeb64e3d6a9be Mon Sep 17 00:00:00 2001 From: Peter Chen <peter.chen@nxp.com> Date: Thu, 20 Sep 2018 19:13:36 +0300 Subject: usb: host: xhci-plat: add platform TPL support The TPL support is used to identify targeted devices during EH2.0 and EH3.0 certification test, the user can add "tpl-support" at dts to enable this feature. Signed-off-by: Peter Chen <peter.chen@nxp.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-plat.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index 94e939249b2b..32b5574ad5c5 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -18,6 +18,7 @@ #include <linux/usb/phy.h> #include <linux/slab.h> #include <linux/acpi.h> +#include <linux/usb/of.h> #include "xhci.h" #include "xhci-plat.h" @@ -305,6 +306,8 @@ static int xhci_plat_probe(struct platform_device *pdev) hcd->skip_phy_initialization = 1; } + hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node); + xhci->shared_hcd->tpl_support = hcd->tpl_support; ret = usb_add_hcd(hcd, irq, IRQF_SHARED); if (ret) goto disable_usb_phy; -- cgit v1.2.3-58-ga151 From f8f80be501aa2f10669585c3e328fad079d8cb3a Mon Sep 17 00:00:00 2001 From: Mathias Nyman <mathias.nyman@linux.intel.com> Date: Thu, 20 Sep 2018 19:13:37 +0300 Subject: xhci: Use soft retry to recover faster from transaction errors Use soft retry to recover from a USB Transaction Errors that are caused by temporary error conditions. The USB device is not aware that the xHC has halted the endpoint, and will be waiting for another retry A Soft Retry perform additional retries and recover from an error which has caused the xHC to halt an endpoint. Soft retry has some limitations: Soft Retry attempts shall not be performed on Isoch endpoints Soft Retry attempts shall not be performed if the device is behind a TT in a HS Hub Software shall limit the number of unsuccessful Soft Retry attempts to prevent an infinite loop. For more details on Soft retry see xhci specs 4.6.8.1 Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-ring.c | 19 +++++++++++++++++++ drivers/usb/host/xhci.h | 2 ++ 2 files changed, 21 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index f0a99aa0ac58..c41341ea8449 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1155,6 +1155,10 @@ static void xhci_handle_cmd_reset_ep(struct xhci_hcd *xhci, int slot_id, /* Clear our internal halted state */ xhci->devs[slot_id]->eps[ep_index].ep_state &= ~EP_HALTED; } + + /* if this was a soft reset, then restart */ + if ((le32_to_cpu(trb->generic.field[3])) & TRB_TSP) + ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } static void xhci_handle_cmd_enable_slot(struct xhci_hcd *xhci, int slot_id, @@ -2132,10 +2136,16 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, struct xhci_virt_ep *ep, int *status) { + struct xhci_slot_ctx *slot_ctx; struct xhci_ring *ep_ring; u32 trb_comp_code; u32 remaining, requested, ep_trb_len; + unsigned int slot_id; + int ep_index; + slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + slot_ctx = xhci_get_slot_ctx(xhci, xhci->devs[slot_id]->out_ctx); + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); remaining = EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); @@ -2144,6 +2154,7 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, switch (trb_comp_code) { case COMP_SUCCESS: + ep_ring->err_count = 0; /* handle success with untransferred data as short packet */ if (ep_trb != td->last_trb || remaining) { xhci_warn(xhci, "WARN Successful completion on short TX\n"); @@ -2167,6 +2178,14 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, ep_trb_len = 0; remaining = 0; break; + case COMP_USB_TRANSACTION_ERROR: + if ((ep_ring->err_count++ > MAX_SOFT_RETRY) || + le32_to_cpu(slot_ctx->tt_info) & TT_SLOT) + break; + *status = 0; + xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, + ep_ring->stream_id, td, EP_SOFT_RESET); + return 0; default: /* do nothing */ break; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 6230a578324c..b63578548bef 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1496,6 +1496,7 @@ static inline const char *xhci_trb_type_string(u8 type) /* How much data is left before the 64KB boundary? */ #define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_BUFF_SIZE - \ (addr & (TRB_MAX_BUFF_SIZE - 1))) +#define MAX_SOFT_RETRY 3 struct xhci_segment { union xhci_trb *trbs; @@ -1583,6 +1584,7 @@ struct xhci_ring { * if we own the TRB (if we are the consumer). See section 4.9.1. */ u32 cycle_state; + unsigned int err_count; unsigned int stream_id; unsigned int num_segs; unsigned int num_trbs_free; -- cgit v1.2.3-58-ga151 From 2815ef7fe4d43072b9eda448d04fbc184f2aa513 Mon Sep 17 00:00:00 2001 From: Mathias Nyman <mathias.nyman@linux.intel.com> Date: Thu, 20 Sep 2018 19:13:38 +0300 Subject: xhci-pci: allow host runtime PM as default for Intel Alpine and Titan Ridge The xhci controller on Alpine and Titan Ridge keeps the whole thunderbolt awake if the host controller is not allowed tp sleep. This is the case even if no USB devices are connected to the host. Because of this bigger impact, allow runtime pm as default for these xhci controllers in the driver. Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-pci.c | 24 ++++++++++++++++++++++++ drivers/usb/host/xhci.h | 1 + 2 files changed, 25 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 6372edf339d9..9433e70aeeb0 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -41,6 +41,13 @@ #define PCI_DEVICE_ID_INTEL_BROXTON_B_XHCI 0x1aa8 #define PCI_DEVICE_ID_INTEL_APL_XHCI 0x5aa8 #define PCI_DEVICE_ID_INTEL_DNV_XHCI 0x19d0 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_XHCI 0x15b5 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_XHCI 0x15b6 +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_XHCI 0x15db +#define PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_XHCI 0x15d4 +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_XHCI 0x15e9 +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_XHCI 0x15ec +#define PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_XHCI 0x15f0 #define PCI_DEVICE_ID_AMD_PROMONTORYA_4 0x43b9 #define PCI_DEVICE_ID_AMD_PROMONTORYA_3 0x43ba @@ -189,6 +196,16 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) pdev->device == PCI_DEVICE_ID_INTEL_DNV_XHCI)) xhci->quirks |= XHCI_MISSING_CAS; + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + (pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_XHCI || + pdev->device == PCI_DEVICE_ID_INTEL_TITAN_RIDGE_DD_XHCI)) + xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW; + if (pdev->vendor == PCI_VENDOR_ID_ETRON && pdev->device == PCI_DEVICE_ID_EJ168) { xhci->quirks |= XHCI_RESET_ON_RESUME; @@ -332,6 +349,9 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) /* USB-2 and USB-3 roothubs initialized, allow runtime pm suspend */ pm_runtime_put_noidle(&dev->dev); + if (xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW) + pm_runtime_allow(&dev->dev); + return 0; put_usb3_hcd: @@ -349,6 +369,10 @@ static void xhci_pci_remove(struct pci_dev *dev) xhci = hcd_to_xhci(pci_get_drvdata(dev)); xhci->xhc_state |= XHCI_STATE_REMOVING; + + if (xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW) + pm_runtime_forbid(&dev->dev); + if (xhci->shared_hcd) { usb_remove_hcd(xhci->shared_hcd); usb_put_hcd(xhci->shared_hcd); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index b63578548bef..bf0b3692dc9a 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1848,6 +1848,7 @@ struct xhci_hcd { #define XHCI_SUSPEND_DELAY BIT_ULL(30) #define XHCI_INTEL_USB_ROLE_SW BIT_ULL(31) #define XHCI_ZERO_64B_REGS BIT_ULL(32) +#define XHCI_DEFAULT_PM_RUNTIME_ALLOW BIT_ULL(33) unsigned int num_active_eps; unsigned int limit_active_eps; -- cgit v1.2.3-58-ga151 From e1c3c7e54ed3655c02248b4f1c7940aff4eecb56 Mon Sep 17 00:00:00 2001 From: Thierry Reding <treding@nvidia.com> Date: Thu, 20 Sep 2018 19:13:39 +0300 Subject: usb: xhci: tegra: Firmware header is little endian The XUSB firmware header is in little endian byte order, so make the fields __le32 and __le16 instead of u32 and u16 to avoid warnings from sparse when the fields are used with the endian-aware __le32_to_cpu() and __le16_to_cpu() accessors, respectively. Signed-off-by: Thierry Reding <treding@nvidia.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-tegra.c | 52 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 4b463e5202a4..4ee510a51d64 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -107,35 +107,35 @@ #define IMEM_BLOCK_SIZE 256 struct tegra_xusb_fw_header { - u32 boot_loadaddr_in_imem; - u32 boot_codedfi_offset; - u32 boot_codetag; - u32 boot_codesize; - u32 phys_memaddr; - u16 reqphys_memsize; - u16 alloc_phys_memsize; - u32 rodata_img_offset; - u32 rodata_section_start; - u32 rodata_section_end; - u32 main_fnaddr; - u32 fwimg_cksum; - u32 fwimg_created_time; - u32 imem_resident_start; - u32 imem_resident_end; - u32 idirect_start; - u32 idirect_end; - u32 l2_imem_start; - u32 l2_imem_end; - u32 version_id; + __le32 boot_loadaddr_in_imem; + __le32 boot_codedfi_offset; + __le32 boot_codetag; + __le32 boot_codesize; + __le32 phys_memaddr; + __le16 reqphys_memsize; + __le16 alloc_phys_memsize; + __le32 rodata_img_offset; + __le32 rodata_section_start; + __le32 rodata_section_end; + __le32 main_fnaddr; + __le32 fwimg_cksum; + __le32 fwimg_created_time; + __le32 imem_resident_start; + __le32 imem_resident_end; + __le32 idirect_start; + __le32 idirect_end; + __le32 l2_imem_start; + __le32 l2_imem_end; + __le32 version_id; u8 init_ddirect; u8 reserved[3]; - u32 phys_addr_log_buffer; - u32 total_log_entries; - u32 dequeue_ptr; - u32 dummy_var[2]; - u32 fwimg_len; + __le32 phys_addr_log_buffer; + __le32 total_log_entries; + __le32 dequeue_ptr; + __le32 dummy_var[2]; + __le32 fwimg_len; u8 magic[8]; - u32 ss_low_power_entry_timeout; + __le32 ss_low_power_entry_timeout; u8 num_hsic_port; u8 padding[139]; /* Pad to 256 bytes */ }; -- cgit v1.2.3-58-ga151 From 330e2d61cdd58363eb5e66b2e72f76fe3c5492e0 Mon Sep 17 00:00:00 2001 From: Anshuman Gupta <anshuman.gupta@intel.com> Date: Thu, 20 Sep 2018 19:13:40 +0300 Subject: xhci: Avoid USB autosuspend when resuming USB2 ports. When USB bus host controller root hub resumes from autosuspend, it immediately tries to enter auto-suspend, but there can be a scenario when root hub is resuming its usb2 ports, in that particular case USB host controller auto suspend fails since it is busy to resuming its usb2 ports. This makes multiple failed cycles of auto-suspend until all usb2 ports of host controller root hub do not resume. This patch uses USB core framework usb_hcd_start_port_resume, usb_hcd_end_port_resume API's in order to autoresume/autosuspend root hub properly. Signed-off-by: Anshuman Gupta <anshuman.gupta@intel.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-hub.c | 5 +++++ drivers/usb/host/xhci-ring.c | 1 + 2 files changed, 6 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 7e2a531ba321..12eea73d9f20 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -900,6 +900,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, set_bit(wIndex, &bus_state->resuming_ports); bus_state->resume_done[wIndex] = timeout; mod_timer(&hcd->rh_timer, timeout); + usb_hcd_start_port_resume(&hcd->self, wIndex); } /* Has resume been signalled for USB_RESUME_TIME yet? */ } else if (time_after_eq(jiffies, @@ -940,6 +941,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, clear_bit(wIndex, &bus_state->rexit_ports); } + usb_hcd_end_port_resume(&hcd->self, wIndex); bus_state->port_c_suspend |= 1 << wIndex; bus_state->suspended_ports &= ~(1 << wIndex); } else { @@ -962,6 +964,7 @@ static u32 xhci_get_port_status(struct usb_hcd *hcd, (raw_port_status & PORT_PLS_MASK) != XDEV_RESUME) { bus_state->resume_done[wIndex] = 0; clear_bit(wIndex, &bus_state->resuming_ports); + usb_hcd_end_port_resume(&hcd->self, wIndex); } @@ -1337,6 +1340,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, goto error; set_bit(wIndex, &bus_state->resuming_ports); + usb_hcd_start_port_resume(&hcd->self, wIndex); xhci_set_link_state(xhci, ports[wIndex], XDEV_RESUME); spin_unlock_irqrestore(&xhci->lock, flags); @@ -1345,6 +1349,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, xhci_set_link_state(xhci, ports[wIndex], XDEV_U0); clear_bit(wIndex, &bus_state->resuming_ports); + usb_hcd_end_port_resume(&hcd->self, wIndex); } bus_state->port_c_suspend |= 1 << wIndex; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index c41341ea8449..a8d92c90fb58 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1606,6 +1606,7 @@ static void handle_port_status(struct xhci_hcd *xhci, set_bit(HCD_FLAG_POLL_RH, &hcd->flags); mod_timer(&hcd->rh_timer, bus_state->resume_done[hcd_portnum]); + usb_hcd_start_port_resume(&hcd->self, hcd_portnum); bogus_port_status = true; } } -- cgit v1.2.3-58-ga151 From 40326e857c57a0095d3f9d72c14cb13aef4ca564 Mon Sep 17 00:00:00 2001 From: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Date: Wed, 26 Sep 2018 16:23:51 +0100 Subject: usb: typec: fusb302: Correct spelling mistake for toggling state There's a typo in the enum name of the 'OFF' state for toggling (TOGGLINE instead of TOGGLING). This commit resolves that trivial spelling inconsistency. Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/tcpm/fusb302.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index 6e9370a813f7..fd851d8558d1 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -42,7 +42,7 @@ #define T_BC_LVL_DEBOUNCE_DELAY_MS 30 enum toggling_mode { - TOGGLINE_MODE_OFF, + TOGGLING_MODE_OFF, TOGGLING_MODE_DRP, TOGGLING_MODE_SNK, TOGGLING_MODE_SRC, @@ -594,7 +594,7 @@ static int fusb302_set_toggling(struct fusb302_chip *chip, chip->intr_comp_chng = false; /* configure toggling mode: none/snk/src/drp */ switch (mode) { - case TOGGLINE_MODE_OFF: + case TOGGLING_MODE_OFF: ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, FUSB_REG_CONTROL2_MODE_MASK, FUSB_REG_CONTROL2_MODE_NONE); @@ -626,7 +626,7 @@ static int fusb302_set_toggling(struct fusb302_chip *chip, break; } - if (mode == TOGGLINE_MODE_OFF) { + if (mode == TOGGLING_MODE_OFF) { /* mask TOGDONE interrupt */ ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, FUSB_REG_MASKA_TOGDONE); @@ -702,7 +702,7 @@ static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) ret = -EINVAL; goto done; } - ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); if (ret < 0) { fusb302_log(chip, "cannot stop toggling, ret=%d", ret); goto done; @@ -1292,7 +1292,7 @@ static int fusb302_handle_togdone_snk(struct fusb302_chip *chip, tcpm_cc_change(chip->tcpm_port); } /* turn off toggling */ - ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); if (ret < 0) { fusb302_log(chip, "cannot set toggling mode off, ret=%d", ret); @@ -1388,7 +1388,7 @@ static int fusb302_handle_togdone_src(struct fusb302_chip *chip, tcpm_cc_change(chip->tcpm_port); } /* turn off toggling */ - ret = fusb302_set_toggling(chip, TOGGLINE_MODE_OFF); + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); if (ret < 0) { fusb302_log(chip, "cannot set toggling mode off, ret=%d", ret); -- cgit v1.2.3-58-ga151 From ea3b4d5523bc8d3e955075d3716af536d6212cc7 Mon Sep 17 00:00:00 2001 From: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Date: Wed, 26 Sep 2018 16:23:52 +0100 Subject: usb: typec: fusb302: Resolve fixed power role contract setup When the controller is configured for a fixed power role (Source only or Sink only), attach does not proceed within the TCPM state machine as there is no CC event generated by this driver to update the CC line status. To rectify this, when CC is configured as Source or Sink we now make use of the hardware's automatic fixed Source or Sink toggling mechanism, which detects attaches in the same way as for DRP toggling. In this way the result of toggling is handled in the same way by the 'fusb302_handle_togdone()' function, and CC events are generated as expected for TCPM allowing a contract to be established. Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/tcpm/fusb302.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c index fd851d8558d1..43b64d9309d0 100644 --- a/drivers/usb/typec/tcpm/fusb302.c +++ b/drivers/usb/typec/tcpm/fusb302.c @@ -679,6 +679,7 @@ static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) int ret = 0; bool pull_up, pull_down; u8 rd_mda; + enum toggling_mode mode; mutex_lock(&chip->lock); switch (cc) { @@ -764,6 +765,29 @@ static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) chip->intr_comp_chng = false; } fusb302_log(chip, "cc := %s", typec_cc_status_name[cc]); + + /* Enable detection for fixed SNK or SRC only roles */ + switch (cc) { + case TYPEC_CC_RD: + mode = TOGGLING_MODE_SNK; + break; + case TYPEC_CC_RP_DEF: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + mode = TOGGLING_MODE_SRC; + break; + default: + mode = TOGGLING_MODE_OFF; + break; + } + + if (mode != TOGGLING_MODE_OFF) { + ret = fusb302_set_toggling(chip, mode); + if (ret < 0) + fusb302_log(chip, + "cannot set fixed role toggling mode, ret=%d", + ret); + } done: mutex_unlock(&chip->lock); -- cgit v1.2.3-58-ga151 From 201af55da8a3986297d7c3493f839dfc96ffd7db Mon Sep 17 00:00:00 2001 From: Jon Flatley <jflat@chromium.org> Date: Thu, 20 Sep 2018 10:17:54 -0700 Subject: usb: core: added uevent for over-current After commit 1cbd53c8cd85 ("usb: core: introduce per-port over-current counters") usb ports expose a sysfs value 'over_current_count' to user space. This value on its own is not very useful as it requires manual polling. As a solution, fire a udev event from the usb hub device that specifies the values 'OVER_CURRENT_PORT' and 'OVER_CURRENT_COUNT' that indicate the path of the usb port where the over-current event occurred and the value of 'over_current_count' in sysfs. Additionally, call sysfs_notify() so the sysfs value supports poll(). Signed-off-by: Jon Flatley <jflat@chromium.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- Documentation/ABI/testing/sysfs-bus-usb | 9 ++++++++- drivers/usb/core/hub.c | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 08d456e07b53..c4a70f532ec3 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -219,7 +219,14 @@ Description: ports and report them to the kernel. This attribute is to expose the number of over-current situation occurred on a specific port to user space. This file will contain an unsigned 32 bit value - which wraps to 0 after its maximum is reached. + which wraps to 0 after its maximum is reached. This file supports + poll() for monitoring changes to this value in user space. + + Any time this value changes the corresponding hub device will send a + udev event with the following attributes: + + OVER_CURRENT_PORT=/sys/bus/usb/devices/.../(hub interface)/portX + OVER_CURRENT_COUNT=[current value of this sysfs attribute] What: /sys/bus/usb/devices/.../(hub interface)/portX/usb3_lpm_permit Date: November 2015 diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 462ce49f683a..7801bb30bdba 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -28,6 +28,7 @@ #include <linux/mutex.h> #include <linux/random.h> #include <linux/pm_qos.h> +#include <linux/kobject.h> #include <linux/uaccess.h> #include <asm/byteorder.h> @@ -5147,6 +5148,40 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, usb_lock_port(port_dev); } +/* Handle notifying userspace about hub over-current events */ +static void port_over_current_notify(struct usb_port *port_dev) +{ + static char *envp[] = { NULL, NULL, NULL }; + struct device *hub_dev; + char *port_dev_path; + + sysfs_notify(&port_dev->dev.kobj, NULL, "over_current_count"); + + hub_dev = port_dev->dev.parent; + + if (!hub_dev) + return; + + port_dev_path = kobject_get_path(&port_dev->dev.kobj, GFP_KERNEL); + if (!port_dev_path) + return; + + envp[0] = kasprintf(GFP_KERNEL, "OVER_CURRENT_PORT=%s", port_dev_path); + if (!envp[0]) + return; + + envp[1] = kasprintf(GFP_KERNEL, "OVER_CURRENT_COUNT=%u", + port_dev->over_current_count); + if (!envp[1]) + goto exit; + + kobject_uevent_env(&hub_dev->kobj, KOBJ_CHANGE, envp); + + kfree(envp[1]); +exit: + kfree(envp[0]); +} + static void port_event(struct usb_hub *hub, int port1) __must_hold(&port_dev->status_lock) { @@ -5189,6 +5224,7 @@ static void port_event(struct usb_hub *hub, int port1) if (portchange & USB_PORT_STAT_C_OVERCURRENT) { u16 status = 0, unused; port_dev->over_current_count++; + port_over_current_notify(port_dev); dev_dbg(&port_dev->dev, "over-current change #%u\n", port_dev->over_current_count); -- cgit v1.2.3-58-ga151 From 91b20c5a5be0088f9534483276f86230c3a5872f Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:28 +0900 Subject: Revert "usb: renesas_usbhs: add extcon notifier to set mode for non-otg channel" This reverts commit 8ada211d0383b72878582bd312b984a9eae62b30. R-Car D3 can use OTG mode in fact. So, the commit doesn't need anymore. In other words, like other R-Car Gen3 SoCs, R-Car D3 can change the mode by using the phy-rcar-gen3-usb2 driver. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/renesas_usbhs/common.c | 9 --------- drivers/usb/renesas_usbhs/common.h | 1 - drivers/usb/renesas_usbhs/rcar3.c | 11 ----------- 3 files changed, 21 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index d6c39ba73190..522cc091a59c 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -677,15 +677,6 @@ static int usbhs_probe(struct platform_device *pdev) break; case USBHS_TYPE_RCAR_GEN3_WITH_PLL: priv->pfunc = usbhs_rcar3_with_pll_ops; - if (!IS_ERR_OR_NULL(priv->edev)) { - priv->nb.notifier_call = priv->pfunc.notifier; - ret = devm_extcon_register_notifier(&pdev->dev, - priv->edev, - EXTCON_USB_HOST, - &priv->nb); - if (ret < 0) - dev_err(&pdev->dev, "no notifier registered\n"); - } break; case USBHS_TYPE_RZA1: priv->pfunc = usbhs_rza1_ops; diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index 555b3e788c6d..3777af848a35 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -257,7 +257,6 @@ struct usbhs_priv { struct platform_device *pdev; struct extcon_dev *edev; - struct notifier_block nb; spinlock_t lock; diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c index d0ea4ff89622..b9a8453a5e68 100644 --- a/drivers/usb/renesas_usbhs/rcar3.c +++ b/drivers/usb/renesas_usbhs/rcar3.c @@ -112,16 +112,6 @@ static int usbhs_rcar3_get_id(struct platform_device *pdev) return USBHS_GADGET; } -static int usbhs_rcar3_notifier(struct notifier_block *nb, unsigned long event, - void *data) -{ - struct usbhs_priv *priv = container_of(nb, struct usbhs_priv, nb); - - usbhs_rcar3_set_usbsel(priv, !!event); - - return NOTIFY_DONE; -} - const struct renesas_usbhs_platform_callback usbhs_rcar3_ops = { .power_ctrl = usbhs_rcar3_power_ctrl, .get_id = usbhs_rcar3_get_id, @@ -130,5 +120,4 @@ const struct renesas_usbhs_platform_callback usbhs_rcar3_ops = { const struct renesas_usbhs_platform_callback usbhs_rcar3_with_pll_ops = { .power_ctrl = usbhs_rcar3_power_and_pll_ctrl, .get_id = usbhs_rcar3_get_id, - .notifier = usbhs_rcar3_notifier, }; -- cgit v1.2.3-58-ga151 From eb757fff08b8c57c22643c65add0dc1a570d1342 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:29 +0900 Subject: Revert "usb: renesas_usbhs: set the mode by using extcon state for non-otg channel" This reverts commit cd14247d5c14b9b20bb3d3dfcaa899ca22c8dccc. R-Car D3 can use OTG mode in fact. So, the commit doesn't need anymore. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/renesas_usbhs/rcar3.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c index b9a8453a5e68..50e5fb55c8a0 100644 --- a/drivers/usb/renesas_usbhs/rcar3.c +++ b/drivers/usb/renesas_usbhs/rcar3.c @@ -27,7 +27,6 @@ * Remarks: bit[31:11] and bit[9:6] should be 0 */ #define UGCTRL2_RESERVED_3 0x00000001 /* bit[3:0] should be B'0001 */ -#define UGCTRL2_USB0SEL_EHCI 0x00000010 #define UGCTRL2_USB0SEL_HSUSB 0x00000020 #define UGCTRL2_USB0SEL_OTG 0x00000030 #define UGCTRL2_VBUSSEL 0x00000400 @@ -50,14 +49,6 @@ static void usbhs_rcar3_set_ugctrl2(struct usbhs_priv *priv, u32 val) usbhs_write32(priv, UGCTRL2, val | UGCTRL2_RESERVED_3); } -static void usbhs_rcar3_set_usbsel(struct usbhs_priv *priv, bool ehci) -{ - if (ehci) - usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_EHCI); - else - usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_HSUSB); -} - static int usbhs_rcar3_power_ctrl(struct platform_device *pdev, void __iomem *base, int enable) { @@ -83,14 +74,10 @@ static int usbhs_rcar3_power_and_pll_ctrl(struct platform_device *pdev, struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); u32 val; int timeout = 1000; - bool is_host = false; if (enable) { usbhs_write32(priv, UGCTRL, 0); /* release PLLRESET */ - if (priv->edev) - is_host = extcon_get_state(priv->edev, EXTCON_USB_HOST); - - usbhs_rcar3_set_usbsel(priv, is_host); + usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_HSUSB); usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM); do { -- cgit v1.2.3-58-ga151 From 6b983aca28bbd4ac18fe42fe2b2dc14a9ce21e3b Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:30 +0900 Subject: usb: renesas_usbhs: rcar3: Use OTG mode for R-Car D3 Since R-Car D3 can use OTG mode, this patch changes the UGCTRL2 value to UGCTRL2_USB0SEL_OTG and UGCTRL2_VBUSSEL like other R-Car Gen3 SoCs. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/renesas_usbhs/rcar3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c index 50e5fb55c8a0..aa3820448286 100644 --- a/drivers/usb/renesas_usbhs/rcar3.c +++ b/drivers/usb/renesas_usbhs/rcar3.c @@ -77,7 +77,8 @@ static int usbhs_rcar3_power_and_pll_ctrl(struct platform_device *pdev, if (enable) { usbhs_write32(priv, UGCTRL, 0); /* release PLLRESET */ - usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_HSUSB); + usbhs_rcar3_set_ugctrl2(priv, + UGCTRL2_USB0SEL_OTG | UGCTRL2_VBUSSEL); usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM); do { -- cgit v1.2.3-58-ga151 From 4d2a863fe9b952d7147bd169d54062e71f343415 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:32 +0900 Subject: usb: renesas_usbhs: add support for R-Car E3 This patch adds support for R-Car E3. This SoC needs to release the PLL reset by the UGCTRL register like R-Car D3. So, this patch adds a usbhs_of_match entry for this SoC with "USBHS_TYPE_RCAR_GEN3_WITH_PLL". Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/renesas_usbhs/common.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 522cc091a59c..a3e1290d682d 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -559,6 +559,10 @@ static const struct of_device_id usbhs_of_match[] = { .compatible = "renesas,usbhs-r8a7796", .data = (void *)USBHS_TYPE_RCAR_GEN3, }, + { + .compatible = "renesas,usbhs-r8a77990", + .data = (void *)USBHS_TYPE_RCAR_GEN3_WITH_PLL, + }, { .compatible = "renesas,usbhs-r8a77995", .data = (void *)USBHS_TYPE_RCAR_GEN3_WITH_PLL, -- cgit v1.2.3-58-ga151 From 100f2cdeadffb3e63121d1d59a60a9882258c415 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido.kiener@rohde-schwarz.com> Date: Tue, 25 Sep 2018 01:30:29 +0200 Subject: usb: usbtmc: Fix memory leak in usbtmc_ioctl_request Kernel memory is allocated twice in new function usbtmc_ioctl_request and creates a memory leak. This fix removes the superfluous kmalloc(). Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Fixes: 658f24f4523e ("usb: usbtmc: Add ioctl for generic requests on control") Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 0fcb81a1399b..dfbcf418dad7 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1895,10 +1895,6 @@ static int usbtmc_ioctl_request(struct usbtmc_device_data *data, if (res) return -EFAULT; - buffer = kmalloc(request.req.wLength, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - if (request.req.wLength > USBTMC_BUFSIZE) return -EMSGSIZE; -- cgit v1.2.3-58-ga151 From b690020a498e33c098dd2b5554a7a59fc08b5ca4 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido.kiener@rohde-schwarz.com> Date: Tue, 25 Sep 2018 01:30:30 +0200 Subject: usb: usbtmc: uninitialized symbol 'actual' in usbtmc_read Fix uninitialized symbol 'actual' in function usbtmc_read. When symbol 'actual' is not initialized and usb_bulk_msg() fails, the subsequent kernel debug message shows a random value. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Fixes: d7604ff0dc01 ("usb: usbtmc: Optimize usbtmc_read") Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index dfbcf418dad7..9cb90603f71f 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1370,6 +1370,7 @@ static ssize_t usbtmc_read(struct file *filp, char __user *buf, /* Loop until we have fetched everything we requested */ remaining = count; + actual = 0; /* Send bulk URB */ retval = usb_bulk_msg(data->usb_dev, -- cgit v1.2.3-58-ga151 From 9a83190300867fb024d53f47c31088e34188efc1 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido.kiener@rohde-schwarz.com> Date: Tue, 25 Sep 2018 01:30:31 +0200 Subject: usb: usbtmc: uninitialized symbol 'actual' in usbtmc_ioctl_clear Fix uninitialized symbol 'actual' in function usbtmc_ioctl_clear. When symbol 'actual' is not initialized and usb_bulk_msg() fails, the subsequent kernel debug message shows a random value. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Fixes: dfee02ac4bce ("usb: usbtmc: Fix ioctl USBTMC_IOCTL_CLEAR") Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 9cb90603f71f..7184fa035434 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -1679,6 +1679,7 @@ usbtmc_clear_check_status: do { dev_dbg(dev, "Reading from bulk in EP\n"); + actual = 0; rv = usb_bulk_msg(data->usb_dev, usb_rcvbulkpipe(data->usb_dev, data->bulk_in), -- cgit v1.2.3-58-ga151 From 2e32188a66142b5b4c2dbdd6ac8dc6f54361f044 Mon Sep 17 00:00:00 2001 From: Guido Kiener <guido.kiener@rohde-schwarz.com> Date: Tue, 25 Sep 2018 01:30:32 +0200 Subject: usb: usbtmc: uninitialized symbol 'actual' in usbtmc_ioctl_abort_bulk_in_tag Fix uninitialized symbol 'actual' in function usbtmc_ioctl_abort_bulk_in_tag(). When symbol 'actual' is not initialized and usb_bulk_msg() fails, the subsequent kernel debug message shows invalid data. Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com> Fixes: cbe743f1333b ("usb: usbtmc: Fix ioctl USBTMC_IOCTL_ABORT_BULK_IN") Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/class/usbtmc.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb') diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 7184fa035434..4942122b2346 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -321,6 +321,7 @@ usbtmc_abort_bulk_in_status: dev_dbg(dev, "Reading from bulk in EP\n"); /* Data must be present. So use low timeout 300 ms */ + actual = 0; rv = usb_bulk_msg(data->usb_dev, usb_rcvbulkpipe(data->usb_dev, data->bulk_in), -- cgit v1.2.3-58-ga151 From bf3854aaa7568bd0dca4e4f70df5c2aeedf0cd5a Mon Sep 17 00:00:00 2001 From: Colin Ian King <colin.king@canonical.com> Date: Sat, 29 Sep 2018 12:43:13 +0100 Subject: usb: gadget: fix spelling mistakeis "[En]queing" -> "[En]queuing" Trivial fix to spelling mistakes in debug warning messages Signed-off-by: Colin Ian King <colin.king@canonical.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/aspeed-vhub/epn.c | 2 +- drivers/usb/gadget/udc/udc-xilinx.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c index 5939eb1e97f2..4a28e3fbeb0b 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/epn.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c @@ -353,7 +353,7 @@ static int ast_vhub_epn_queue(struct usb_ep* u_ep, struct usb_request *u_req, /* Endpoint enabled ? */ if (!ep->epn.enabled || !u_ep->desc || !ep->dev || !ep->d_idx || !ep->dev->enabled || ep->dev->suspended) { - EPDBG(ep,"Enqueing request on wrong or disabled EP\n"); + EPDBG(ep, "Enqueuing request on wrong or disabled EP\n"); return -ESHUTDOWN; } diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 6407e433bc78..b1f4104d1283 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -1078,7 +1078,7 @@ static int xudc_ep_queue(struct usb_ep *_ep, struct usb_request *_req, unsigned long flags; if (!ep->desc) { - dev_dbg(udc->dev, "%s:queing request to disabled %s\n", + dev_dbg(udc->dev, "%s: queuing request to disabled %s\n", __func__, ep->name); return -ESHUTDOWN; } -- cgit v1.2.3-58-ga151 From 4018aa9b57c218e0aea3a0b83b8f87c7c8c51172 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:28 +0900 Subject: Revert "usb: renesas_usbhs: add extcon notifier to set mode for non-otg channel" This reverts commit 8ada211d0383b72878582bd312b984a9eae62b30. R-Car D3 can use OTG mode in fact. So, the commit doesn't need anymore. In other words, like other R-Car Gen3 SoCs, R-Car D3 can change the mode by using the phy-rcar-gen3-usb2 driver. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/renesas_usbhs/common.c | 9 --------- drivers/usb/renesas_usbhs/common.h | 1 - drivers/usb/renesas_usbhs/rcar3.c | 11 ----------- 3 files changed, 21 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 4310df46639d..dc041110039c 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -591,15 +591,6 @@ static int usbhs_probe(struct platform_device *pdev) break; case USBHS_TYPE_RCAR_GEN3_WITH_PLL: priv->pfunc = usbhs_rcar3_with_pll_ops; - if (!IS_ERR_OR_NULL(priv->edev)) { - priv->nb.notifier_call = priv->pfunc.notifier; - ret = devm_extcon_register_notifier(&pdev->dev, - priv->edev, - EXTCON_USB_HOST, - &priv->nb); - if (ret < 0) - dev_err(&pdev->dev, "no notifier registered\n"); - } break; case USBHS_TYPE_RZA1: priv->pfunc = usbhs_rza1_ops; diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index 6137f7942c05..473e87e77cd4 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -255,7 +255,6 @@ struct usbhs_priv { struct platform_device *pdev; struct extcon_dev *edev; - struct notifier_block nb; spinlock_t lock; diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c index d0ea4ff89622..b9a8453a5e68 100644 --- a/drivers/usb/renesas_usbhs/rcar3.c +++ b/drivers/usb/renesas_usbhs/rcar3.c @@ -112,16 +112,6 @@ static int usbhs_rcar3_get_id(struct platform_device *pdev) return USBHS_GADGET; } -static int usbhs_rcar3_notifier(struct notifier_block *nb, unsigned long event, - void *data) -{ - struct usbhs_priv *priv = container_of(nb, struct usbhs_priv, nb); - - usbhs_rcar3_set_usbsel(priv, !!event); - - return NOTIFY_DONE; -} - const struct renesas_usbhs_platform_callback usbhs_rcar3_ops = { .power_ctrl = usbhs_rcar3_power_ctrl, .get_id = usbhs_rcar3_get_id, @@ -130,5 +120,4 @@ const struct renesas_usbhs_platform_callback usbhs_rcar3_ops = { const struct renesas_usbhs_platform_callback usbhs_rcar3_with_pll_ops = { .power_ctrl = usbhs_rcar3_power_and_pll_ctrl, .get_id = usbhs_rcar3_get_id, - .notifier = usbhs_rcar3_notifier, }; -- cgit v1.2.3-58-ga151 From 971a0d4e1be2bb18c3b58d6567743b7de0e4059f Mon Sep 17 00:00:00 2001 From: Josh Abraham <j.abraham1776@gmail.com> Date: Sun, 23 Sep 2018 21:41:35 -0400 Subject: usb: dwc2: remove set but unused variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch removes a set but unused variable in hcd.c. Fixes gcc warning: variable ‘data_fifo’ set but not used [-Wunused-but-set-variable] Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Joshua Abraham <j.abraham1776@gmail.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/hcd.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 2bd6e6bfc241..5f23b933cafc 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -1328,14 +1328,11 @@ static void dwc2_hc_write_packet(struct dwc2_hsotg *hsotg, u32 remaining_count; u32 byte_count; u32 dword_count; - u32 __iomem *data_fifo; u32 *data_buf = (u32 *)chan->xfer_buf; if (dbg_hc(chan)) dev_vdbg(hsotg->dev, "%s()\n", __func__); - data_fifo = (u32 __iomem *)(hsotg->regs + HCFIFO(chan->hc_num)); - remaining_count = chan->xfer_len - chan->xfer_count; if (remaining_count > chan->max_packet) byte_count = chan->max_packet; -- cgit v1.2.3-58-ga151 From a9383a6c3679ce7317f4689fe746deb82cfbee5e Mon Sep 17 00:00:00 2001 From: Nathan Chancellor <natechancellor@gmail.com> Date: Thu, 20 Sep 2018 13:28:13 -0700 Subject: usb: gadget: udc: Remove unnecessary parentheses Clang warns when multiple pairs of parentheses are used for a single conditional statement. drivers/usb/gadget/udc/mv_udc_core.c:188:33: warning: equality comparison with extraneous parentheses [-Wparentheses-equality] while ((curr_dqh->curr_dtd_ptr == curr_dtd->td_dma)) { ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~ drivers/usb/gadget/udc/mv_udc_core.c:188:33: note: remove extraneous parentheses around the comparison to silence this warning while ((curr_dqh->curr_dtd_ptr == curr_dtd->td_dma)) { ~ ^ ~ drivers/usb/gadget/udc/mv_udc_core.c:188:33: note: use '=' to turn this equality comparison into an assignment while ((curr_dqh->curr_dtd_ptr == curr_dtd->td_dma)) { ^~ = 1 warning generated. Link: https://github.com/ClangBuiltLinux/linux/issues/120 Signed-off-by: Nathan Chancellor <natechancellor@gmail.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/mv_udc_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 95f52232493b..cafde053788b 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -185,7 +185,7 @@ static int process_ep_req(struct mv_udc *udc, int index, else bit_pos = 1 << (16 + curr_req->ep->ep_num); - while ((curr_dqh->curr_dtd_ptr == curr_dtd->td_dma)) { + while (curr_dqh->curr_dtd_ptr == curr_dtd->td_dma) { if (curr_dtd->dtd_next == EP_QUEUE_HEAD_NEXT_TERMINATE) { while (readl(&udc->op_regs->epstatus) & bit_pos) udelay(1); -- cgit v1.2.3-58-ga151 From 4a13b9689da8d9d62b5eff1632115bf21f24c52d Mon Sep 17 00:00:00 2001 From: Colin Ian King <colin.king@canonical.com> Date: Tue, 18 Sep 2018 08:54:11 +0200 Subject: usb: phy: mxs: fix spelling mistake "stardard" -> "standard" Trivial fix to spelling mistake in dev_dbg message Acked-by: Peter Chen <peter.chen@nxp.com> Signed-off-by: Colin Ian King <colin.king@canonical.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/phy/phy-mxs-usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index e5aa24c1e4fd..1b1bb0ad40c3 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -563,7 +563,7 @@ static enum usb_charger_type mxs_charger_primary_detection(struct mxs_phy *x) regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) { chgr_type = SDP_TYPE; - dev_dbg(x->phy.dev, "It is a stardard downstream port\n"); + dev_dbg(x->phy.dev, "It is a standard downstream port\n"); } /* Disable charger detector */ -- cgit v1.2.3-58-ga151 From 1e041b6f313aaa966612a7e415cfc09c90d6b829 Mon Sep 17 00:00:00 2001 From: Marek Szyprowski <m.szyprowski@samsung.com> Date: Tue, 18 Sep 2018 10:16:50 +0200 Subject: usb: dwc3: exynos: Remove dead code All supported Exynos variants provide respective generic PHY framework based drivers for controlling USB PHYs, so there is no point creating fake USB PHYs based on platform devices. While removing useless code, remove calls to runtime PM, which have no effect. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc3/dwc3-exynos.c | 75 ------------------------------------------ 1 file changed, 75 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index a94fb1ba8f2c..0a946c66c3bb 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -13,15 +13,11 @@ #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/clk.h> -#include <linux/usb/otg.h> -#include <linux/usb/usb_phy_generic.h> #include <linux/of.h> #include <linux/of_platform.h> #include <linux/regulator/consumer.h> struct dwc3_exynos { - struct platform_device *usb2_phy; - struct platform_device *usb3_phy; struct device *dev; struct clk *clk; @@ -32,61 +28,6 @@ struct dwc3_exynos { struct regulator *vdd10; }; -static int dwc3_exynos_register_phys(struct dwc3_exynos *exynos) -{ - struct usb_phy_generic_platform_data pdata; - struct platform_device *pdev; - int ret; - - memset(&pdata, 0x00, sizeof(pdata)); - - pdev = platform_device_alloc("usb_phy_generic", PLATFORM_DEVID_AUTO); - if (!pdev) - return -ENOMEM; - - exynos->usb2_phy = pdev; - pdata.type = USB_PHY_TYPE_USB2; - pdata.gpio_reset = -1; - - ret = platform_device_add_data(exynos->usb2_phy, &pdata, sizeof(pdata)); - if (ret) - goto err1; - - pdev = platform_device_alloc("usb_phy_generic", PLATFORM_DEVID_AUTO); - if (!pdev) { - ret = -ENOMEM; - goto err1; - } - - exynos->usb3_phy = pdev; - pdata.type = USB_PHY_TYPE_USB3; - - ret = platform_device_add_data(exynos->usb3_phy, &pdata, sizeof(pdata)); - if (ret) - goto err2; - - ret = platform_device_add(exynos->usb2_phy); - if (ret) - goto err2; - - ret = platform_device_add(exynos->usb3_phy); - if (ret) - goto err3; - - return 0; - -err3: - platform_device_del(exynos->usb2_phy); - -err2: - platform_device_put(exynos->usb3_phy); - -err1: - platform_device_put(exynos->usb2_phy); - - return ret; -} - static int dwc3_exynos_remove_child(struct device *dev, void *unused) { struct platform_device *pdev = to_platform_device(dev); @@ -164,12 +105,6 @@ static int dwc3_exynos_probe(struct platform_device *pdev) goto vdd10_err; } - ret = dwc3_exynos_register_phys(exynos); - if (ret) { - dev_err(dev, "couldn't register PHYs\n"); - goto phys_err; - } - if (node) { ret = of_platform_populate(node, NULL, NULL, dev); if (ret) { @@ -185,9 +120,6 @@ static int dwc3_exynos_probe(struct platform_device *pdev) return 0; populate_err: - platform_device_unregister(exynos->usb2_phy); - platform_device_unregister(exynos->usb3_phy); -phys_err: regulator_disable(exynos->vdd10); vdd10_err: regulator_disable(exynos->vdd33); @@ -205,8 +137,6 @@ static int dwc3_exynos_remove(struct platform_device *pdev) struct dwc3_exynos *exynos = platform_get_drvdata(pdev); device_for_each_child(&pdev->dev, NULL, dwc3_exynos_remove_child); - platform_device_unregister(exynos->usb2_phy); - platform_device_unregister(exynos->usb3_phy); clk_disable_unprepare(exynos->axius_clk); clk_disable_unprepare(exynos->susp_clk); @@ -258,11 +188,6 @@ static int dwc3_exynos_resume(struct device *dev) clk_enable(exynos->clk); clk_enable(exynos->axius_clk); - /* runtime set active to reflect active state. */ - pm_runtime_disable(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); - return 0; } -- cgit v1.2.3-58-ga151 From 9f2168367a0ab73e57e365f980a9283d478c41ee Mon Sep 17 00:00:00 2001 From: Marek Szyprowski <m.szyprowski@samsung.com> Date: Tue, 18 Sep 2018 10:16:51 +0200 Subject: usb: dwc3: exynos: Rework clock handling and prepare for new variants Add per-variant list of clocks and manage them all together in the single array. This is a preparation for adding new variants of Exynos SoCs. No functional changes for existing Exynos SoCs. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc3/dwc3-exynos.c | 128 +++++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 48 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 0a946c66c3bb..3f434a53be8e 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -17,12 +17,21 @@ #include <linux/of_platform.h> #include <linux/regulator/consumer.h> +#define DWC3_EXYNOS_MAX_CLOCKS 4 + +struct dwc3_exynos_driverdata { + const char *clk_names[DWC3_EXYNOS_MAX_CLOCKS]; + int num_clks; + int suspend_clk_idx; +}; + struct dwc3_exynos { struct device *dev; - struct clk *clk; - struct clk *susp_clk; - struct clk *axius_clk; + const char **clk_names; + struct clk *clks[DWC3_EXYNOS_MAX_CLOCKS]; + int num_clks; + int suspend_clk_idx; struct regulator *vdd33; struct regulator *vdd10; @@ -42,47 +51,42 @@ static int dwc3_exynos_probe(struct platform_device *pdev) struct dwc3_exynos *exynos; struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; - - int ret; + const struct dwc3_exynos_driverdata *driver_data; + int i, ret; exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL); if (!exynos) return -ENOMEM; - platform_set_drvdata(pdev, exynos); + driver_data = of_device_get_match_data(dev); + exynos->dev = dev; + exynos->num_clks = driver_data->num_clks; + exynos->clk_names = (const char **)driver_data->clk_names; + exynos->suspend_clk_idx = driver_data->suspend_clk_idx; - exynos->dev = dev; + platform_set_drvdata(pdev, exynos); - exynos->clk = devm_clk_get(dev, "usbdrd30"); - if (IS_ERR(exynos->clk)) { - dev_err(dev, "couldn't get clock\n"); - return -EINVAL; + for (i = 0; i < exynos->num_clks; i++) { + exynos->clks[i] = devm_clk_get(dev, exynos->clk_names[i]); + if (IS_ERR(exynos->clks[i])) { + dev_err(dev, "failed to get clock: %s\n", + exynos->clk_names[i]); + return PTR_ERR(exynos->clks[i]); + } } - ret = clk_prepare_enable(exynos->clk); - if (ret) - return ret; - exynos->susp_clk = devm_clk_get(dev, "usbdrd30_susp_clk"); - if (IS_ERR(exynos->susp_clk)) - exynos->susp_clk = NULL; - ret = clk_prepare_enable(exynos->susp_clk); - if (ret) - goto susp_clk_err; - - if (of_device_is_compatible(node, "samsung,exynos7-dwusb3")) { - exynos->axius_clk = devm_clk_get(dev, "usbdrd30_axius_clk"); - if (IS_ERR(exynos->axius_clk)) { - dev_err(dev, "no AXI UpScaler clk specified\n"); - ret = -ENODEV; - goto axius_clk_err; + for (i = 0; i < exynos->num_clks; i++) { + ret = clk_prepare_enable(exynos->clks[i]); + if (ret) { + while (--i > 0) + clk_disable_unprepare(exynos->clks[i]); + return ret; } - ret = clk_prepare_enable(exynos->axius_clk); - if (ret) - goto axius_clk_err; - } else { - exynos->axius_clk = NULL; } + if (exynos->suspend_clk_idx >= 0) + clk_prepare_enable(exynos->clks[exynos->suspend_clk_idx]); + exynos->vdd33 = devm_regulator_get(dev, "vdd33"); if (IS_ERR(exynos->vdd33)) { ret = PTR_ERR(exynos->vdd33); @@ -124,23 +128,27 @@ populate_err: vdd10_err: regulator_disable(exynos->vdd33); vdd33_err: - clk_disable_unprepare(exynos->axius_clk); -axius_clk_err: - clk_disable_unprepare(exynos->susp_clk); -susp_clk_err: - clk_disable_unprepare(exynos->clk); + for (i = exynos->num_clks - 1; i >= 0; i--) + clk_disable_unprepare(exynos->clks[i]); + + if (exynos->suspend_clk_idx >= 0) + clk_disable_unprepare(exynos->clks[exynos->suspend_clk_idx]); + return ret; } static int dwc3_exynos_remove(struct platform_device *pdev) { struct dwc3_exynos *exynos = platform_get_drvdata(pdev); + int i; device_for_each_child(&pdev->dev, NULL, dwc3_exynos_remove_child); - clk_disable_unprepare(exynos->axius_clk); - clk_disable_unprepare(exynos->susp_clk); - clk_disable_unprepare(exynos->clk); + for (i = exynos->num_clks - 1; i >= 0; i--) + clk_disable_unprepare(exynos->clks[i]); + + if (exynos->suspend_clk_idx >= 0) + clk_disable_unprepare(exynos->clks[exynos->suspend_clk_idx]); regulator_disable(exynos->vdd33); regulator_disable(exynos->vdd10); @@ -148,10 +156,27 @@ static int dwc3_exynos_remove(struct platform_device *pdev) return 0; } +static const struct dwc3_exynos_driverdata exynos5250_drvdata = { + .clk_names = { "usbdrd30" }, + .num_clks = 1, + .suspend_clk_idx = -1, +}; + +static const struct dwc3_exynos_driverdata exynos7_drvdata = { + .clk_names = { "usbdrd30", "usbdrd30_susp_clk", "usbdrd30_axius_clk" }, + .num_clks = 3, + .suspend_clk_idx = 1, +}; + static const struct of_device_id exynos_dwc3_match[] = { - { .compatible = "samsung,exynos5250-dwusb3" }, - { .compatible = "samsung,exynos7-dwusb3" }, - {}, + { + .compatible = "samsung,exynos5250-dwusb3", + .data = &exynos5250_drvdata, + }, { + .compatible = "samsung,exynos7-dwusb3", + .data = &exynos7_drvdata, + }, { + } }; MODULE_DEVICE_TABLE(of, exynos_dwc3_match); @@ -159,9 +184,10 @@ MODULE_DEVICE_TABLE(of, exynos_dwc3_match); static int dwc3_exynos_suspend(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); + int i; - clk_disable(exynos->axius_clk); - clk_disable(exynos->clk); + for (i = exynos->num_clks - 1; i >= 0; i--) + clk_disable_unprepare(exynos->clks[i]); regulator_disable(exynos->vdd33); regulator_disable(exynos->vdd10); @@ -172,7 +198,7 @@ static int dwc3_exynos_suspend(struct device *dev) static int dwc3_exynos_resume(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); - int ret; + int i, ret; ret = regulator_enable(exynos->vdd33); if (ret) { @@ -185,8 +211,14 @@ static int dwc3_exynos_resume(struct device *dev) return ret; } - clk_enable(exynos->clk); - clk_enable(exynos->axius_clk); + for (i = 0; i < exynos->num_clks; i++) { + ret = clk_prepare_enable(exynos->clks[i]); + if (ret) { + while (--i > 0) + clk_disable_unprepare(exynos->clks[i]); + return ret; + } + } return 0; } -- cgit v1.2.3-58-ga151 From 4c19cc14064d99ef0a20fb5ba0d45c94dbedb13c Mon Sep 17 00:00:00 2001 From: Marek Szyprowski <m.szyprowski@samsung.com> Date: Tue, 18 Sep 2018 10:16:52 +0200 Subject: usb: dwc3: exynos: Add support for Exynos5433 variant with all clocks DWC3 variant found in Exynos5433 SoCs requires keeping all DRD30/UHOST30 clocks enabled all the time the driver does any access to DWC3 registers, otherwise external abort happens. So far DWC3 hardware module worked with samsung,exynos5250-dwusb3 compatible only by luck when built into kernel: all DRD30 clocks were left enabled by bootloader and later kept enabled by the DRD PHY driver. However, if one tried to use Exnos DWC3 driver as a module or performed system suspend/resume cycle, external abort happened. This patch finally fixes this issue. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- Documentation/devicetree/bindings/usb/dwc3.txt | 1 + Documentation/devicetree/bindings/usb/exynos-usb.txt | 2 ++ drivers/usb/dwc3/dwc3-exynos.c | 9 +++++++++ 3 files changed, 12 insertions(+) (limited to 'drivers/usb') diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index 3e4c38b806ac..636630fb92d7 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -19,6 +19,7 @@ Exception for clocks: "cavium,octeon-7130-usb-uctl" "qcom,dwc3" "samsung,exynos5250-dwusb3" + "samsung,exynos5433-dwusb3" "samsung,exynos7-dwusb3" "sprd,sc9860-dwc3" "st,stih407-dwc3" diff --git a/Documentation/devicetree/bindings/usb/exynos-usb.txt b/Documentation/devicetree/bindings/usb/exynos-usb.txt index c97374315049..b7111f43fa59 100644 --- a/Documentation/devicetree/bindings/usb/exynos-usb.txt +++ b/Documentation/devicetree/bindings/usb/exynos-usb.txt @@ -83,6 +83,8 @@ Required properties: - compatible: should be one of the following - "samsung,exynos5250-dwusb3": for USB 3.0 DWC3 controller on Exynos5250/5420. + "samsung,exynos5433-dwusb3": for USB 3.0 DWC3 controller on + Exynos5433. "samsung,exynos7-dwusb3": for USB 3.0 DWC3 controller on Exynos7. - #address-cells, #size-cells : should be '1' if the device has sub-nodes with 'reg' property. diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 3f434a53be8e..cb7fcd7c0ad8 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -162,6 +162,12 @@ static const struct dwc3_exynos_driverdata exynos5250_drvdata = { .suspend_clk_idx = -1, }; +static const struct dwc3_exynos_driverdata exynos5433_drvdata = { + .clk_names = { "aclk", "susp_clk", "pipe_pclk", "phyclk" }, + .num_clks = 4, + .suspend_clk_idx = 1, +}; + static const struct dwc3_exynos_driverdata exynos7_drvdata = { .clk_names = { "usbdrd30", "usbdrd30_susp_clk", "usbdrd30_axius_clk" }, .num_clks = 3, @@ -172,6 +178,9 @@ static const struct of_device_id exynos_dwc3_match[] = { { .compatible = "samsung,exynos5250-dwusb3", .data = &exynos5250_drvdata, + }, { + .compatible = "samsung,exynos5433-dwusb3", + .data = &exynos5433_drvdata, }, { .compatible = "samsung,exynos7-dwusb3", .data = &exynos7_drvdata, -- cgit v1.2.3-58-ga151 From dccf1bad4be7eaa096c1f3697bd37883f9a08ecb Mon Sep 17 00:00:00 2001 From: Minas Harutyunyan <Minas.Harutyunyan@synopsys.com> Date: Wed, 19 Sep 2018 18:13:52 +0400 Subject: usb: dwc2: Disable all EP's on disconnect Disabling all EP's allow to reset EP's to initial state. On disconnect disable all EP's instead of just killing all requests. Because of some platform didn't catch disconnect event, same stuff added to dwc2_hsotg_core_init_disconnected() function when USB reset detected on the bus. Changed from version 1: Changed lock acquire flow in dwc2_hsotg_ep_disable() function. Signed-off-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/gadget.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 220c0f9b89b0..79189db4bf17 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3109,6 +3109,8 @@ static void kill_all_requests(struct dwc2_hsotg *hsotg, dwc2_hsotg_txfifo_flush(hsotg, ep->fifo_index); } +static int dwc2_hsotg_ep_disable(struct usb_ep *ep); + /** * dwc2_hsotg_disconnect - disconnect service * @hsotg: The device state. @@ -3127,13 +3129,12 @@ void dwc2_hsotg_disconnect(struct dwc2_hsotg *hsotg) hsotg->connected = 0; hsotg->test_mode = 0; + /* all endpoints should be shutdown */ for (ep = 0; ep < hsotg->num_of_eps; ep++) { if (hsotg->eps_in[ep]) - kill_all_requests(hsotg, hsotg->eps_in[ep], - -ESHUTDOWN); + dwc2_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); if (hsotg->eps_out[ep]) - kill_all_requests(hsotg, hsotg->eps_out[ep], - -ESHUTDOWN); + dwc2_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); } call_gadget(hsotg, disconnect); @@ -3191,13 +3192,23 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, u32 val; u32 usbcfg; u32 dcfg = 0; + int ep; /* Kill any ep0 requests as controller will be reinitialized */ kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET); - if (!is_usb_reset) + if (!is_usb_reset) { if (dwc2_core_reset(hsotg, true)) return; + } else { + /* all endpoints should be shutdown */ + for (ep = 1; ep < hsotg->num_of_eps; ep++) { + if (hsotg->eps_in[ep]) + dwc2_hsotg_ep_disable(&hsotg->eps_in[ep]->ep); + if (hsotg->eps_out[ep]) + dwc2_hsotg_ep_disable(&hsotg->eps_out[ep]->ep); + } + } /* * we must now enable ep0 ready for host detection and then @@ -3993,6 +4004,7 @@ static int dwc2_hsotg_ep_disable(struct usb_ep *ep) unsigned long flags; u32 epctrl_reg; u32 ctrl; + int locked; dev_dbg(hsotg->dev, "%s(ep %p)\n", __func__, ep); @@ -4008,7 +4020,9 @@ static int dwc2_hsotg_ep_disable(struct usb_ep *ep) epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); - spin_lock_irqsave(&hsotg->lock, flags); + locked = spin_is_locked(&hsotg->lock); + if (!locked) + spin_lock_irqsave(&hsotg->lock, flags); ctrl = dwc2_readl(hsotg, epctrl_reg); @@ -4032,7 +4046,9 @@ static int dwc2_hsotg_ep_disable(struct usb_ep *ep) hs_ep->fifo_index = 0; hs_ep->fifo_size = 0; - spin_unlock_irqrestore(&hsotg->lock, flags); + if (!locked) + spin_unlock_irqrestore(&hsotg->lock, flags); + return 0; } -- cgit v1.2.3-58-ga151 From 2337a77c1cc86bc4e504ecf3799f947659c86026 Mon Sep 17 00:00:00 2001 From: Jia-Ju Bai <baijiaju1990@gmail.com> Date: Sat, 15 Sep 2018 11:04:40 +0800 Subject: usb: gadget: udc: fotg210-udc: Fix a sleep-in-atomic-context bug in fotg210_get_status() The driver may sleep in an interrupt handler. The function call path (from bottom to top) in Linux-4.17 is: [FUNC] fotg210_ep_queue(GFP_KERNEL) drivers/usb/gadget/udc/fotg210-udc.c, 744: fotg210_ep_queue in fotg210_get_status drivers/usb/gadget/udc/fotg210-udc.c, 768: fotg210_get_status in fotg210_setup_packet drivers/usb/gadget/udc/fotg210-udc.c, 949: fotg210_setup_packet in fotg210_irq (interrupt handler) To fix this bug, GFP_KERNEL is replaced with GFP_ATOMIC. If possible, spin_unlock() and spin_lock() around fotg210_ep_queue() can be also removed. This bug is found by my static analysis tool DSAC. Signed-off-by: Jia-Ju Bai <baijiaju1990@gmail.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/fotg210-udc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/fotg210-udc.c b/drivers/usb/gadget/udc/fotg210-udc.c index 587c5037ff07..bc6abaea907d 100644 --- a/drivers/usb/gadget/udc/fotg210-udc.c +++ b/drivers/usb/gadget/udc/fotg210-udc.c @@ -741,7 +741,7 @@ static void fotg210_get_status(struct fotg210_udc *fotg210, fotg210->ep0_req->length = 2; spin_unlock(&fotg210->lock); - fotg210_ep_queue(fotg210->gadget.ep0, fotg210->ep0_req, GFP_KERNEL); + fotg210_ep_queue(fotg210->gadget.ep0, fotg210->ep0_req, GFP_ATOMIC); spin_lock(&fotg210->lock); } -- cgit v1.2.3-58-ga151 From bb80e4fa57eb75ebd64ae9be4155da6d12c1a997 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni <alexandre.belloni@bootlin.com> Date: Mon, 10 Sep 2018 22:12:49 +0200 Subject: usb: gadget: udc: atmel: handle at91sam9rl PMC The at91sam9rl PMC is not quite the same as the at91sam9g45 one and now has its own compatible string. Add support for that. Fixes: 217bace8e548 ("ARM: dts: fix PMC compatible") Acked-by: Cristian Birsan <cristian.birsan@microchip.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/atmel_usba_udc.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 17147b8c771e..8f267be1745d 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -2017,6 +2017,8 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, udc->errata = match->data; udc->pmc = syscon_regmap_lookup_by_compatible("atmel,at91sam9g45-pmc"); + if (IS_ERR(udc->pmc)) + udc->pmc = syscon_regmap_lookup_by_compatible("atmel,at91sam9rl-pmc"); if (IS_ERR(udc->pmc)) udc->pmc = syscon_regmap_lookup_by_compatible("atmel,at91sam9x5-pmc"); if (udc->errata && IS_ERR(udc->pmc)) -- cgit v1.2.3-58-ga151 From 3def4031b3e3fbb524cbd01555b057a6cef0d5e6 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann <arnd@arndb.de> Date: Thu, 13 Sep 2018 11:37:31 +0200 Subject: usb: dwc3: add EXTCON dependency for qcom Like the omap back-end, we get a link error with CONFIG_EXTCON=m when building the qcom back-end into the kernel: drivers/usb/dwc3/dwc3-qcom.o: In function `dwc3_qcom_probe': dwc3-qcom.c:(.text+0x13dc): undefined reference to `extcon_get_edev_by_phandle' dwc3-qcom.c:(.text+0x1b18): undefined reference to `devm_extcon_register_notifier' dwc3-qcom.c:(.text+0x1b9c): undefined reference to `extcon_get_state' Do the same thing as OMAP and add an explicit dependency on EXTCON. Fixes: a4333c3a6ba9 ("usb: dwc3: Add Qualcomm DWC3 glue driver") Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc3/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index 518ead12458d..1a0404fda596 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -113,7 +113,7 @@ config USB_DWC3_ST config USB_DWC3_QCOM tristate "Qualcomm Platform" - depends on ARCH_QCOM || COMPILE_TEST + depends on EXTCON && (ARCH_QCOM || COMPILE_TEST) depends on OF default USB_DWC3 help -- cgit v1.2.3-58-ga151 From e0f681c2c11a25b76626cea77deb819a4754375d Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier <fabrice.gasnier@st.com> Date: Wed, 5 Sep 2018 13:40:02 +0200 Subject: usb: dwc2: get optional vbus-supply regulator once Move devm_regulator_get_optional() call to probe routine. This avoids 'vbus-supply' regulator to be requested lots of times, upon each call to dwc2_vbus_supply_init(), e.g. like with runtime pm. Fixes: 531ef5ebea96 ("usb: dwc2: add support for host mode external vbus supply") Tested-by: Artur Petrosyan <arturp@synopsys.com> Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com> Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/hcd.c | 12 +++--------- drivers/usb/dwc2/platform.c | 8 ++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 5f23b933cafc..24aa5a3acf86 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -358,16 +358,10 @@ static void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) static int dwc2_vbus_supply_init(struct dwc2_hsotg *hsotg) { - int ret; - - hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus"); - if (IS_ERR(hsotg->vbus_supply)) { - ret = PTR_ERR(hsotg->vbus_supply); - hsotg->vbus_supply = NULL; - return ret == -ENODEV ? 0 : ret; - } + if (hsotg->vbus_supply) + return regulator_enable(hsotg->vbus_supply); - return regulator_enable(hsotg->vbus_supply); + return 0; } static int dwc2_vbus_supply_exit(struct dwc2_hsotg *hsotg) diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 577642895b57..c0b64d483552 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -432,6 +432,14 @@ static int dwc2_driver_probe(struct platform_device *dev) if (retval) return retval; + hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus"); + if (IS_ERR(hsotg->vbus_supply)) { + retval = PTR_ERR(hsotg->vbus_supply); + hsotg->vbus_supply = NULL; + if (retval != -ENODEV) + return retval; + } + retval = dwc2_lowlevel_hw_enable(hsotg); if (retval) return retval; -- cgit v1.2.3-58-ga151 From 41ee1ea21052583eaf5487dfa0d0c907c9667548 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier <fabrice.gasnier@st.com> Date: Wed, 5 Sep 2018 13:40:03 +0200 Subject: usb: dwc2: fix a race with external vbus supply There's a race with root hub resume, when using external vbus supply. Root hub gets resumed, but runtime pm autosuspend runs as external vbus supply isn't enabled. So, host never exit from power down properly. Initialize vbus external supply before, rater that after hub resume. Fixes: 531ef5ebea96 ("usb: dwc2: add support for host mode external vbus supply") Tested-by: Artur Petrosyan <arturp@synopsys.com> Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com> Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/hcd.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 24aa5a3acf86..4fdf373db011 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -4384,6 +4384,7 @@ static int _dwc2_hcd_start(struct usb_hcd *hcd) struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); struct usb_bus *bus = hcd_to_bus(hcd); unsigned long flags; + int ret; dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); @@ -4399,6 +4400,13 @@ static int _dwc2_hcd_start(struct usb_hcd *hcd) dwc2_hcd_reinit(hsotg); + /* enable external vbus supply before resuming root hub */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_vbus_supply_init(hsotg); + if (ret) + return ret; + spin_lock_irqsave(&hsotg->lock, flags); + /* Initialize and connect root hub if one is not already attached */ if (bus->root_hub) { dev_dbg(hsotg->dev, "DWC OTG HCD Has Root Hub\n"); @@ -4408,7 +4416,7 @@ static int _dwc2_hcd_start(struct usb_hcd *hcd) spin_unlock_irqrestore(&hsotg->lock, flags); - return dwc2_vbus_supply_init(hsotg); + return 0; } /* -- cgit v1.2.3-58-ga151 From 5aa678c7fd5371769efde30763fb43a43a118cd0 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier <fabrice.gasnier@st.com> Date: Wed, 5 Sep 2018 13:40:04 +0200 Subject: usb: dwc2: fix call to vbus supply exit routine, call it unlocked dwc2_vbus_supply_exit() may call regulator_disable(). It shouldn't be called with interrupts disabled as it might sleep. This is seen with DEBUG_ATOMIC_SLEEP=y. Fixes: 531ef5ebea96 ("usb: dwc2: add support for host mode external vbus supply") Tested-by: Artur Petrosyan <arturp@synopsys.com> Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com> Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/hcd.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 4fdf373db011..103a0521466b 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -4481,7 +4481,9 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd) hprt0 |= HPRT0_SUSP; hprt0 &= ~HPRT0_PWR; dwc2_writel(hsotg, hprt0, HPRT0); + spin_unlock_irqrestore(&hsotg->lock, flags); dwc2_vbus_supply_exit(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); } /* Enter partial_power_down */ -- cgit v1.2.3-58-ga151 From cd7cd0e6cedfda8da6668a4af6748f96bbb6fed4 Mon Sep 17 00:00:00 2001 From: Fabrice Gasnier <fabrice.gasnier@st.com> Date: Wed, 5 Sep 2018 13:40:05 +0200 Subject: usb: dwc2: fix unbalanced use of external vbus-supply When using external vbus supply regulator, it should be enabled synchronously with PWR bit in HPRT register. This also fixes unbalanced use of this optional regulator (This can be reproduced easily when unbinding the driver). Fixes: 531ef5ebea96 ("usb: dwc2: add support for host mode external vbus supply") Tested-by: Artur Petrosyan <arturp@synopsys.com> Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com> Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/hcd.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 103a0521466b..dd82fa516f3f 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -3555,6 +3555,7 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, u32 port_status; u32 speed; u32 pcgctl; + u32 pwr; switch (typereq) { case ClearHubFeature: @@ -3603,8 +3604,11 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, dev_dbg(hsotg->dev, "ClearPortFeature USB_PORT_FEAT_POWER\n"); hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; hprt0 &= ~HPRT0_PWR; dwc2_writel(hsotg, hprt0, HPRT0); + if (pwr) + dwc2_vbus_supply_exit(hsotg); break; case USB_PORT_FEAT_INDICATOR: @@ -3814,8 +3818,11 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, dev_dbg(hsotg->dev, "SetPortFeature - USB_PORT_FEAT_POWER\n"); hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; hprt0 |= HPRT0_PWR; dwc2_writel(hsotg, hprt0, HPRT0); + if (!pwr) + dwc2_vbus_supply_init(hsotg); break; case USB_PORT_FEAT_RESET: @@ -3832,6 +3839,7 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, dwc2_writel(hsotg, 0, PCGCTL); hprt0 = dwc2_read_hprt0(hsotg); + pwr = hprt0 & HPRT0_PWR; /* Clear suspend bit if resetting from suspend state */ hprt0 &= ~HPRT0_SUSP; @@ -3845,6 +3853,8 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, dev_dbg(hsotg->dev, "In host mode, hprt0=%08x\n", hprt0); dwc2_writel(hsotg, hprt0, HPRT0); + if (!pwr) + dwc2_vbus_supply_init(hsotg); } /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ @@ -4384,6 +4394,7 @@ static int _dwc2_hcd_start(struct usb_hcd *hcd) struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); struct usb_bus *bus = hcd_to_bus(hcd); unsigned long flags; + u32 hprt0; int ret; dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); @@ -4400,12 +4411,16 @@ static int _dwc2_hcd_start(struct usb_hcd *hcd) dwc2_hcd_reinit(hsotg); - /* enable external vbus supply before resuming root hub */ - spin_unlock_irqrestore(&hsotg->lock, flags); - ret = dwc2_vbus_supply_init(hsotg); - if (ret) - return ret; - spin_lock_irqsave(&hsotg->lock, flags); + hprt0 = dwc2_read_hprt0(hsotg); + /* Has vbus power been turned on in dwc2_core_host_init ? */ + if (hprt0 & HPRT0_PWR) { + /* Enable external vbus supply before resuming root hub */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_vbus_supply_init(hsotg); + if (ret) + return ret; + spin_lock_irqsave(&hsotg->lock, flags); + } /* Initialize and connect root hub if one is not already attached */ if (bus->root_hub) { @@ -4427,6 +4442,7 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) { struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); unsigned long flags; + u32 hprt0; /* Turn off all host-specific interrupts */ dwc2_disable_host_interrupts(hsotg); @@ -4435,6 +4451,7 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) synchronize_irq(hcd->irq); spin_lock_irqsave(&hsotg->lock, flags); + hprt0 = dwc2_read_hprt0(hsotg); /* Ensure hcd is disconnected */ dwc2_hcd_disconnect(hsotg, true); dwc2_hcd_stop(hsotg); @@ -4443,7 +4460,9 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd) clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); spin_unlock_irqrestore(&hsotg->lock, flags); - dwc2_vbus_supply_exit(hsotg); + /* keep balanced supply init/exit by checking HPRT0_PWR */ + if (hprt0 & HPRT0_PWR) + dwc2_vbus_supply_exit(hsotg); usleep_range(1000, 3000); } -- cgit v1.2.3-58-ga151 From 87dd96111b0bb8e616fcbd74dbf4bb4182f2c596 Mon Sep 17 00:00:00 2001 From: Thinh Nguyen <Thinh.Nguyen@synopsys.com> Date: Tue, 11 Sep 2018 12:42:05 -0700 Subject: usb: dwc3: gadget: Check ENBLSLPM before sending ep command When operating in USB 2.0 speeds (HS/FS), if GUSB2PHYCFG.ENBLSLPM or GUSB2PHYCFG.SUSPHY is set, it must be cleared before issuing an endpoint command. Current implementation only save and restore GUSB2PHYCFG.SUSPHY configuration. We must save and clear both GUSB2PHYCFG.ENBLSLPM and GUSB2PHYCFG.SUSPHY settings. Restore them after the command is completed. DWC_usb3 3.30a and DWC_usb31 1.90a programming guide section 3.2.2 Signed-off-by: Thinh Nguyen <thinhn@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc3/gadget.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 2b53194081ba..679c12e14522 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -270,27 +270,36 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; struct dwc3 *dwc = dep->dwc; u32 timeout = 1000; + u32 saved_config = 0; u32 reg; int cmd_status = 0; - int susphy = false; int ret = -EINVAL; /* - * Synopsys Databook 2.60a states, on section 6.3.2.5.[1-8], that if - * we're issuing an endpoint command, we must check if - * GUSB2PHYCFG.SUSPHY bit is set. If it is, then we need to clear it. + * When operating in USB 2.0 speeds (HS/FS), if GUSB2PHYCFG.ENBLSLPM or + * GUSB2PHYCFG.SUSPHY is set, it must be cleared before issuing an + * endpoint command. * - * We will also set SUSPHY bit to what it was before returning as stated - * by the same section on Synopsys databook. + * Save and clear both GUSB2PHYCFG.ENBLSLPM and GUSB2PHYCFG.SUSPHY + * settings. Restore them after the command is completed. + * + * DWC_usb3 3.30a and DWC_usb31 1.90a programming guide section 3.2.2 */ if (dwc->gadget.speed <= USB_SPEED_HIGH) { reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); if (unlikely(reg & DWC3_GUSB2PHYCFG_SUSPHY)) { - susphy = true; + saved_config |= DWC3_GUSB2PHYCFG_SUSPHY; reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; - dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); } + + if (reg & DWC3_GUSB2PHYCFG_ENBLSLPM) { + saved_config |= DWC3_GUSB2PHYCFG_ENBLSLPM; + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + } + + if (saved_config) + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); } if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) { @@ -389,9 +398,9 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, } } - if (unlikely(susphy)) { + if (saved_config) { reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); - reg |= DWC3_GUSB2PHYCFG_SUSPHY; + reg |= saved_config; dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); } -- cgit v1.2.3-58-ga151 From 26eef8e0115d12fc996750e288975ebba13b7a91 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Tue, 11 Sep 2018 17:47:03 +0900 Subject: usb: renesas_usbhs: Add reset_control R-Car Gen3 needs to deassert resets of both host and peripheral. Since [eo]hci-platform is possible to assert the reset(s) when the probing failed, renesas_usbhs driver doesn't work correctly regardless of finished probing. To fix this issue, this patch adds reset_control on this renesas_usbhs driver. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/renesas_usbhs/common.c | 12 ++++++++++++ drivers/usb/renesas_usbhs/common.h | 2 ++ 2 files changed, 14 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index dc041110039c..03489d87ecc5 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -12,6 +12,7 @@ #include <linux/of_device.h> #include <linux/of_gpio.h> #include <linux/pm_runtime.h> +#include <linux/reset.h> #include <linux/slab.h> #include <linux/sysfs.h> #include "common.h" @@ -574,6 +575,10 @@ static int usbhs_probe(struct platform_device *pdev) return PTR_ERR(priv->edev); } + priv->rsts = devm_reset_control_array_get_optional_shared(&pdev->dev); + if (IS_ERR(priv->rsts)) + return PTR_ERR(priv->rsts); + /* * care platform info */ @@ -649,6 +654,10 @@ static int usbhs_probe(struct platform_device *pdev) /* dev_set_drvdata should be called after usbhs_mod_init */ platform_set_drvdata(pdev, priv); + ret = reset_control_deassert(priv->rsts); + if (ret) + goto probe_fail_rst; + /* * deviece reset here because * USB device might be used in boot loader. @@ -702,6 +711,8 @@ static int usbhs_probe(struct platform_device *pdev) return ret; probe_end_mod_exit: + reset_control_assert(priv->rsts); +probe_fail_rst: usbhs_mod_remove(priv); probe_end_fifo_exit: usbhs_fifo_remove(priv); @@ -730,6 +741,7 @@ static int usbhs_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); usbhs_platform_call(priv, hardware_exit, pdev); + reset_control_assert(priv->rsts); usbhs_mod_remove(priv); usbhs_fifo_remove(priv); usbhs_pipe_remove(priv); diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index 473e87e77cd4..f997a4dca9b6 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -10,6 +10,7 @@ #include <linux/extcon.h> #include <linux/platform_device.h> +#include <linux/reset.h> #include <linux/usb/renesas_usbhs.h> struct usbhs_priv; @@ -276,6 +277,7 @@ struct usbhs_priv { struct usbhs_fifo_info fifo_info; struct phy *phy; + struct reset_control *rsts; }; /* -- cgit v1.2.3-58-ga151 From 794f97a4b964a5f9880529a561ab26ed781bca56 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Tue, 11 Sep 2018 17:47:05 +0900 Subject: usb: renesas_usbhs: Add multiple clocks management R-Car Gen3 needs to enable clocks of both host and peripheral. Since [eo]hci-platform disables the reset(s) when the drivers are removed, renesas_usbhs driver doesn't work correctly. To fix this issue, this patch adds multiple clocks management on this renesas_usbhs driver. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/renesas_usbhs/common.c | 88 ++++++++++++++++++++++++++++++++++++++ drivers/usb/renesas_usbhs/common.h | 2 + 2 files changed, 90 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 03489d87ecc5..522cc091a59c 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -5,6 +5,7 @@ * Copyright (C) 2011 Renesas Solutions Corp. * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> */ +#include <linux/clk.h> #include <linux/err.h> #include <linux/gpio.h> #include <linux/io.h> @@ -291,6 +292,79 @@ static void usbhsc_set_buswait(struct usbhs_priv *priv) usbhs_bset(priv, BUSWAIT, 0x000F, wait); } +static bool usbhsc_is_multi_clks(struct usbhs_priv *priv) +{ + if (priv->dparam.type == USBHS_TYPE_RCAR_GEN3 || + priv->dparam.type == USBHS_TYPE_RCAR_GEN3_WITH_PLL) + return true; + + return false; +} + +static int usbhsc_clk_get(struct device *dev, struct usbhs_priv *priv) +{ + if (!usbhsc_is_multi_clks(priv)) + return 0; + + /* The first clock should exist */ + priv->clks[0] = of_clk_get(dev->of_node, 0); + if (IS_ERR(priv->clks[0])) + return PTR_ERR(priv->clks[0]); + + /* + * To backward compatibility with old DT, this driver checks the return + * value if it's -ENOENT or not. + */ + priv->clks[1] = of_clk_get(dev->of_node, 1); + if (PTR_ERR(priv->clks[1]) == -ENOENT) + priv->clks[1] = NULL; + else if (IS_ERR(priv->clks[1])) + return PTR_ERR(priv->clks[1]); + + return 0; +} + +static void usbhsc_clk_put(struct usbhs_priv *priv) +{ + int i; + + if (!usbhsc_is_multi_clks(priv)) + return; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) + clk_put(priv->clks[i]); +} + +static int usbhsc_clk_prepare_enable(struct usbhs_priv *priv) +{ + int i, ret; + + if (!usbhsc_is_multi_clks(priv)) + return 0; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) { + ret = clk_prepare_enable(priv->clks[i]); + if (ret) { + while (--i >= 0) + clk_disable_unprepare(priv->clks[i]); + return ret; + } + } + + return ret; +} + +static void usbhsc_clk_disable_unprepare(struct usbhs_priv *priv) +{ + int i; + + if (!usbhsc_is_multi_clks(priv)) + return; + + for (i = 0; i < ARRAY_SIZE(priv->clks); i++) + clk_disable_unprepare(priv->clks[i]); +} + /* * platform default param */ @@ -341,6 +415,10 @@ static void usbhsc_power_ctrl(struct usbhs_priv *priv, int enable) /* enable PM */ pm_runtime_get_sync(dev); + /* enable clks */ + if (usbhsc_clk_prepare_enable(priv)) + return; + /* enable platform power */ usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); @@ -353,6 +431,9 @@ static void usbhsc_power_ctrl(struct usbhs_priv *priv, int enable) /* disable platform power */ usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); + /* disable clks */ + usbhsc_clk_disable_unprepare(priv); + /* disable PM */ pm_runtime_put_sync(dev); } @@ -658,6 +739,10 @@ static int usbhs_probe(struct platform_device *pdev) if (ret) goto probe_fail_rst; + ret = usbhsc_clk_get(&pdev->dev, priv); + if (ret) + goto probe_fail_clks; + /* * deviece reset here because * USB device might be used in boot loader. @@ -711,6 +796,8 @@ static int usbhs_probe(struct platform_device *pdev) return ret; probe_end_mod_exit: + usbhsc_clk_put(priv); +probe_fail_clks: reset_control_assert(priv->rsts); probe_fail_rst: usbhs_mod_remove(priv); @@ -741,6 +828,7 @@ static int usbhs_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); usbhs_platform_call(priv, hardware_exit, pdev); + usbhsc_clk_put(priv); reset_control_assert(priv->rsts); usbhs_mod_remove(priv); usbhs_fifo_remove(priv); diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index f997a4dca9b6..3777af848a35 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -8,6 +8,7 @@ #ifndef RENESAS_USB_DRIVER_H #define RENESAS_USB_DRIVER_H +#include <linux/clk.h> #include <linux/extcon.h> #include <linux/platform_device.h> #include <linux/reset.h> @@ -278,6 +279,7 @@ struct usbhs_priv { struct phy *phy; struct reset_control *rsts; + struct clk *clks[2]; }; /* -- cgit v1.2.3-58-ga151 From adc23f16bcc56768500034cd2e398f60b120ee42 Mon Sep 17 00:00:00 2001 From: Chunfeng Yun <chunfeng.yun@mediatek.com> Date: Fri, 7 Sep 2018 14:00:39 +0800 Subject: usb: mtu3: disable vbus rise/fall interrupts of ltssm The vbus rise & fall interrupts are used to enable and disable U3 function of device automatically, this cause some issues when class driver is initialized as deactivated, and will skip over software-controlled connect by pullup(), but UDC wants to keep disconnect until usb_gadget_activate() is called which calls pullup() if needed. So we disable vbus rise & fall interrupts and just use pullup() to enable & disable U3 function, and reset mtu3 state when disconnect instead when vbus fall. Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/mtu3/mtu3_core.c | 4 ++-- drivers/usb/mtu3/mtu3_gadget.c | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/mtu3/mtu3_core.c b/drivers/usb/mtu3/mtu3_core.c index d045d8458f81..ae70b9bfd797 100644 --- a/drivers/usb/mtu3/mtu3_core.c +++ b/drivers/usb/mtu3/mtu3_core.c @@ -185,8 +185,8 @@ static void mtu3_intr_enable(struct mtu3 *mtu) if (mtu->is_u3_ip) { /* Enable U3 LTSSM interrupts */ - value = HOT_RST_INTR | WARM_RST_INTR | VBUS_RISE_INTR | - VBUS_FALL_INTR | ENTER_U3_INTR | EXIT_U3_INTR; + value = HOT_RST_INTR | WARM_RST_INTR | + ENTER_U3_INTR | EXIT_U3_INTR; mtu3_writel(mbase, U3D_LTSSM_INTR_ENABLE, value); } diff --git a/drivers/usb/mtu3/mtu3_gadget.c b/drivers/usb/mtu3/mtu3_gadget.c index 5c60a8c5a0b5..bbcd3332471d 100644 --- a/drivers/usb/mtu3/mtu3_gadget.c +++ b/drivers/usb/mtu3/mtu3_gadget.c @@ -585,6 +585,17 @@ static const struct usb_gadget_ops mtu3_gadget_ops = { .udc_stop = mtu3_gadget_stop, }; +static void mtu3_state_reset(struct mtu3 *mtu) +{ + mtu->address = 0; + mtu->ep0_state = MU3D_EP0_STATE_SETUP; + mtu->may_wakeup = 0; + mtu->u1_enable = 0; + mtu->u2_enable = 0; + mtu->delayed_status = false; + mtu->test_mode = false; +} + static void init_hw_ep(struct mtu3 *mtu, struct mtu3_ep *mep, u32 epnum, u32 is_in) { @@ -702,6 +713,7 @@ void mtu3_gadget_disconnect(struct mtu3 *mtu) spin_lock(&mtu->lock); } + mtu3_state_reset(mtu); usb_gadget_set_state(&mtu->g, USB_STATE_NOTATTACHED); } @@ -712,12 +724,6 @@ void mtu3_gadget_reset(struct mtu3 *mtu) /* report disconnect, if we didn't flush EP state */ if (mtu->g.speed != USB_SPEED_UNKNOWN) mtu3_gadget_disconnect(mtu); - - mtu->address = 0; - mtu->ep0_state = MU3D_EP0_STATE_SETUP; - mtu->may_wakeup = 0; - mtu->u1_enable = 0; - mtu->u2_enable = 0; - mtu->delayed_status = false; - mtu->test_mode = false; + else + mtu3_state_reset(mtu); } -- cgit v1.2.3-58-ga151 From 4ab2b48c98f2ec9712452d520a381917f91ac3d2 Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire <hofrat@osadl.org> Date: Thu, 30 Aug 2018 12:16:58 +0200 Subject: usb: gadget: fsl_udc_core: check allocation return value and cleanup on failure The allocation with fsl_alloc_request() and kmalloc() were unchecked fixed this up with a NULL check and appropriate cleanup. Additionally udc->ep_qh_size was reset to 0 on failure of allocation. Similar udc->phy_mode is initially 0 (as udc_controller was allocated with kzalloc in fsl_udc_probe()) so reset it to 0 as well so that this function is side-effect free on failure. Not clear if this is necessary or sensible as fsl_udc_release() probably can not be called if fsl_udc_probe() failed - but it should not hurt. Signed-off-by: Nicholas Mc Guire <hofrat@osadl.org> Fixes: b504882da5 ("USB: add Freescale high-speed USB SOC device controller driver") Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/fsl_udc_core.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index be59309e848c..845dee3d93d6 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2247,8 +2247,10 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->phy_mode = pdata->phy_mode; udc->eps = kcalloc(udc->max_ep, sizeof(struct fsl_ep), GFP_KERNEL); - if (!udc->eps) - return -1; + if (!udc->eps) { + ERR("kmalloc udc endpoint status failed\n"); + goto eps_alloc_failed; + } /* initialized QHs, take care of alignment */ size = udc->max_ep * sizeof(struct ep_queue_head); @@ -2262,8 +2264,7 @@ static int struct_udc_setup(struct fsl_udc *udc, &udc->ep_qh_dma, GFP_KERNEL); if (!udc->ep_qh) { ERR("malloc QHs for udc failed\n"); - kfree(udc->eps); - return -1; + goto ep_queue_alloc_failed; } udc->ep_qh_size = size; @@ -2272,8 +2273,17 @@ static int struct_udc_setup(struct fsl_udc *udc, /* FIXME: fsl_alloc_request() ignores ep argument */ udc->status_req = container_of(fsl_alloc_request(NULL, GFP_KERNEL), struct fsl_req, req); + if (!udc->status_req) { + ERR("kzalloc for udc status request failed\n"); + goto udc_status_alloc_failed; + } + /* allocate a small amount of memory to get valid address */ udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + if (!udc->status_req->req.buf) { + ERR("kzalloc for udc request buffer failed\n"); + goto udc_req_buf_alloc_failed; + } udc->resume_state = USB_STATE_NOTATTACHED; udc->usb_state = USB_STATE_POWERED; @@ -2281,6 +2291,18 @@ static int struct_udc_setup(struct fsl_udc *udc, udc->remote_wakeup = 0; /* default to 0 on reset */ return 0; + +udc_req_buf_alloc_failed: + kfree(udc->status_req); +udc_status_alloc_failed: + kfree(udc->ep_qh); + udc->ep_qh_size = 0; +ep_queue_alloc_failed: + kfree(udc->eps); +eps_alloc_failed: + udc->phy_mode = 0; + return -1; + } /*---------------------------------------------------------------- -- cgit v1.2.3-58-ga151 From 24b804e40f23a199e6d82de5b5571bb642490ca1 Mon Sep 17 00:00:00 2001 From: Nicholas Mc Guire <hofrat@osadl.org> Date: Thu, 30 Aug 2018 12:16:59 +0200 Subject: usb: gadget: fsl_udc_core: fixup struct_udc_setup documentation The original implementation from commit b504882da539 ("USB: add Freescale high-speed USB SOC device controller driver") returned NULL on failure and an allocated + initialized struct fsl_udc on success. The current code introduced in commit 4365831dadfe ("USB: fsl_usb2_udc: Get max ep number from DCCPARAMS register") only provides partial initialization as well as returning 0 on success and -1 on failures. The function documentation is updated accordingly. Signed-off-by: Nicholas Mc Guire <hofrat@osadl.org> Fixes: 4365831dadfe ("USB: fsl_usb2_udc: Get max ep number from DCCPARAMS register") Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/fsl_udc_core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 845dee3d93d6..20141c3096f6 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -2234,8 +2234,10 @@ static void fsl_udc_release(struct device *dev) Internal structure setup functions *******************************************************************/ /*------------------------------------------------------------------ - * init resource for globle controller - * Return the udc handle on success or NULL on failure + * init resource for global controller called by fsl_udc_probe() + * On success the udc handle is initialized, on failure it is + * unchanged (reset). + * Return 0 on success and -1 on allocation failure ------------------------------------------------------------------*/ static int struct_udc_setup(struct fsl_udc *udc, struct platform_device *pdev) -- cgit v1.2.3-58-ga151 From 6fd573e1a7bf0e5f2cbb902e3cdf11a6714fb1f1 Mon Sep 17 00:00:00 2001 From: Rob Herring <robh@kernel.org> Date: Wed, 29 Aug 2018 15:01:11 -0500 Subject: usb: gadget: atmel: remove pointless retrieval of DT name property The name is always non-NULL and then is not used anywhere in this function, so remove it. Cc: Nicolas Ferre <nicolas.ferre@microchip.com> Cc: Felipe Balbi <balbi@kernel.org> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Alexandre Belloni <alexandre.belloni@bootlin.com> Cc: linux-arm-kernel@lists.infradead.org Cc: linux-usb@vger.kernel.org Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com> Signed-off-by: Rob Herring <robh@kernel.org> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/atmel_usba_udc.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 8f267be1745d..11247322d587 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -2004,7 +2004,6 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, struct usba_udc *udc) { u32 val; - const char *name; struct device_node *np = pdev->dev.of_node; const struct of_device_id *match; struct device_node *pp; @@ -2096,11 +2095,6 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, ep->can_dma = of_property_read_bool(pp, "atmel,can-dma"); ep->can_isoc = of_property_read_bool(pp, "atmel,can-isoc"); - ret = of_property_read_string(pp, "name", &name); - if (ret) { - dev_err(&pdev->dev, "of_probe: name error(%d)\n", ret); - goto err; - } sprintf(ep->name, "ep%d", ep->index); ep->ep.name = ep->name; -- cgit v1.2.3-58-ga151 From 6af19fd10595936abb02c9199cd9ea1ff0411490 Mon Sep 17 00:00:00 2001 From: Faisal Mehmood <f.m3hm00d@gmail.com> Date: Sat, 18 Aug 2018 21:49:54 +0500 Subject: usb: dwc3: Fix spelling of 'optimizations' 'optimizations' was misspelled as 'optmizations'. Fixed it. It is a coding style change which should have no impact on runtime execution of code. Signed-off-by: Faisal Mehmood <f.m3hm00d@gmail.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc3/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 88c80fcc39f5..becfbb87f791 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -756,7 +756,7 @@ static void dwc3_core_setup_global_control(struct dwc3 *dwc) /* check if current dwc3 is on simulation board */ if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) { - dev_info(dwc->dev, "Running with FPGA optmizations\n"); + dev_info(dwc->dev, "Running with FPGA optimizations\n"); dwc->is_fpga = true; } -- cgit v1.2.3-58-ga151 From 0a55187a1ec8c03d0619e7ce41d10fdc39cff036 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Fri, 10 Aug 2018 15:32:25 -0400 Subject: USB: gadget core: Issue ->disconnect() callback from usb_gadget_disconnect() The gadget documentation doesn't state clearly whether a gadget driver's ->disconnect() callback should be invoked when the D+ pullup is turned off. Some UDC drivers do this and some don't. This patch settles the issue by making the core function usb_gadget_disconnect() issue the callback, so that UDC drivers don't need to worry about it. A description of the new behavior is added to the function's kerneldoc. Also, the patch removes a few superseded callbacks from other core routines. Future patches will remove the ->disconnect() calls from the UDC drivers that make them, as they are now unnecessary. Until all those patches are merged gadget drivers may receive extra ->disconnect() callbacks, but this should be harmless. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index af88b48c1cea..87d6b12779f2 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -690,6 +690,9 @@ EXPORT_SYMBOL_GPL(usb_gadget_connect); * as a disconnect (when a VBUS session is active). Not all systems * support software pullup controls. * + * Following a successful disconnect, invoke the ->disconnect() callback + * for the current gadget driver so that UDC drivers don't need to. + * * Returns zero on success, else negative errno. */ int usb_gadget_disconnect(struct usb_gadget *gadget) @@ -711,8 +714,10 @@ int usb_gadget_disconnect(struct usb_gadget *gadget) } ret = gadget->ops->pullup(gadget, 0); - if (!ret) + if (!ret) { gadget->connected = 0; + gadget->udc->driver->disconnect(gadget); + } out: trace_usb_gadget_disconnect(gadget, ret); @@ -1281,7 +1286,6 @@ static void usb_gadget_remove_driver(struct usb_udc *udc) kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); usb_gadget_disconnect(udc->gadget); - udc->driver->disconnect(udc->gadget); udc->driver->unbind(udc->gadget); usb_gadget_udc_stop(udc); @@ -1471,7 +1475,6 @@ static ssize_t soft_connect_store(struct device *dev, usb_gadget_connect(udc->gadget); } else if (sysfs_streq(buf, "disconnect")) { usb_gadget_disconnect(udc->gadget); - udc->driver->disconnect(udc->gadget); usb_gadget_udc_stop(udc); } else { dev_err(dev, "unsupported command '%s'\n", buf); -- cgit v1.2.3-58-ga151 From 3fa4eaa6c08206b5fa6a8ba49b891d6aab243f52 Mon Sep 17 00:00:00 2001 From: Andreas Pape <apape@de.adit-jv.com> Date: Thu, 21 Jun 2018 17:22:51 +0200 Subject: usb: gadget: f_uac2: disable IN/OUT ep if unused Via p_chmask/c_chmask the user can define whether uac2 shall support playback and/or capture. This has only effect on the created ALSA device, but not on the USB descriptor. This patch adds playback/capture descriptors dependent on that parameter. Signed-off-by: Andreas Pape <apape@de.adit-jv.com> Signed-off-by: Eugeniu Rosca <erosca@de.adit-jv.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/function/f_uac2.c | 216 ++++++++++++++++++++++++++++------- 1 file changed, 172 insertions(+), 44 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index d582921f7257..db2d4980cb35 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -22,12 +22,8 @@ * controlled by two clock sources : * CLK_5 := c_srate, and CLK_6 := p_srate */ -#define USB_OUT_IT_ID 1 -#define IO_IN_IT_ID 2 -#define IO_OUT_OT_ID 3 -#define USB_IN_OT_ID 4 -#define USB_OUT_CLK_ID 5 -#define USB_IN_CLK_ID 6 +#define USB_OUT_CLK_ID (out_clk_src_desc.bClockID) +#define USB_IN_CLK_ID (in_clk_src_desc.bClockID) #define CONTROL_ABSENT 0 #define CONTROL_RDONLY 1 @@ -43,6 +39,9 @@ #define UNFLW_CTRL 8 #define OVFLW_CTRL 10 +#define EPIN_EN(_opts) ((_opts)->p_chmask != 0) +#define EPOUT_EN(_opts) ((_opts)->c_chmask != 0) + struct f_uac2 { struct g_audio g_audio; u8 ac_intf, as_in_intf, as_out_intf; @@ -135,7 +134,7 @@ static struct uac_clock_source_descriptor in_clk_src_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC2_CLOCK_SOURCE, - .bClockID = USB_IN_CLK_ID, + /* .bClockID = DYNAMIC */ .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL), .bAssocTerminal = 0, @@ -147,7 +146,7 @@ static struct uac_clock_source_descriptor out_clk_src_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC2_CLOCK_SOURCE, - .bClockID = USB_OUT_CLK_ID, + /* .bClockID = DYNAMIC */ .bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED, .bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL), .bAssocTerminal = 0, @@ -159,10 +158,10 @@ static struct uac2_input_terminal_descriptor usb_out_it_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_INPUT_TERMINAL, - .bTerminalID = USB_OUT_IT_ID, + /* .bTerminalID = DYNAMIC */ .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), .bAssocTerminal = 0, - .bCSourceID = USB_OUT_CLK_ID, + /* .bCSourceID = DYNAMIC */ .iChannelNames = 0, .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), }; @@ -173,10 +172,10 @@ static struct uac2_input_terminal_descriptor io_in_it_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_INPUT_TERMINAL, - .bTerminalID = IO_IN_IT_ID, + /* .bTerminalID = DYNAMIC */ .wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED), .bAssocTerminal = 0, - .bCSourceID = USB_IN_CLK_ID, + /* .bCSourceID = DYNAMIC */ .iChannelNames = 0, .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), }; @@ -187,11 +186,11 @@ static struct uac2_output_terminal_descriptor usb_in_ot_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, - .bTerminalID = USB_IN_OT_ID, + /* .bTerminalID = DYNAMIC */ .wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING), .bAssocTerminal = 0, - .bSourceID = IO_IN_IT_ID, - .bCSourceID = USB_IN_CLK_ID, + /* .bSourceID = DYNAMIC */ + /* .bCSourceID = DYNAMIC */ .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), }; @@ -201,11 +200,11 @@ static struct uac2_output_terminal_descriptor io_out_ot_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, - .bTerminalID = IO_OUT_OT_ID, + /* .bTerminalID = DYNAMIC */ .wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED), .bAssocTerminal = 0, - .bSourceID = USB_OUT_IT_ID, - .bCSourceID = USB_OUT_CLK_ID, + /* .bSourceID = DYNAMIC */ + /* .bCSourceID = DYNAMIC */ .bmControls = cpu_to_le16(CONTROL_RDWR << COPY_CTRL), }; @@ -253,7 +252,7 @@ static struct uac2_as_header_descriptor as_out_hdr_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_AS_GENERAL, - .bTerminalLink = USB_OUT_IT_ID, + /* .bTerminalLink = DYNAMIC */ .bmControls = 0, .bFormatType = UAC_FORMAT_TYPE_I, .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM), @@ -330,7 +329,7 @@ static struct uac2_as_header_descriptor as_in_hdr_desc = { .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubtype = UAC_AS_GENERAL, - .bTerminalLink = USB_IN_OT_ID, + /* .bTerminalLink = DYNAMIC */ .bmControls = 0, .bFormatType = UAC_FORMAT_TYPE_I, .bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM), @@ -471,6 +470,125 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, le16_to_cpu(ep_desc->wMaxPacketSize))); } +/* Use macro to overcome line length limitation */ +#define USBDHDR(p) (struct usb_descriptor_header *)(p) + +static void setup_descriptor(struct f_uac2_opts *opts) +{ + /* patch descriptors */ + int i = 1; /* ID's start with 1 */ + + if (EPOUT_EN(opts)) + usb_out_it_desc.bTerminalID = i++; + if (EPIN_EN(opts)) + io_in_it_desc.bTerminalID = i++; + if (EPOUT_EN(opts)) + io_out_ot_desc.bTerminalID = i++; + if (EPIN_EN(opts)) + usb_in_ot_desc.bTerminalID = i++; + if (EPOUT_EN(opts)) + out_clk_src_desc.bClockID = i++; + if (EPIN_EN(opts)) + in_clk_src_desc.bClockID = i++; + + usb_out_it_desc.bCSourceID = out_clk_src_desc.bClockID; + usb_in_ot_desc.bSourceID = io_in_it_desc.bTerminalID; + usb_in_ot_desc.bCSourceID = in_clk_src_desc.bClockID; + io_in_it_desc.bCSourceID = in_clk_src_desc.bClockID; + io_out_ot_desc.bCSourceID = out_clk_src_desc.bClockID; + io_out_ot_desc.bSourceID = usb_out_it_desc.bTerminalID; + as_out_hdr_desc.bTerminalLink = usb_out_it_desc.bTerminalID; + as_in_hdr_desc.bTerminalLink = usb_in_ot_desc.bTerminalID; + + iad_desc.bInterfaceCount = 1; + ac_hdr_desc.wTotalLength = 0; + + if (EPIN_EN(opts)) { + u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength); + + len += sizeof(in_clk_src_desc); + len += sizeof(usb_in_ot_desc); + len += sizeof(io_in_it_desc); + ac_hdr_desc.wTotalLength = cpu_to_le16(len); + iad_desc.bInterfaceCount++; + } + if (EPOUT_EN(opts)) { + u16 len = le16_to_cpu(ac_hdr_desc.wTotalLength); + + len += sizeof(out_clk_src_desc); + len += sizeof(usb_out_it_desc); + len += sizeof(io_out_ot_desc); + ac_hdr_desc.wTotalLength = cpu_to_le16(len); + iad_desc.bInterfaceCount++; + } + + i = 0; + fs_audio_desc[i++] = USBDHDR(&iad_desc); + fs_audio_desc[i++] = USBDHDR(&std_ac_if_desc); + fs_audio_desc[i++] = USBDHDR(&ac_hdr_desc); + if (EPIN_EN(opts)) + fs_audio_desc[i++] = USBDHDR(&in_clk_src_desc); + if (EPOUT_EN(opts)) { + fs_audio_desc[i++] = USBDHDR(&out_clk_src_desc); + fs_audio_desc[i++] = USBDHDR(&usb_out_it_desc); + } + if (EPIN_EN(opts)) { + fs_audio_desc[i++] = USBDHDR(&io_in_it_desc); + fs_audio_desc[i++] = USBDHDR(&usb_in_ot_desc); + } + if (EPOUT_EN(opts)) { + fs_audio_desc[i++] = USBDHDR(&io_out_ot_desc); + fs_audio_desc[i++] = USBDHDR(&std_as_out_if0_desc); + fs_audio_desc[i++] = USBDHDR(&std_as_out_if1_desc); + fs_audio_desc[i++] = USBDHDR(&as_out_hdr_desc); + fs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc); + fs_audio_desc[i++] = USBDHDR(&fs_epout_desc); + fs_audio_desc[i++] = USBDHDR(&as_iso_out_desc); + } + if (EPIN_EN(opts)) { + fs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc); + fs_audio_desc[i++] = USBDHDR(&std_as_in_if1_desc); + fs_audio_desc[i++] = USBDHDR(&as_in_hdr_desc); + fs_audio_desc[i++] = USBDHDR(&as_in_fmt1_desc); + fs_audio_desc[i++] = USBDHDR(&fs_epin_desc); + fs_audio_desc[i++] = USBDHDR(&as_iso_in_desc); + } + fs_audio_desc[i] = NULL; + + i = 0; + hs_audio_desc[i++] = USBDHDR(&iad_desc); + hs_audio_desc[i++] = USBDHDR(&std_ac_if_desc); + hs_audio_desc[i++] = USBDHDR(&ac_hdr_desc); + if (EPIN_EN(opts)) + hs_audio_desc[i++] = USBDHDR(&in_clk_src_desc); + if (EPOUT_EN(opts)) { + hs_audio_desc[i++] = USBDHDR(&out_clk_src_desc); + hs_audio_desc[i++] = USBDHDR(&usb_out_it_desc); + } + if (EPIN_EN(opts)) { + hs_audio_desc[i++] = USBDHDR(&io_in_it_desc); + hs_audio_desc[i++] = USBDHDR(&usb_in_ot_desc); + } + if (EPOUT_EN(opts)) { + hs_audio_desc[i++] = USBDHDR(&io_out_ot_desc); + hs_audio_desc[i++] = USBDHDR(&std_as_out_if0_desc); + hs_audio_desc[i++] = USBDHDR(&std_as_out_if1_desc); + hs_audio_desc[i++] = USBDHDR(&as_out_hdr_desc); + hs_audio_desc[i++] = USBDHDR(&as_out_fmt1_desc); + hs_audio_desc[i++] = USBDHDR(&hs_epout_desc); + hs_audio_desc[i++] = USBDHDR(&as_iso_out_desc); + } + if (EPIN_EN(opts)) { + hs_audio_desc[i++] = USBDHDR(&std_as_in_if0_desc); + hs_audio_desc[i++] = USBDHDR(&std_as_in_if1_desc); + hs_audio_desc[i++] = USBDHDR(&as_in_hdr_desc); + hs_audio_desc[i++] = USBDHDR(&as_in_fmt1_desc); + hs_audio_desc[i++] = USBDHDR(&hs_epin_desc); + hs_audio_desc[i++] = USBDHDR(&as_iso_in_desc); + } + hs_audio_desc[i] = NULL; +} + static int afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) { @@ -530,25 +648,29 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) uac2->ac_intf = ret; uac2->ac_alt = 0; - ret = usb_interface_id(cfg, fn); - if (ret < 0) { - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + if (EPOUT_EN(uac2_opts)) { + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + std_as_out_if0_desc.bInterfaceNumber = ret; + std_as_out_if1_desc.bInterfaceNumber = ret; + uac2->as_out_intf = ret; + uac2->as_out_alt = 0; } - std_as_out_if0_desc.bInterfaceNumber = ret; - std_as_out_if1_desc.bInterfaceNumber = ret; - uac2->as_out_intf = ret; - uac2->as_out_alt = 0; - ret = usb_interface_id(cfg, fn); - if (ret < 0) { - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return ret; + if (EPIN_EN(uac2_opts)) { + ret = usb_interface_id(cfg, fn); + if (ret < 0) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return ret; + } + std_as_in_if0_desc.bInterfaceNumber = ret; + std_as_in_if1_desc.bInterfaceNumber = ret; + uac2->as_in_intf = ret; + uac2->as_in_alt = 0; } - std_as_in_if0_desc.bInterfaceNumber = ret; - std_as_in_if1_desc.bInterfaceNumber = ret; - uac2->as_in_intf = ret; - uac2->as_in_alt = 0; /* Calculate wMaxPacketSize according to audio bandwidth */ set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); @@ -556,16 +678,20 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); - agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); - if (!agdev->out_ep) { - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + if (EPOUT_EN(uac2_opts)) { + agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); + if (!agdev->out_ep) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -ENODEV; + } } - agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc); - if (!agdev->in_ep) { - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - return -ENODEV; + if (EPIN_EN(uac2_opts)) { + agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc); + if (!agdev->in_ep) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -ENODEV; + } } agdev->in_ep_maxpsize = max_t(u16, @@ -578,6 +704,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; + setup_descriptor(uac2_opts); + ret = usb_assign_descriptors(fn, fs_audio_desc, hs_audio_desc, NULL, NULL); if (ret) -- cgit v1.2.3-58-ga151 From ce66ab1df6707cb5aeed0a0706ce69002aeb86ca Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:29 +0900 Subject: Revert "usb: renesas_usbhs: set the mode by using extcon state for non-otg channel" This reverts commit cd14247d5c14b9b20bb3d3dfcaa899ca22c8dccc. R-Car D3 can use OTG mode in fact. So, the commit doesn't need anymore. Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/renesas_usbhs/rcar3.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c index b9a8453a5e68..50e5fb55c8a0 100644 --- a/drivers/usb/renesas_usbhs/rcar3.c +++ b/drivers/usb/renesas_usbhs/rcar3.c @@ -27,7 +27,6 @@ * Remarks: bit[31:11] and bit[9:6] should be 0 */ #define UGCTRL2_RESERVED_3 0x00000001 /* bit[3:0] should be B'0001 */ -#define UGCTRL2_USB0SEL_EHCI 0x00000010 #define UGCTRL2_USB0SEL_HSUSB 0x00000020 #define UGCTRL2_USB0SEL_OTG 0x00000030 #define UGCTRL2_VBUSSEL 0x00000400 @@ -50,14 +49,6 @@ static void usbhs_rcar3_set_ugctrl2(struct usbhs_priv *priv, u32 val) usbhs_write32(priv, UGCTRL2, val | UGCTRL2_RESERVED_3); } -static void usbhs_rcar3_set_usbsel(struct usbhs_priv *priv, bool ehci) -{ - if (ehci) - usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_EHCI); - else - usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_HSUSB); -} - static int usbhs_rcar3_power_ctrl(struct platform_device *pdev, void __iomem *base, int enable) { @@ -83,14 +74,10 @@ static int usbhs_rcar3_power_and_pll_ctrl(struct platform_device *pdev, struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); u32 val; int timeout = 1000; - bool is_host = false; if (enable) { usbhs_write32(priv, UGCTRL, 0); /* release PLLRESET */ - if (priv->edev) - is_host = extcon_get_state(priv->edev, EXTCON_USB_HOST); - - usbhs_rcar3_set_usbsel(priv, is_host); + usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_HSUSB); usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM); do { -- cgit v1.2.3-58-ga151 From c6fe39356a09fc23fce37cf8980704d9e180d94f Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:30 +0900 Subject: usb: renesas_usbhs: rcar3: Use OTG mode for R-Car D3 Since R-Car D3 can use OTG mode, this patch changes the UGCTRL2 value to UGCTRL2_USB0SEL_OTG and UGCTRL2_VBUSSEL like other R-Car Gen3 SoCs. Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/renesas_usbhs/rcar3.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/rcar3.c b/drivers/usb/renesas_usbhs/rcar3.c index 50e5fb55c8a0..aa3820448286 100644 --- a/drivers/usb/renesas_usbhs/rcar3.c +++ b/drivers/usb/renesas_usbhs/rcar3.c @@ -77,7 +77,8 @@ static int usbhs_rcar3_power_and_pll_ctrl(struct platform_device *pdev, if (enable) { usbhs_write32(priv, UGCTRL, 0); /* release PLLRESET */ - usbhs_rcar3_set_ugctrl2(priv, UGCTRL2_USB0SEL_HSUSB); + usbhs_rcar3_set_ugctrl2(priv, + UGCTRL2_USB0SEL_OTG | UGCTRL2_VBUSSEL); usbhs_bset(priv, LPSTS, LPSTS_SUSPM, LPSTS_SUSPM); do { -- cgit v1.2.3-58-ga151 From 54e4f66ba83c3091a0b572879143c7354730571f Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Fri, 21 Sep 2018 21:26:32 +0900 Subject: usb: renesas_usbhs: add support for R-Car E3 This patch adds support for R-Car E3. This SoC needs to release the PLL reset by the UGCTRL register like R-Car D3. So, this patch adds a usbhs_of_match entry for this SoC with "USBHS_TYPE_RCAR_GEN3_WITH_PLL". Reviewed-by: Simon Horman <horms+renesas@verge.net.au> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/renesas_usbhs/common.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 522cc091a59c..a3e1290d682d 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -559,6 +559,10 @@ static const struct of_device_id usbhs_of_match[] = { .compatible = "renesas,usbhs-r8a7796", .data = (void *)USBHS_TYPE_RCAR_GEN3, }, + { + .compatible = "renesas,usbhs-r8a77990", + .data = (void *)USBHS_TYPE_RCAR_GEN3_WITH_PLL, + }, { .compatible = "renesas,usbhs-r8a77995", .data = (void *)USBHS_TYPE_RCAR_GEN3_WITH_PLL, -- cgit v1.2.3-58-ga151 From c464da0bff6ab6fd39b4603d017de940832bc388 Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 20:59:07 +0400 Subject: usb: dwc2: Update registers definitions to support service interval Added GHWCFG4_SERVICE_INTERVAL_SUPPORTED and DCTL_SERVICE_INTERVAL_SUPPORTED bits definitions to support service interval based scheduling. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/hw.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 0ca8e7bc7aaf..524629428439 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -312,6 +312,7 @@ #define GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT 14 #define GHWCFG4_ACG_SUPPORTED BIT(12) #define GHWCFG4_IPG_ISOC_SUPPORTED BIT(11) +#define GHWCFG4_SERVICE_INTERVAL_SUPPORTED BIT(10) #define GHWCFG4_UTMI_PHY_DATA_WIDTH_8 0 #define GHWCFG4_UTMI_PHY_DATA_WIDTH_16 1 #define GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16 2 @@ -443,6 +444,7 @@ #define DCFG_DEVSPD_FS48 3 #define DCTL HSOTG_REG(0x804) +#define DCTL_SERVICE_INTERVAL_SUPPORTED BIT(19) #define DCTL_PWRONPRGDONE BIT(11) #define DCTL_CGOUTNAK BIT(10) #define DCTL_SGOUTNAK BIT(9) -- cgit v1.2.3-58-ga151 From ca531bc2bfa655a1a0acaac4f7a6ea4b2111cc43 Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 20:59:34 +0400 Subject: usb: dwc2: Add core parameter for service interval support Added core parameter for service interval based scheduling. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/core.h | 9 +++++++++ drivers/usb/dwc2/debugfs.c | 1 + drivers/usb/dwc2/gadget.c | 4 ++++ drivers/usb/dwc2/params.c | 4 ++++ 4 files changed, 18 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index cc9c93affa14..2678dc9d559b 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -416,6 +416,9 @@ enum dwc2_ep0_state { * back to DWC2_SPEED_PARAM_HIGH while device is gone. * 0 - No (default) * 1 - Yes + * @service_interval: Enable service interval based scheduling. + * 0 - No + * 1 - Yes * * The following parameters may be specified when starting the module. These * parameters define how the DWC_otg controller should be configured. A @@ -461,6 +464,7 @@ struct dwc2_core_params { bool lpm_clock_gating; bool besl; bool hird_threshold_en; + bool service_interval; u8 hird_threshold; bool activate_stm_fs_transceiver; bool ipg_isoc_en; @@ -605,6 +609,10 @@ struct dwc2_core_params { * FIFO sizing is enabled 16 to 32768 * Actual maximum value is autodetected and also * the default. + * @service_interval_mode: For enabling service interval based scheduling in the + * controller. + * 0 - Disable + * 1 - Enable */ struct dwc2_hw_params { unsigned op_mode:3; @@ -635,6 +643,7 @@ struct dwc2_hw_params { unsigned utmi_phy_data_width:2; unsigned lpm_mode:1; unsigned ipg_isoc_en:1; + unsigned service_interval_mode:1; u32 snpsid; u32 dev_ep_dirs; u32 g_tx_fifo_size[MAX_EPS_CHANNELS]; diff --git a/drivers/usb/dwc2/debugfs.c b/drivers/usb/dwc2/debugfs.c index 22d015b0424f..7f62f4cdc265 100644 --- a/drivers/usb/dwc2/debugfs.c +++ b/drivers/usb/dwc2/debugfs.c @@ -701,6 +701,7 @@ static int params_show(struct seq_file *seq, void *v) print_param(seq, p, besl); print_param(seq, p, hird_threshold_en); print_param(seq, p, hird_threshold); + print_param(seq, p, service_interval); print_param(seq, p, host_dma); print_param(seq, p, g_dma); print_param(seq, p, g_dma_desc); diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 79189db4bf17..12032f0488d8 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3323,6 +3323,10 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, dwc2_set_bit(hsotg, DIEPMSK, DIEPMSK_BNAININTRMSK); } + /* Enable Service Interval mode if supported */ + if (using_desc_dma(hsotg) && hsotg->params.service_interval) + dwc2_set_bit(hsotg, DCTL, DCTL_SERVICE_INTERVAL_SUPPORTED); + dwc2_writel(hsotg, 0, DAINTMSK); dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index bf7052e037d6..dd3c10d537e2 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -299,6 +299,7 @@ static void dwc2_set_default_params(struct dwc2_hsotg *hsotg) p->hird_threshold_en = true; p->hird_threshold = 4; p->ipg_isoc_en = false; + p->service_interval = false; p->max_packet_count = hw->max_packet_count; p->max_transfer_size = hw->max_transfer_size; p->ahbcfg = GAHBCFG_HBSTLEN_INCR << GAHBCFG_HBSTLEN_SHIFT; @@ -592,6 +593,7 @@ static void dwc2_check_params(struct dwc2_hsotg *hsotg) CHECK_BOOL(besl, (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a)); CHECK_BOOL(hird_threshold_en, hsotg->params.lpm); CHECK_RANGE(hird_threshold, 0, hsotg->params.besl ? 12 : 7, 0); + CHECK_BOOL(service_interval, hw->service_interval_mode); CHECK_RANGE(max_packet_count, 15, hw->max_packet_count, hw->max_packet_count); @@ -780,6 +782,8 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT; hw->acg_enable = !!(hwcfg4 & GHWCFG4_ACG_SUPPORTED); hw->ipg_isoc_en = !!(hwcfg4 & GHWCFG4_IPG_ISOC_SUPPORTED); + hw->service_interval_mode = !!(hwcfg4 & + GHWCFG4_SERVICE_INTERVAL_SUPPORTED); /* fifo sizes */ hw->rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> -- cgit v1.2.3-58-ga151 From 9d630b9cde28dc261cdfc608ed68ef5487404422 Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 21:00:03 +0400 Subject: usb: dwc2: Add dwc2_gadget_dec_frame_num_by_one() function Added dwc2_gadget_dec_frame_num_by_one() function in gadget.c. This function will be used to calculate descriptor frame number field value. For service interval mode frame number in descriptor should point to last (u)frame in the interval. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/gadget.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 12032f0488d8..71f097d89001 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -122,6 +122,24 @@ static inline void dwc2_gadget_incr_frame_num(struct dwc2_hsotg_ep *hs_ep) } } +/** + * dwc2_gadget_dec_frame_num_by_one - Decrements the targeted frame number + * by one. + * @hs_ep: The endpoint. + * + * This function used in service interval based scheduling flow to calculate + * descriptor frame number filed value. For service interval mode frame + * number in descriptor should point to last (u)frame in the interval. + * + */ +static inline void dwc2_gadget_dec_frame_num_by_one(struct dwc2_hsotg_ep *hs_ep) +{ + if (hs_ep->target_frame) + hs_ep->target_frame -= 1; + else + hs_ep->target_frame = DSTS_SOFFN_LIMIT; +} + /** * dwc2_hsotg_en_gsint - enable one or more of the general interrupt * @hsotg: The device state -- cgit v1.2.3-58-ga151 From 48dac4e4a5eed3fa478db2c59945b6283281566d Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 21:00:33 +0400 Subject: usb: dwc2: Update target (u)frame calculation In service interval based scheduling target (u)frame must be set as a last frame in this the service interval. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/gadget.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 71f097d89001..9de16453a890 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -2830,6 +2830,23 @@ static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep) if (using_desc_dma(hsotg)) { hs_ep->target_frame = hsotg->frame_number; dwc2_gadget_incr_frame_num(hs_ep); + + /* In service interval mode target_frame must + * be set to last (u)frame of the service interval. + */ + if (hsotg->params.service_interval) { + /* Set target_frame to the first (u)frame of + * the service interval + */ + hs_ep->target_frame &= ~hs_ep->interval + 1; + + /* Set target_frame to the last (u)frame of + * the service interval + */ + dwc2_gadget_incr_frame_num(hs_ep); + dwc2_gadget_dec_frame_num_by_one(hs_ep); + } + dwc2_gadget_start_isoc_ddma(hs_ep); return; } -- cgit v1.2.3-58-ga151 From 392af0232640abf7acd12754515f8363c4c0df67 Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 21:01:01 +0400 Subject: usb: dwc2: Add definitions for new registers New registers were added to dwc otg core. GREFCLK - This register used to control ref_clk parameters. GINTSTS2 - New WKUP_ALERT interrupt was added. GINTMSK2 - Mask register for GINTSTS2. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/hw.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index 524629428439..2b1ea441b7d4 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -405,6 +405,19 @@ #define ADPCTL_PRB_DSCHRG_MASK (0x3 << 0) #define ADPCTL_PRB_DSCHRG_SHIFT 0 +#define GREFCLK HSOTG_REG(0x0064) +#define GREFCLK_REFCLKPER_MASK (0x1ffff << 15) +#define GREFCLK_REFCLKPER_SHIFT 15 +#define GREFCLK_REF_CLK_MODE BIT(14) +#define GREFCLK_SOF_CNT_WKUP_ALERT_MASK (0x3ff) +#define GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT 0 + +#define GINTMSK2 HSOTG_REG(0x0068) +#define GINTMSK2_WKUP_ALERT_INT_MSK BIT(0) + +#define GINTSTS2 HSOTG_REG(0x006c) +#define GINTSTS2_WKUP_ALERT_INT BIT(0) + #define HPTXFSIZ HSOTG_REG(0x100) /* Use FIFOSIZE_* constants to access this register */ -- cgit v1.2.3-58-ga151 From f3a61e4e033e808e7ac1239b151ec46f833fff4a Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 21:01:31 +0400 Subject: usb: dwc2: gadget: Add parameters for GREFCLK register Added ref_clk_per and sof_cnt_wkup_alert parameters in dwc2_core_params struct and set default values. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/core.h | 18 ++++++++++++++++++ drivers/usb/dwc2/params.c | 2 ++ 2 files changed, 20 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 2678dc9d559b..655f5274e801 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -393,6 +393,20 @@ enum dwc2_ep0_state { * 0 - No * 1 - Yes * @hird_threshold: Value of BESL or HIRD Threshold. + * @ref_clk_per: Indicates in terms of pico seconds the period + * of ref_clk. + * 62500 - 16MHz + * 58823 - 17MHz + * 52083 - 19.2MHz + * 50000 - 20MHz + * 41666 - 24MHz + * 33333 - 30MHz (default) + * 25000 - 40MHz + * @sof_cnt_wkup_alert: Indicates in term of number of SOF's after which + * the controller should generate an interrupt if the + * device had been in L1 state until that period. + * This is used by SW to initiate Remote WakeUp in the + * controller so as to sync to the uF number from the host. * @activate_stm_fs_transceiver: Activate internal transceiver using GGPIO * register. * 0 - Deactivate the transceiver (default) @@ -472,6 +486,10 @@ struct dwc2_core_params { u32 max_transfer_size; u32 ahbcfg; + /* GREFCLK parameters */ + u32 ref_clk_per; + u16 sof_cnt_wkup_alert; + /* Host parameters */ bool host_dma; bool dma_desc_enable; diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index dd3c10d537e2..d150984406ee 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -303,6 +303,8 @@ static void dwc2_set_default_params(struct dwc2_hsotg *hsotg) p->max_packet_count = hw->max_packet_count; p->max_transfer_size = hw->max_transfer_size; p->ahbcfg = GAHBCFG_HBSTLEN_INCR << GAHBCFG_HBSTLEN_SHIFT; + p->ref_clk_per = 33333; + p->sof_cnt_wkup_alert = 100; if ((hsotg->dr_mode == USB_DR_MODE_HOST) || (hsotg->dr_mode == USB_DR_MODE_OTG)) { -- cgit v1.2.3-58-ga151 From 15d9dbf8cbd4fc777a7fc92209903dbb47d0783e Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 21:01:59 +0400 Subject: usb: dwc2: gadget: Program GREFCLK register Added dwc2_gadget_program_ref_clk function to program GREFCLK register in device mode. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/core.h | 2 ++ drivers/usb/dwc2/gadget.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 655f5274e801..30bab8463c96 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -1381,6 +1381,7 @@ int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg); int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg); int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg); void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg); +void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg); #else static inline int dwc2_hsotg_remove(struct dwc2_hsotg *dwc2) { return 0; } @@ -1415,6 +1416,7 @@ static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) static inline int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg) { return 0; } static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) {} #endif #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 9de16453a890..e8dd6897e2c3 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3418,6 +3418,10 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg, /* configure the core to support LPM */ dwc2_gadget_init_lpm(hsotg); + /* program GREFCLK register if needed */ + if (using_desc_dma(hsotg) && hsotg->params.service_interval) + dwc2_gadget_program_ref_clk(hsotg); + /* must be at-least 3ms to allow bus to see disconnect */ mdelay(3); @@ -5001,6 +5005,25 @@ void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "GLPMCFG=0x%08x\n", dwc2_readl(hsotg, GLPMCFG)); } +/** + * dwc2_gadget_program_ref_clk - Program GREFCLK register in device mode + * + * @hsotg: Programming view of DWC_otg controller + * + */ +void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) +{ + u32 val = 0; + + val |= GREFCLK_REF_CLK_MODE; + val |= hsotg->params.ref_clk_per << GREFCLK_REFCLKPER_SHIFT; + val |= hsotg->params.sof_cnt_wkup_alert << + GREFCLK_SOF_CNT_WKUP_ALERT_SHIFT; + + dwc2_writel(hsotg, val, GREFCLK); + dev_dbg(hsotg->dev, "GREFCLK=0x%08x\n", dwc2_readl(hsotg, GREFCLK)); +} + /** * dwc2_gadget_enter_hibernation() - Put controller in Hibernation. * -- cgit v1.2.3-58-ga151 From 4abe453750db8ada8b0a56c45c89ab18920e9a80 Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 21:02:28 +0400 Subject: usb: dwc2: gadget: enable WKUP_ALERT interrupt WKUP_ALERT interrupt should be unmask when lpm mode is enabled. This interrupt is asserted when the device is in L1 for the duration mentioned in GREFCLK.SOF_CNN_WKUP_ALERT. This is used to alert SW to initiate Remote wake up so that the device resumes in time in order not to lose sync with the host frame number. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/gadget.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index e8dd6897e2c3..24bd9fdabc67 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -5003,6 +5003,10 @@ void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) val |= hsotg->params.besl ? GLPMCFG_ENBESL : 0; dwc2_writel(hsotg, val, GLPMCFG); dev_dbg(hsotg->dev, "GLPMCFG=0x%08x\n", dwc2_readl(hsotg, GLPMCFG)); + + /* Unmask WKUP_ALERT Interrupt */ + if (hsotg->params.service_interval) + dwc2_set_bit(hsotg, GINTMSK2, GINTMSK2_WKUP_ALERT_INT_MSK); } /** -- cgit v1.2.3-58-ga151 From 187c5298a12292eab55e3eb09e70e2b145646bcc Mon Sep 17 00:00:00 2001 From: Grigor Tovmasyan <Grigor.Tovmasyan@synopsys.com> Date: Wed, 29 Aug 2018 21:02:57 +0400 Subject: usb: dwc2: gadget: Add handler for WkupAlert interrupt Added interrupt handler for WkupAlert interrupt. This interrupt should initiate Remote Wake up. Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Grigor Tovmasyan <tovmasya@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/gadget.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 24bd9fdabc67..2d6d2c8244de 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -245,6 +245,27 @@ int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) return tx_addr_max - addr; } +/** + * dwc2_gadget_wkup_alert_handler - Handler for WKUP_ALERT interrupt + * + * @hsotg: Programming view of the DWC_otg controller + * + */ +static void dwc2_gadget_wkup_alert_handler(struct dwc2_hsotg *hsotg) +{ + u32 gintsts2; + u32 gintmsk2; + + gintsts2 = dwc2_readl(hsotg, GINTSTS2); + gintmsk2 = dwc2_readl(hsotg, GINTMSK2); + + if (gintsts2 & GINTSTS2_WKUP_ALERT_INT) { + dev_dbg(hsotg->dev, "%s: Wkup_Alert_Int\n", __func__); + dwc2_clear_bit(hsotg, GINTSTS2, GINTSTS2_WKUP_ALERT_INT); + dwc2_set_bit(hsotg, DCFG, DCTL_RMTWKUPSIG); + } +} + /** * dwc2_hsotg_tx_fifo_average_depth - returns average depth of device mode * TX FIFOs @@ -3730,6 +3751,10 @@ irq_retry: if (gintsts & IRQ_RETRY_MASK && --retry_count > 0) goto irq_retry; + /* Check WKUP_ALERT interrupt*/ + if (hsotg->params.service_interval) + dwc2_gadget_wkup_alert_handler(hsotg); + spin_unlock(&hsotg->lock); return IRQ_HANDLED; -- cgit v1.2.3-58-ga151 From afc92514a34c7414b28047b1205a6b709103c699 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Tue, 2 Oct 2018 20:57:44 +0900 Subject: usb: gadget: udc: renesas_usb3: Fix b-device mode for "workaround" If the "workaround_for_vbus" is true, the driver will not call usb_disconnect(). So, since the controller keeps some registers' value, the driver doesn't re-enumarate suitable speed after the b-device mode is disabled. To fix the issue, this patch adds usb_disconnect() calling in renesas_usb3_b_device_write() if workaround_for_vbus is true. Fixes: 43ba968b00ea ("usb: gadget: udc: renesas_usb3: add debugfs to set the b-device mode") Cc: <stable@vger.kernel.org> # v4.14+ Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/renesas_usb3.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index e1656f361e08..67d8a501d994 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -2437,6 +2437,9 @@ static ssize_t renesas_usb3_b_device_write(struct file *file, else usb3->forced_b_device = false; + if (usb3->workaround_for_vbus) + usb3_disconnect(usb3); + /* Let this driver call usb3_connect() anyway */ usb3_check_id(usb3); -- cgit v1.2.3-58-ga151 From 1b6af2f58c2b1522e0804b150ca95e50a9e80ea7 Mon Sep 17 00:00:00 2001 From: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Date: Fri, 21 Sep 2018 16:04:11 +0100 Subject: usb: typec: tcpm: Fix APDO PPS order checking to be based on voltage Current code mistakenly checks against max current to determine order but this should be max voltage. This commit fixes the issue so order is correctly determined, thus avoiding failure based on a higher voltage PPS APDO having a lower maximum current output, which is actually valid. Fixes: 2eadc33f40d4 ("typec: tcpm: Add core support for sink side PPS") Cc: <stable@vger.kernel.org> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/tcpm/tcpm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 4f1f4215f3d6..c11b3befa87f 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1430,8 +1430,8 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, 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])) + if (pdo_pps_apdo_max_voltage(pdo[i]) < + pdo_pps_apdo_max_voltage(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]) && -- cgit v1.2.3-58-ga151 From bd0e6c9614b95352eb31d0207df16dc156c527fa Mon Sep 17 00:00:00 2001 From: Zeng Tao <prime.zeng@hisilicon.com> Date: Fri, 28 Sep 2018 19:27:52 +0800 Subject: usb: hub: try old enumeration scheme first for high speed devices The new scheme is required just to support legacy low and full-speed devices. For high speed devices, it will slower the enumeration speed. So in this patch we try the "old" enumeration scheme first for high speed devices, and this is what Windows does since Windows 8. Signed-off-by: Zeng Tao <prime.zeng@hisilicon.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Reviewed-by: Roger Quadros <rogerq@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- Documentation/admin-guide/kernel-parameters.txt | 3 ++- drivers/usb/core/hub.c | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 92eb1f42240d..151c527571e5 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4610,7 +4610,8 @@ usbcore.old_scheme_first= [USB] Start with the old device initialization - scheme (default 0 = off). + scheme, applies only to low and full-speed devices + (default 0 = off). usbcore.usbfs_memory_mb= [USB] Memory limit (in MB) for buffers allocated by diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 7801bb30bdba..bf76a3dd4359 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2661,11 +2661,13 @@ static bool use_new_scheme(struct usb_device *udev, int retry, { int old_scheme_first_port = port_dev->quirks & USB_PORT_QUIRK_OLD_SCHEME; + int quick_enumeration = (udev->speed == USB_SPEED_HIGH); if (udev->speed >= USB_SPEED_SUPER) return false; - return USE_NEW_SCHEME(retry, old_scheme_first_port || old_scheme_first); + return USE_NEW_SCHEME(retry, old_scheme_first_port || old_scheme_first + || quick_enumeration); } /* Is a USB 3.0 port in the Inactive or Compliance Mode state? -- cgit v1.2.3-58-ga151 From 355c74e55e9992126ec5e568a3edb8e280fe040d Mon Sep 17 00:00:00 2001 From: Bjørn Mork <bjorn@mork.no> Date: Fri, 28 Sep 2018 15:40:31 +0200 Subject: usb: export firmware port location in sysfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The platform firmware "location" data is used to find port peer relationships. But firmware is an unreliable source, and there are real world examples of errors leading to missing or wrong peer relationships. Debugging this is currently hard. Exporting the location attribute makes it easier to spot mismatches between the firmware data and the real world. Signed-off-by: Bjørn Mork <bjorn@mork.no> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- Documentation/ABI/testing/sysfs-bus-usb | 10 ++++++++++ drivers/usb/core/port.c | 10 ++++++++++ 2 files changed, 20 insertions(+) (limited to 'drivers/usb') diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index c4a70f532ec3..559baa5c418c 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -189,6 +189,16 @@ Description: The file will read "hotplug", "wired" and "not used" if the information is available, and "unknown" otherwise. +What: /sys/bus/usb/devices/.../(hub interface)/portX/location +Date: October 2018 +Contact: Bjørn Mork <bjorn@mork.no> +Description: + Some platforms provide usb port physical location through + firmware. This is used by the kernel to pair up logical ports + mapping to the same physical connector. The attribute exposes the + raw location value as a hex integer. + + What: /sys/bus/usb/devices/.../(hub interface)/portX/quirks Date: May 2018 Contact: Nicolas Boichat <drinkcat@chromium.org> diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 4a2143195395..1a06a4b5fbb1 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -16,6 +16,15 @@ static int usb_port_block_power_off; static const struct attribute_group *port_dev_group[]; +static ssize_t location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_port *port_dev = to_usb_port(dev); + + return sprintf(buf, "0x%08x\n", port_dev->location); +} +static DEVICE_ATTR_RO(location); + static ssize_t connect_type_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -140,6 +149,7 @@ static DEVICE_ATTR_RW(usb3_lpm_permit); static struct attribute *port_dev_attrs[] = { &dev_attr_connect_type.attr, + &dev_attr_location.attr, &dev_attr_quirks.attr, &dev_attr_over_current_count.attr, NULL, -- cgit v1.2.3-58-ga151 From 8e4657c60c2bbb9918b33e226458cce3e5fa1d8c Mon Sep 17 00:00:00 2001 From: YueHaibing <yuehaibing@huawei.com> Date: Sat, 29 Sep 2018 09:53:16 +0000 Subject: usb: typec: remove set but not used variables 'snk_ma, min_mv' Fixes gcc '-Wunused-but-set-variable' warning: drivers/usb/typec/tcpm/tcpm.c: In function 'tcpm_pd_select_pps_apdo': drivers/usb/typec/tcpm/tcpm.c:2212:39: warning: variable 'snk_ma' set but not used [-Wunused-but-set-variable] drivers/usb/typec/tcpm/tcpm.c: In function 'tcpm_pd_build_pps_request': drivers/usb/typec/tcpm/tcpm.c:2405:37: warning: variable 'min_mv' set but not used [-Wunused-but-set-variable] Signed-off-by: YueHaibing <yuehaibing@huawei.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/tcpm/tcpm.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index c11b3befa87f..b06eac8ad70d 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -2209,7 +2209,7 @@ 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; + unsigned int min_snk_mv, max_snk_mv; u32 pdo; unsigned int src_pdo = 0, snk_pdo = 0; @@ -2253,8 +2253,6 @@ static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port) 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, @@ -2402,7 +2400,7 @@ static int tcpm_pd_send_request(struct tcpm_port *port) 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; + unsigned int out_mv, op_ma, op_mw, max_mv, max_ma, flags; enum pd_pdo_type type; unsigned int src_pdo_index; u32 pdo; @@ -2420,7 +2418,6 @@ static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo) 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; -- cgit v1.2.3-58-ga151 From 3c168909002eac649fb8b803d6a9babe758ec7c2 Mon Sep 17 00:00:00 2001 From: Colin Ian King <colin.king@canonical.com> Date: Sat, 29 Sep 2018 12:43:13 +0100 Subject: usb: gadget: fix spelling mistakeis "[En]queing" -> "[En]queuing" Trivial fix to spelling mistakes in debug warning messages Signed-off-by: Colin Ian King <colin.king@canonical.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/gadget/udc/aspeed-vhub/epn.c | 2 +- drivers/usb/gadget/udc/udc-xilinx.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c index 5939eb1e97f2..4a28e3fbeb0b 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/epn.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c @@ -353,7 +353,7 @@ static int ast_vhub_epn_queue(struct usb_ep* u_ep, struct usb_request *u_req, /* Endpoint enabled ? */ if (!ep->epn.enabled || !u_ep->desc || !ep->dev || !ep->d_idx || !ep->dev->enabled || ep->dev->suspended) { - EPDBG(ep,"Enqueing request on wrong or disabled EP\n"); + EPDBG(ep, "Enqueuing request on wrong or disabled EP\n"); return -ESHUTDOWN; } diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 6407e433bc78..b1f4104d1283 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -1078,7 +1078,7 @@ static int xudc_ep_queue(struct usb_ep *_ep, struct usb_request *_req, unsigned long flags; if (!ep->desc) { - dev_dbg(udc->dev, "%s:queing request to disabled %s\n", + dev_dbg(udc->dev, "%s: queuing request to disabled %s\n", __func__, ep->name); return -ESHUTDOWN; } -- cgit v1.2.3-58-ga151 From e0658e3074231d68f6546be1c5916ba2f4dc1295 Mon Sep 17 00:00:00 2001 From: Johan Hovold <johan@kernel.org> Date: Sun, 30 Sep 2018 14:27:02 +0200 Subject: USB: serial: ftdi_sio: fix gpio name collisions Drop the gpio line names, which cause gpiolib to complain loudly whenever a second ftdi gpiochip is registered: gpio gpiochip5: Detected name collision for GPIO name 'CBUS0' gpio gpiochip5: Detected name collision for GPIO name 'CBUS1' gpio gpiochip5: Detected name collision for GPIO name 'CBUS2' gpio gpiochip5: Detected name collision for GPIO name 'CBUS3' and also prevents the legacy sysfs interface from being used (as the line names are used as device names whenever they are set): sysfs: cannot create duplicate filename '/class/gpio/CBUS0' Until non-unique names are supported by gpiolib (without warnings and stack dumps), let's leave the gpio lines unnamed. Signed-off-by: Johan Hovold <johan@kernel.org> --- drivers/usb/serial/ftdi_sio.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 6b727ada20cf..be50b2a200aa 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -1778,10 +1778,6 @@ static void remove_sysfs_attrs(struct usb_serial_port *port) #ifdef CONFIG_GPIOLIB -static const char * const ftdi_ftx_gpio_names[] = { - "CBUS0", "CBUS1", "CBUS2", "CBUS3" -}; - static int ftdi_set_bitmode(struct usb_serial_port *port, u8 mode) { struct ftdi_private *priv = usb_get_serial_port_data(port); @@ -2032,7 +2028,6 @@ static int ftx_gpioconf_init(struct usb_serial_port *port) /* FIXME: FT234XD alone has 1 GPIO, but how to recognize this IC? */ priv->gc.ngpio = 4; - priv->gc.names = ftdi_ftx_gpio_names; /* Determine which pins are configured for CBUS bitbanging */ priv->gpio_altfunc = 0xff; -- cgit v1.2.3-58-ga151 From ff32d97e39e7053fdc1d316bd2e2eff70b77fdd2 Mon Sep 17 00:00:00 2001 From: Johan Hovold <johan@kernel.org> Date: Sun, 30 Sep 2018 14:27:03 +0200 Subject: USB: serial: ftdi_sio: add support for FT232R CBUS gpios Enable support for cbus gpios on FT232R. The cbus configuration is stored in one word in the EEPROM at offset 0x0a (byte-offset 0x14) with the mux config for CBUS0, CBUS1, CBUS2 and CBUS3 in bits 0..3, 4..7, 8..11 and 12..15, respectively. Tested using FT232RL by configuring one cbus pin at a time. Signed-off-by: Johan Hovold <johan@kernel.org> --- drivers/usb/serial/ftdi_sio.c | 40 ++++++++++++++++++++++++++++++++++++++-- drivers/usb/serial/ftdi_sio.h | 3 ++- 2 files changed, 40 insertions(+), 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index be50b2a200aa..f1eb20acb3bb 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -2007,7 +2007,40 @@ static int ftdi_read_eeprom(struct usb_serial *serial, void *dst, u16 addr, return 0; } -static int ftx_gpioconf_init(struct usb_serial_port *port) +static int ftdi_gpio_init_ft232r(struct usb_serial_port *port) +{ + struct ftdi_private *priv = usb_get_serial_port_data(port); + u16 cbus_config; + u8 *buf; + int ret; + int i; + + buf = kmalloc(2, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = ftdi_read_eeprom(port->serial, buf, 0x14, 2); + if (ret < 0) + goto out_free; + + cbus_config = le16_to_cpup((__le16 *)buf); + dev_dbg(&port->dev, "cbus_config = 0x%04x\n", cbus_config); + + priv->gc.ngpio = 4; + + priv->gpio_altfunc = 0xff; + for (i = 0; i < priv->gc.ngpio; ++i) { + if ((cbus_config & 0xf) == FTDI_FT232R_CBUS_MUX_GPIO) + priv->gpio_altfunc &= ~BIT(i); + cbus_config >>= 4; + } +out_free: + kfree(buf); + + return ret; +} + +static int ftdi_gpio_init_ftx(struct usb_serial_port *port) { struct ftdi_private *priv = usb_get_serial_port_data(port); struct usb_serial *serial = port->serial; @@ -2049,8 +2082,11 @@ static int ftdi_gpio_init(struct usb_serial_port *port) int result; switch (priv->chip_type) { + case FT232RL: + result = ftdi_gpio_init_ft232r(port); + break; case FTX: - result = ftx_gpioconf_init(port); + result = ftdi_gpio_init_ftx(port); break; default: return 0; diff --git a/drivers/usb/serial/ftdi_sio.h b/drivers/usb/serial/ftdi_sio.h index 6cfe682f8348..a79a1325b4d9 100644 --- a/drivers/usb/serial/ftdi_sio.h +++ b/drivers/usb/serial/ftdi_sio.h @@ -457,7 +457,8 @@ enum ftdi_sio_baudrate { #define FTDI_SIO_READ_EEPROM_REQUEST_TYPE 0xc0 #define FTDI_SIO_READ_EEPROM_REQUEST FTDI_SIO_READ_EEPROM -#define FTDI_FTX_CBUS_MUX_GPIO 8 +#define FTDI_FTX_CBUS_MUX_GPIO 0x8 +#define FTDI_FT232R_CBUS_MUX_GPIO 0xa /* Descriptors returned by the device -- cgit v1.2.3-58-ga151 From 56445eef55cb5904096fed7a73cf87b755dfffc7 Mon Sep 17 00:00:00 2001 From: Johan Hovold <johan@kernel.org> Date: Sun, 30 Sep 2018 18:03:11 +0200 Subject: USB: serial: cypress_m8: fix interrupt-out transfer length Fix interrupt-out transfer length which was being set to the transfer-buffer length rather than the size of the outgoing packet. Note that no slab data was leaked as the whole transfer buffer is always cleared before each transfer. Fixes: 9aa8dae7b1fa ("cypress_m8: use usb_fill_int_urb where appropriate") Signed-off-by: Johan Hovold <johan@kernel.org> --- drivers/usb/serial/cypress_m8.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index 31c6091be46a..5aaab8f4dd8f 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -769,7 +769,7 @@ send: usb_fill_int_urb(port->interrupt_out_urb, port->serial->dev, usb_sndintpipe(port->serial->dev, port->interrupt_out_endpointAddress), - port->interrupt_out_buffer, port->interrupt_out_size, + port->interrupt_out_buffer, actual_size, cypress_write_int_callback, port, priv->write_urb_interval); result = usb_submit_urb(port->interrupt_out_urb, GFP_ATOMIC); if (result) { -- cgit v1.2.3-58-ga151 From 17c42e34997ae172c794f84fefe47f00bec13f9a Mon Sep 17 00:00:00 2001 From: YueHaibing <yuehaibing@huawei.com> Date: Thu, 4 Oct 2018 07:09:53 +0000 Subject: USB: serial: cypress_m8: remove set but not used variable 'iflag' Fixes gcc '-Wunused-but-set-variable' warning: drivers/usb/serial/cypress_m8.c: In function 'cypress_set_termios': drivers/usb/serial/cypress_m8.c:866:18: warning: variable 'iflag' set but not used [-Wunused-but-set-variable] Signed-off-by: YueHaibing <yuehaibing@huawei.com> Signed-off-by: Johan Hovold <johan@kernel.org> --- drivers/usb/serial/cypress_m8.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/serial/cypress_m8.c b/drivers/usb/serial/cypress_m8.c index 5aaab8f4dd8f..ed51bc48eea6 100644 --- a/drivers/usb/serial/cypress_m8.c +++ b/drivers/usb/serial/cypress_m8.c @@ -863,7 +863,7 @@ static void cypress_set_termios(struct tty_struct *tty, struct cypress_private *priv = usb_get_serial_port_data(port); struct device *dev = &port->dev; int data_bits, stop_bits, parity_type, parity_enable; - unsigned cflag, iflag; + unsigned int cflag; unsigned long flags; __u8 oldlines; int linechange = 0; @@ -899,7 +899,6 @@ static void cypress_set_termios(struct tty_struct *tty, tty->termios.c_cflag &= ~(CMSPAR|CRTSCTS); cflag = tty->termios.c_cflag; - iflag = tty->termios.c_iflag; /* check if there are new settings */ if (old_termios) { -- cgit v1.2.3-58-ga151 From 30025efa8b5e75f545e38a592158c34b3169423b Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Date: Tue, 2 Oct 2018 20:58:27 +0900 Subject: usb: gadget: udc: renesas_usb3: add support for r8a77990 Since r8a77990 (R-Car E3) doesn't have VBUS detect pin and number of ramif is 4, this patch adds a new renesas_usb3_priv variable for the SoC. Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/renesas_usb3.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index 67d8a501d994..cdffbd1e0316 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -2603,6 +2603,13 @@ static const struct renesas_usb3_priv renesas_usb3_priv_gen3 = { .ramsize_per_pipe = SZ_4K, }; +static const struct renesas_usb3_priv renesas_usb3_priv_r8a77990 = { + .ramsize_per_ramif = SZ_16K, + .num_ramif = 4, + .ramsize_per_pipe = SZ_4K, + .workaround_for_vbus = true, +}; + static const struct of_device_id usb3_of_match[] = { { .compatible = "renesas,r8a7795-usb3-peri", @@ -2621,6 +2628,10 @@ static const struct soc_device_attribute renesas_usb3_quirks_match[] = { .soc_id = "r8a7795", .revision = "ES1.*", .data = &renesas_usb3_priv_r8a7795_es1, }, + { + .soc_id = "r8a77990", + .data = &renesas_usb3_priv_r8a77990, + }, { /* sentinel */ }, }; -- cgit v1.2.3-58-ga151 From c216765d3a1defda5e7e2dabd878f99f0cd2ebf2 Mon Sep 17 00:00:00 2001 From: SolidHal <hal@halemmerich.com> Date: Tue, 2 Oct 2018 20:58:16 -0500 Subject: usb: dwc2: disable power_down on rockchip devices The bug would let the usb controller enter partial power down, which was formally known as hibernate, upon boot if nothing was plugged in to the port. Partial power down couldn't be exited properly, so any usb devices plugged in after boot would not be usable. Before the name change, params.hibernation was false by default, so _dwc2_hcd_suspend() would skip entering hibernation. With the rename, _dwc2_hcd_suspend() was changed to use params.power_down to decide whether or not to enter partial power down. Since params.power_down is non-zero by default, it needs to be set to 0 for rockchip devices to restore functionality. This bug was reported in the linux-usb thread: REGRESSION: usb: dwc2: USB device not seen after boot The commit that caused this regression is: 6d23ee9caa6790aea047f9aca7f3c03cb8d96eb6 Signed-off-by: SolidHal <hal@halemmerich.com> Acked-by: Minas Harutyunyan <hminas@synopsys.com> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/dwc2/params.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb') diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index d150984406ee..7c1b6938f212 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -81,6 +81,7 @@ static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg) p->host_perio_tx_fifo_size = 256; p->ahbcfg = GAHBCFG_HBSTLEN_INCR16 << GAHBCFG_HBSTLEN_SHIFT; + p->power_down = 0; } static void dwc2_set_ltq_params(struct dwc2_hsotg *hsotg) -- cgit v1.2.3-58-ga151 From 3b766f45355775fc5c404b7ff88f3fd3e9d77f86 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Tue, 2 Oct 2018 10:18:40 -0400 Subject: USB: net2280: Remove ->disconnect() callback from net2280_pullup() The net2280 UDC driver invokes the gadget driver's ->disconnect() callback routine when the net2280_pullup() routine turns off the D+ pullup. This is now unnecessary, because the gadget core performs the callback on our behalf. This patch removes the unneeded callback. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> --- drivers/usb/gadget/udc/net2280.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index b02ab2a8d927..e7dae5379e04 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -1550,9 +1550,6 @@ static int net2280_pullup(struct usb_gadget *_gadget, int is_on) spin_unlock_irqrestore(&dev->lock, flags); - if (!is_on && dev->driver) - dev->driver->disconnect(&dev->gadget); - return 0; } -- cgit v1.2.3-58-ga151 From bf7f547ecdd707e7b4fcbc467b4f9ddb29915391 Mon Sep 17 00:00:00 2001 From: Colin Ian King <colin.king@canonical.com> Date: Wed, 3 Oct 2018 10:59:57 +0100 Subject: usb: core: fix memory leak on port_dev_path allocation Currently the allocation of port_dev_path from the call to kobject_get_path is not being kfree'd, causing a memory leak. Fix this by kfree'ing this at the end of the function. Add an extra error exit path to fix one of the early leaks when envp[0] fails to be allocated. Detected by CoverityScan, CID#1473771 ("Resource Leak") Fixes: 201af55da8a3 ("usb: core: added uevent for over-current") Signed-off-by: Colin Ian King <colin.king@canonical.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/core/hub.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index bf76a3dd4359..c6077d582d29 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -5170,7 +5170,7 @@ static void port_over_current_notify(struct usb_port *port_dev) envp[0] = kasprintf(GFP_KERNEL, "OVER_CURRENT_PORT=%s", port_dev_path); if (!envp[0]) - return; + goto exit_path; envp[1] = kasprintf(GFP_KERNEL, "OVER_CURRENT_COUNT=%u", port_dev->over_current_count); @@ -5182,6 +5182,8 @@ static void port_over_current_notify(struct usb_port *port_dev) kfree(envp[1]); exit: kfree(envp[0]); +exit_path: + kfree(port_dev_path); } static void port_event(struct usb_hub *hub, int port1) -- cgit v1.2.3-58-ga151 From c36e96bd259d5ec1e73c2cbfdc3ef935e6b0f830 Mon Sep 17 00:00:00 2001 From: YueHaibing <yuehaibing@huawei.com> Date: Thu, 4 Oct 2018 03:06:12 +0000 Subject: USB: core: remove set but not used variable 'udev' Fixes gcc '-Wunused-but-set-variable' warning: drivers/usb/core/driver.c: In function 'usb_driver_claim_interface': drivers/usb/core/driver.c:513:21: warning: variable 'udev' set but not used [-Wunused-but-set-variable] Since commit c183813fcee44a24 ("USB: remove LPM management from usb_driver_claim_interface()"), 'udev' is not used. Signed-off-by: YueHaibing <yuehaibing@huawei.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/core/driver.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index a1f225f077cd..53564386ed57 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -510,7 +510,6 @@ int usb_driver_claim_interface(struct usb_driver *driver, struct usb_interface *iface, void *priv) { struct device *dev; - struct usb_device *udev; int retval = 0; if (!iface) @@ -524,8 +523,6 @@ int usb_driver_claim_interface(struct usb_driver *driver, if (!iface->authorized) return -ENODEV; - udev = interface_to_usbdev(iface); - dev->driver = &driver->drvwrap.driver; usb_set_intfdata(iface, priv); iface->needs_binding = 0; -- cgit v1.2.3-58-ga151 From 554fab6dbf20ee7298ed2d4e8398b85e6058abb7 Mon Sep 17 00:00:00 2001 From: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Date: Mon, 8 Oct 2018 13:53:59 +0100 Subject: usb: typec: tcpm: Report back negotiated PPS voltage and current Currently when requesting a specific voltage or current through the psy interface, for PPS, when reading back from that interface the values will always be the same as previously given, if the request was successful. However PPS only allows for 20mV voltage steps and 50mA current steps, and the psy class expects microvolt and micro amp requests, so inbetween values can be provided through this interface. Really when reading back the true values negotiated should be given, and not the ones originally asked for. To report the actual values negotiated with the Source, the values stored are now rounded down to the relevant step units prior to building the PPS request, so that those values are later correctly reported through the psy interface. In addition this improves the adjustments made to meet the operating power requirements of the platform, which previously could have been slightly out due to not using valid PPS units of voltage and current. Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/tcpm/tcpm.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index b06eac8ad70d..dbbd71f754d0 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -4113,6 +4113,9 @@ static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) goto port_unlock; } + /* Round down operating current to align with PPS valid steps */ + op_curr = op_curr - (op_curr % RDO_PROG_CURR_MA_STEP); + reinit_completion(&port->pps_complete); port->pps_data.op_curr = op_curr; port->pps_status = 0; @@ -4166,6 +4169,9 @@ static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) goto port_unlock; } + /* Round down output voltage to align with PPS valid steps */ + out_volt = out_volt - (out_volt % RDO_PROG_VOLT_MV_STEP); + reinit_completion(&port->pps_complete); port->pps_data.out_volt = out_volt; port->pps_status = 0; -- cgit v1.2.3-58-ga151 From f65861c645ceb59f7325f696f0e8fa195dde575d Mon Sep 17 00:00:00 2001 From: Stephen Boyd <swboyd@chromium.org> Date: Sun, 7 Oct 2018 16:46:12 -0700 Subject: usb: typec: Fix copy/paste on typec_set_vconn_role() kerneldoc This must have been copy pasted from the function above. Fix it. Signed-off-by: Stephen Boyd <swboyd@chromium.org> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/typec/class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 00141e05bc72..5db0593ca0bd 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -1322,7 +1322,7 @@ void typec_set_pwr_role(struct typec_port *port, enum typec_role role) EXPORT_SYMBOL_GPL(typec_set_pwr_role); /** - * typec_set_pwr_role - Report VCONN source change + * typec_set_vconn_role - Report VCONN source change * @port: The USB Type-C Port which VCONN role changed * @role: Source when @port is sourcing VCONN, or Sink when it's not * -- cgit v1.2.3-58-ga151 From 24f5975f3aff03cb24026c768fdad0772304009b Mon Sep 17 00:00:00 2001 From: YueHaibing <yuehaibing@huawei.com> Date: Sat, 6 Oct 2018 02:23:01 +0000 Subject: usb/early: remove set but not used variable 'remain_length' Fixes gcc '-Wunused-but-set-variable' warning: drivers/usb/early/xhci-dbc.c: In function 'xdbc_handle_tx_event': drivers/usb/early/xhci-dbc.c:720:9: warning: variable 'remain_length' set but not used [-Wunused-but-set-variable] It never be used since introduction in commit aeb9dd1de98c ("usb/early: Add driver for xhci debug capability") Signed-off-by: YueHaibing <yuehaibing@huawei.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/early/xhci-dbc.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c index e15e896f356c..165653a5e45d 100644 --- a/drivers/usb/early/xhci-dbc.c +++ b/drivers/usb/early/xhci-dbc.c @@ -717,17 +717,14 @@ static void xdbc_handle_port_status(struct xdbc_trb *evt_trb) static void xdbc_handle_tx_event(struct xdbc_trb *evt_trb) { - size_t remain_length; u32 comp_code; int ep_id; comp_code = GET_COMP_CODE(le32_to_cpu(evt_trb->field[2])); - remain_length = EVENT_TRB_LEN(le32_to_cpu(evt_trb->field[2])); ep_id = TRB_TO_EP_ID(le32_to_cpu(evt_trb->field[3])); switch (comp_code) { case COMP_SUCCESS: - remain_length = 0; case COMP_SHORT_PACKET: break; case COMP_TRB_ERROR: -- cgit v1.2.3-58-ga151 From 325b9313ec3be56c8e2fe03f977fee19cec75820 Mon Sep 17 00:00:00 2001 From: "Tudor.Ambarus@microchip.com" <Tudor.Ambarus@microchip.com> Date: Mon, 15 Oct 2018 09:00:54 +0000 Subject: usb: host: ohci-at91: fix request of irq for optional gpio atmel,oc-gpio is optional. Request its irq only when atmel,oc is set in device tree. devm_gpiod_get_index_optional returns NULL if -ENOENT. Check its return value for NULL before error, because it is more probable that atmel,oc is not set. This fixes the following errors on boards where atmel,oc is not set in device tree: [ 0.960000] at91_ohci 500000.ohci: failed to request gpio "overcurrent" IRQ [ 0.960000] at91_ohci 500000.ohci: failed to request gpio "overcurrent" IRQ [ 0.970000] at91_ohci 500000.ohci: failed to request gpio "overcurrent" IRQ Signed-off-by: Tudor Ambarus <tudor.ambarus@microchip.com> Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/ohci-at91.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index e98673954020..ec6739ef3129 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -551,6 +551,8 @@ static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) pdata->overcurrent_pin[i] = devm_gpiod_get_index_optional(&pdev->dev, "atmel,oc", i, GPIOD_IN); + if (!pdata->overcurrent_pin[i]) + continue; if (IS_ERR(pdata->overcurrent_pin[i])) { err = PTR_ERR(pdata->overcurrent_pin[i]); dev_err(&pdev->dev, "unable to claim gpio \"overcurrent\": %d\n", err); -- cgit v1.2.3-58-ga151 From 090158555ff8d194a98616034100b16697dd80d0 Mon Sep 17 00:00:00 2001 From: Mattias Jacobsson <2pi@mok.nu> Date: Tue, 16 Oct 2018 14:20:08 +0200 Subject: USB: misc: appledisplay: fix backlight update_status return code Upon success the update_status handler returns a positive number corresponding to the number of bytes transferred by usb_control_msg. However the return code of the update_status handler should indicate if an error occurred(negative) or how many bytes of the user's input to sysfs that was consumed. Return code zero indicates all bytes were consumed. The bug can for example result in the update_status handler being called twice, the second time with only the "unconsumed" part of the user's input to sysfs. Effectively setting an incorrect brightness. Change the update_status handler to return zero for all successful transactions and forward usb_control_msg's error code upon failure. Signed-off-by: Mattias Jacobsson <2pi@mok.nu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/misc/appledisplay.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c index d746c26a8055..bd539f3058bc 100644 --- a/drivers/usb/misc/appledisplay.c +++ b/drivers/usb/misc/appledisplay.c @@ -146,8 +146,11 @@ static int appledisplay_bl_update_status(struct backlight_device *bd) pdata->msgdata, 2, ACD_USB_TIMEOUT); mutex_unlock(&pdata->sysfslock); - - return retval; + + if (retval < 0) + return retval; + else + return 0; } static int appledisplay_bl_get_brightness(struct backlight_device *bd) -- cgit v1.2.3-58-ga151 From e28fd56ad5273be67d0fae5bedc7e1680e729952 Mon Sep 17 00:00:00 2001 From: "Shuah Khan (Samsung OSG)" <shuah@kernel.org> Date: Thu, 18 Oct 2018 10:19:29 -0600 Subject: usbip:vudc: BUG kmalloc-2048 (Not tainted): Poison overwritten In rmmod path, usbip_vudc does platform_device_put() twice once from platform_device_unregister() and then from put_vudc_device(). The second put results in: BUG kmalloc-2048 (Not tainted): Poison overwritten error or BUG: KASAN: use-after-free in kobject_put+0x1e/0x230 if KASAN is enabled. [ 169.042156] calling init+0x0/0x1000 [usbip_vudc] @ 1697 [ 169.042396] ============================================================================= [ 169.043678] probe of usbip-vudc.0 returned 1 after 350 usecs [ 169.044508] BUG kmalloc-2048 (Not tainted): Poison overwritten [ 169.044509] ----------------------------------------------------------------------------- ... [ 169.057849] INFO: Freed in device_release+0x2b/0x80 age=4223 cpu=3 pid=1693 [ 169.057852] kobject_put+0x86/0x1b0 [ 169.057853] 0xffffffffc0c30a96 [ 169.057855] __x64_sys_delete_module+0x157/0x240 Fix it to call platform_device_del() instead and let put_vudc_device() do the platform_device_put(). Reported-by: Randy Dunlap <rdunlap@infradead.org> Signed-off-by: Shuah Khan (Samsung OSG) <shuah@kernel.org> Cc: <stable@vger.kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/usbip/vudc_main.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers/usb') diff --git a/drivers/usb/usbip/vudc_main.c b/drivers/usb/usbip/vudc_main.c index 3fc22037a82f..390733e6937e 100644 --- a/drivers/usb/usbip/vudc_main.c +++ b/drivers/usb/usbip/vudc_main.c @@ -73,6 +73,10 @@ static int __init init(void) cleanup: list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { list_del(&udc_dev->dev_entry); + /* + * Just do platform_device_del() here, put_vudc_device() + * calls the platform_device_put() + */ platform_device_del(udc_dev->pdev); put_vudc_device(udc_dev); } @@ -89,7 +93,11 @@ static void __exit cleanup(void) list_for_each_entry_safe(udc_dev, udc_dev2, &vudc_devices, dev_entry) { list_del(&udc_dev->dev_entry); - platform_device_unregister(udc_dev->pdev); + /* + * Just do platform_device_del() here, put_vudc_device() + * calls the platform_device_put() + */ + platform_device_del(udc_dev->pdev); put_vudc_device(udc_dev); } platform_driver_unregister(&vudc_driver); -- cgit v1.2.3-58-ga151 From 8c14796b6b2485a31a50318d76d6bc4518dc9d39 Mon Sep 17 00:00:00 2001 From: Jon Hunter <jonathanh@nvidia.com> Date: Tue, 16 Oct 2018 14:22:42 +0300 Subject: usb: xhci: tegra: Power-off power-domains on removal Currently the XUSB power domains used by the Tegra xHCI controller are never powered off on the removal of the driver, however, they will be powered off on probe failure. Update the removal code to be consistent with the probe failure path to power off the XUSB power domains. Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Acked-by: Thierry Reding <treding@nvidia.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-tegra.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 4ee510a51d64..920a50a54095 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -1249,6 +1249,11 @@ static int tegra_xusb_remove(struct platform_device *pdev) pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); + if (!pdev->dev.pm_domain) { + tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); + tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + } + tegra_xusb_padctl_put(tegra->padctl); return 0; -- cgit v1.2.3-58-ga151 From 6494a9ad86de921766afe91066f53a794f6c52ea Mon Sep 17 00:00:00 2001 From: Jon Hunter <jonathanh@nvidia.com> Date: Tue, 16 Oct 2018 14:22:43 +0300 Subject: usb: xhci: tegra: Add genpd support The generic power-domain framework has been updated to allow devices that require more than one power-domain to create a new device for each power-domain required and then link these new power-domain devices to the consumer device. Update the Tegra xHCI driver to use the new APIs provided by the generic power-domain framework so we can use the generic power-domain framework for managing the xHCI controllers power-domains. Please note that to maintain backward compatibility with older device-tree blobs these new generic power-domain APIs are only used if the 'power-domains' property is present and otherwise we fall back to using the legacy Tegra APIs for managing the power-domains. Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Acked-by: Thierry Reding <treding@nvidia.com> Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/host/xhci-tegra.c | 89 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 12 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/xhci-tegra.c b/drivers/usb/host/xhci-tegra.c index 920a50a54095..6b5db344de30 100644 --- a/drivers/usb/host/xhci-tegra.c +++ b/drivers/usb/host/xhci-tegra.c @@ -18,6 +18,7 @@ #include <linux/phy/tegra/xusb.h> #include <linux/platform_device.h> #include <linux/pm.h> +#include <linux/pm_domain.h> #include <linux/pm_runtime.h> #include <linux/regulator/consumer.h> #include <linux/reset.h> @@ -194,6 +195,11 @@ struct tegra_xusb { struct reset_control *host_rst; struct reset_control *ss_rst; + struct device *genpd_dev_host; + struct device *genpd_dev_ss; + struct device_link *genpd_dl_host; + struct device_link *genpd_dl_ss; + struct phy **phys; unsigned int num_phys; @@ -928,6 +934,57 @@ static int tegra_xusb_load_firmware(struct tegra_xusb *tegra) return 0; } +static void tegra_xusb_powerdomain_remove(struct device *dev, + struct tegra_xusb *tegra) +{ + if (tegra->genpd_dl_ss) + device_link_del(tegra->genpd_dl_ss); + if (tegra->genpd_dl_host) + device_link_del(tegra->genpd_dl_host); + if (tegra->genpd_dev_ss) + dev_pm_domain_detach(tegra->genpd_dev_ss, true); + if (tegra->genpd_dev_host) + dev_pm_domain_detach(tegra->genpd_dev_host, true); +} + +static int tegra_xusb_powerdomain_init(struct device *dev, + struct tegra_xusb *tegra) +{ + int err; + + tegra->genpd_dev_host = dev_pm_domain_attach_by_name(dev, "xusb_host"); + if (IS_ERR(tegra->genpd_dev_host)) { + err = PTR_ERR(tegra->genpd_dev_host); + dev_err(dev, "failed to get host pm-domain: %d\n", err); + return err; + } + + tegra->genpd_dev_ss = dev_pm_domain_attach_by_name(dev, "xusb_ss"); + if (IS_ERR(tegra->genpd_dev_ss)) { + err = PTR_ERR(tegra->genpd_dev_ss); + dev_err(dev, "failed to get superspeed pm-domain: %d\n", err); + return err; + } + + tegra->genpd_dl_host = device_link_add(dev, tegra->genpd_dev_host, + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); + if (!tegra->genpd_dl_host) { + dev_err(dev, "adding host device link failed!\n"); + return -ENODEV; + } + + tegra->genpd_dl_ss = device_link_add(dev, tegra->genpd_dev_ss, + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); + if (!tegra->genpd_dl_ss) { + dev_err(dev, "adding superspeed device link failed!\n"); + return -ENODEV; + } + + return 0; +} + static int tegra_xusb_probe(struct platform_device *pdev) { struct tegra_xusb_mbox_msg msg; @@ -1038,7 +1095,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) goto put_padctl; } - if (!pdev->dev.pm_domain) { + if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { tegra->host_rst = devm_reset_control_get(&pdev->dev, "xusb_host"); if (IS_ERR(tegra->host_rst)) { @@ -1069,17 +1126,22 @@ static int tegra_xusb_probe(struct platform_device *pdev) tegra->host_clk, tegra->host_rst); if (err) { + tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); dev_err(&pdev->dev, "failed to enable XUSBC domain: %d\n", err); - goto disable_xusba; + goto put_padctl; } + } else { + err = tegra_xusb_powerdomain_init(&pdev->dev, tegra); + if (err) + goto put_powerdomains; } tegra->supplies = devm_kcalloc(&pdev->dev, tegra->soc->num_supplies, sizeof(*tegra->supplies), GFP_KERNEL); if (!tegra->supplies) { err = -ENOMEM; - goto disable_xusbc; + goto put_powerdomains; } for (i = 0; i < tegra->soc->num_supplies; i++) @@ -1089,7 +1151,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) tegra->supplies); if (err) { dev_err(&pdev->dev, "failed to get regulators: %d\n", err); - goto disable_xusbc; + goto put_powerdomains; } for (i = 0; i < tegra->soc->num_types; i++) @@ -1099,7 +1161,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) sizeof(*tegra->phys), GFP_KERNEL); if (!tegra->phys) { err = -ENOMEM; - goto disable_xusbc; + goto put_powerdomains; } for (i = 0, k = 0; i < tegra->soc->num_types; i++) { @@ -1115,7 +1177,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) "failed to get PHY %s: %ld\n", prop, PTR_ERR(phy)); err = PTR_ERR(phy); - goto disable_xusbc; + goto put_powerdomains; } tegra->phys[k++] = phy; @@ -1126,7 +1188,7 @@ static int tegra_xusb_probe(struct platform_device *pdev) dev_name(&pdev->dev)); if (!tegra->hcd) { err = -ENOMEM; - goto disable_xusbc; + goto put_powerdomains; } /* @@ -1222,12 +1284,13 @@ put_rpm: disable_rpm: pm_runtime_disable(&pdev->dev); usb_put_hcd(tegra->hcd); -disable_xusbc: - if (!pdev->dev.pm_domain) +put_powerdomains: + if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); -disable_xusba: - if (!pdev->dev.pm_domain) tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + } else { + tegra_xusb_powerdomain_remove(&pdev->dev, tegra); + } put_padctl: tegra_xusb_padctl_put(tegra->padctl); return err; @@ -1249,9 +1312,11 @@ static int tegra_xusb_remove(struct platform_device *pdev) pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); - if (!pdev->dev.pm_domain) { + if (!of_property_read_bool(pdev->dev.of_node, "power-domains")) { tegra_powergate_power_off(TEGRA_POWERGATE_XUSBC); tegra_powergate_power_off(TEGRA_POWERGATE_XUSBA); + } else { + tegra_xusb_powerdomain_remove(&pdev->dev, tegra); } tegra_xusb_padctl_put(tegra->padctl); -- cgit v1.2.3-58-ga151 From b8d9ee24493d862fbfeb3d209c032647f6073d5d Mon Sep 17 00:00:00 2001 From: Dan Carpenter <dan.carpenter@oracle.com> Date: Thu, 18 Oct 2018 11:04:00 +0300 Subject: usb: phy: ab8500: silence some uninitialized variable warnings Smatch complains that "reg" can be uninitialized if the abx500_get_register_interruptible() call fails. It's an interruptable function, so we should check if the user presses CTRL-C. Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> --- drivers/usb/phy/phy-ab8500-usb.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/phy/phy-ab8500-usb.c b/drivers/usb/phy/phy-ab8500-usb.c index 66143ab8c043..aaf363f19714 100644 --- a/drivers/usb/phy/phy-ab8500-usb.c +++ b/drivers/usb/phy/phy-ab8500-usb.c @@ -505,15 +505,19 @@ static int abx500_usb_link_status_update(struct ab8500_usb *ab) if (is_ab8500(ab->ab8500)) { enum ab8500_usb_link_status lsts; - abx500_get_register_interruptible(ab->dev, + ret = abx500_get_register_interruptible(ab->dev, AB8500_USB, AB8500_USB_LINE_STAT_REG, ®); + if (ret < 0) + return ret; lsts = (reg >> 3) & 0x0F; ret = ab8500_usb_link_status_update(ab, lsts); } else if (is_ab8505(ab->ab8500)) { enum ab8505_usb_link_status lsts; - abx500_get_register_interruptible(ab->dev, + ret = abx500_get_register_interruptible(ab->dev, AB8500_USB, AB8505_USB_LINE_STAT_REG, ®); + if (ret < 0) + return ret; lsts = (reg >> 3) & 0x1F; ret = ab8505_usb_link_status_update(ab, lsts); } -- cgit v1.2.3-58-ga151