> >Hi, > >On 26/11/18 10:24, Pawel Laszczak wrote: >>> EXTERNAL MAIL >>> >>> >>> On 18/11/18 12:09, Pawel Laszczak wrote: >>>> Patch adds host-export.h and host.c file and mplements functions that >>>> allow to initialize, start and stop XHCI host driver. >>>> >>>> Signed-off-by: Pawel Laszczak <pawell@xxxxxxxxxxx> >>>> --- >>>> drivers/usb/cdns3/Kconfig | 10 ++ >>>> drivers/usb/cdns3/Makefile | 1 + >>>> drivers/usb/cdns3/core.c | 7 +- >>>> drivers/usb/cdns3/host-export.h | 30 ++++ >>>> drivers/usb/cdns3/host.c | 256 ++++++++++++++++++++++++++++++++ >>>> 5 files changed, 302 insertions(+), 2 deletions(-) >>>> create mode 100644 drivers/usb/cdns3/host-export.h >>>> create mode 100644 drivers/usb/cdns3/host.c >>>> >>>> diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig >>>> index eb22a8692991..d92bc3d68eb0 100644 >>>> --- a/drivers/usb/cdns3/Kconfig >>>> +++ b/drivers/usb/cdns3/Kconfig >>>> @@ -10,6 +10,16 @@ config USB_CDNS3 >>>> >>>> if USB_CDNS3 >>>> >>>> +config USB_CDNS3_HOST >>>> + bool "Cadence USB3 host controller" >>>> + depends on USB_XHCI_HCD >>>> + help >>>> + Say Y here to enable host controller functionality of the >>>> + cadence driver. >>>> + >>>> + Host controller is compliance with XHCI so it will use >>>> + standard XHCI driver. >>>> + >>>> config USB_CDNS3_PCI_WRAP >>>> tristate "PCIe-based Platforms" >>>> depends on USB_PCI && ACPI >>>> diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile >>>> index e779b2a2f8eb..976117ba67ff 100644 >>>> --- a/drivers/usb/cdns3/Makefile >>>> +++ b/drivers/usb/cdns3/Makefile >>>> @@ -2,4 +2,5 @@ obj-$(CONFIG_USB_CDNS3) += cdns3.o >>>> obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci.o >>>> >>>> cdns3-y := core.o drd.o >>>> +cdns3-$(CONFIG_USB_CDNS3_HOST) += host.o >>>> cdns3-pci-y := cdns3-pci-wrap.o >>>> diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c >>>> index dbee4325da7f..4cb820be9ff3 100644 >>>> --- a/drivers/usb/cdns3/core.c >>>> +++ b/drivers/usb/cdns3/core.c >>>> @@ -17,6 +17,7 @@ >>>> >>>> #include "gadget.h" >>>> #include "core.h" >>>> +#include "host-export.h" >>>> #include "drd.h" >>>> >>>> static inline struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns) >>>> @@ -98,7 +99,8 @@ static int cdns3_core_init_role(struct cdns3 *cdns) >>>> } >>>> >>>> if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { >>>> - //TODO: implements host initialization >>>> + if (cdns3_host_init(cdns)) >>>> + dev_info(dev, "doesn't support host\n"); >>> >>> dev_err() >>> >>> And you need to error out with error code. >> >> ok, but I assume that even if host returns error then we can use >> only device role. Only when both functions return errors, then it's a critical error >> and function return error code. > >But at this point we are in OTG or HOST dr_mode and without host functional >both will not function correctly. So we must error out so user can debug. Ok, > >>> >>>> } >>>> >>>> if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { >>>> @@ -142,7 +144,7 @@ static irqreturn_t cdns3_irq(int irq, void *data) >>>> >>>> static void cdns3_remove_roles(struct cdns3 *cdns) >>>> { >>>> - //TODO: implements this function >>> >>> if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) >>> >>>> + cdns3_host_remove(cdns); >>> >>> How about calling it cdns3_host_exit() to complement cdns3_host_init(). >>> >>>> } >>>> >>>> static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role) >>>> @@ -410,6 +412,7 @@ static struct platform_driver cdns3_driver = { >>>> >>>> static int __init cdns3_driver_platform_register(void) >>>> { >>>> + cdns3_host_driver_init(); >>>> return platform_driver_register(&cdns3_driver); >>>> } >>>> module_init(cdns3_driver_platform_register); >>>> diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h >>>> new file mode 100644 >>>> index 000000000000..f8f3b230b472 >>>> --- /dev/null >>>> +++ b/drivers/usb/cdns3/host-export.h >>>> @@ -0,0 +1,30 @@ >>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>> +/* >>>> + * Cadence USBSS DRD Driver -Host Export APIs >>>> + * >>>> + * Copyright (C) 2017 NXP >>>> + * >>>> + * Authors: Peter Chen <peter.chen@xxxxxxx> >>>> + */ >>>> +#ifndef __LINUX_CDNS3_HOST_EXPORT >>>> +#define __LINUX_CDNS3_HOST_EXPORT >>>> + >>>> +#ifdef CONFIG_USB_CDNS3_HOST >>>> + >>>> +int cdns3_host_init(struct cdns3 *cdns); >>>> +void cdns3_host_remove(struct cdns3 *cdns); >>>> +void cdns3_host_driver_init(void); >>>> + >>>> +#else >>>> + >>>> +static inline int cdns3_host_init(struct cdns3 *cdns) >>>> +{ >>>> + return -ENXIO; >>>> +} >>>> + >>>> +static inline void cdns3_host_remove(struct cdns3 *cdns) { } >>>> +static inline void cdns3_host_driver_init(void) {} >>>> + >>>> +#endif /* CONFIG_USB_CDNS3_HOST */ >>>> + >>>> +#endif /* __LINUX_CDNS3_HOST_EXPORT */ >>>> diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c >>>> new file mode 100644 >>>> index 000000000000..0dd47976cb28 >>>> --- /dev/null >>>> +++ b/drivers/usb/cdns3/host.c >>>> @@ -0,0 +1,256 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> +/* >>>> + * Cadence USBSS DRD Driver - host side >>>> + * >>>> + * Copyright (C) 2018 Cadence Design Systems. >>>> + * Copyright (C) 2018 NXP >>>> + * >>>> + * Authors: Peter Chen <peter.chen@xxxxxxx> >>>> + * Pawel Laszczak <pawell@xxxxxxxxxxx> >>>> + */ >>>> + >>>> +#include <linux/kernel.h> >>>> +#include <linux/device.h> >>>> +#include <linux/io.h> >>>> +#include <linux/slab.h> >>>> +#include <linux/dma-mapping.h> >>>> +#include <linux/usb.h> >>>> +#include <linux/usb/hcd.h> >>>> +#include <linux/pm_runtime.h> >>>> +#include <linux/usb/of.h> >>>> + >>>> +#include "../host/xhci.h" >>>> +#include "core.h" >>>> +#include "host-export.h" >>>> + >>>> +static struct hc_driver __read_mostly xhci_cdns3_hc_driver; >>>> + >>>> +static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci) >>>> +{ >>>> + /* >>>> + * As of now platform drivers don't provide MSI support so we ensure >>>> + * here that the generic code does not try to make a pci_dev from our >>>> + * dev struct in order to setup MSI >>>> + */ >>>> + xhci->quirks |= XHCI_PLAT; >>>> +} >>>> + >>>> +static int xhci_cdns3_setup(struct usb_hcd *hcd) >>>> +{ >>>> + struct xhci_hcd *xhci = hcd_to_xhci(hcd); >>>> + u32 command; >>>> + int ret; >>>> + >>>> + ret = xhci_gen_setup(hcd, xhci_cdns3_quirks); >>>> + if (ret) >>>> + return ret; >>>> + >>>> + /* set usbcmd.EU3S */ >>>> + command = readl(&xhci->op_regs->command); >>>> + command |= CMD_PM_INDEX; >>>> + writel(command, &xhci->op_regs->command); >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = { >>>> + .extra_priv_size = sizeof(struct xhci_hcd), >>>> + .reset = xhci_cdns3_setup, >>>> +}; >>>> + >>>> +struct cdns3_host { >>>> + struct device dev; >>>> + struct usb_hcd *hcd; >>>> +}; >>>> + >>>> +static irqreturn_t cdns3_host_irq(struct cdns3 *cdns) >>>> +{ >>>> + struct device *dev = cdns->host_dev; >>>> + struct usb_hcd *hcd; >>>> + >>>> + if (dev) >>>> + hcd = dev_get_drvdata(dev); >>>> + else >>>> + return IRQ_NONE; >>>> + >>>> + if (hcd) >>>> + return usb_hcd_irq(cdns->irq, hcd); >>>> + else >>>> + return IRQ_NONE; >>> >>> Why can't you just reuse the xhci-platform driver and let it manage the IRQ? >>> Since it is a shared IRQ, different drivers can request the same IRQ and return IRQ_NONE >>> if the IRQ wasn't from their device. >> >> In device role the host part of controller is kept in reset, so driver can't read the host register. >> Such solution allows driver to control access to host register. >> So if driver has shared separate interrupt for host role then it has to check if controller work in >> Host role. > >I understand what you mean. I think the issue here is that you are having the host ISR active >even when host role is stopped. This is the root cause of the problem. > >When you stop host role, the host driver *must* unregister the ISR >and then place the host in reset. > >This will happen correctly if you use platform_unregister_device() to unregister the >XHCI device in cdns3_host_stop(). Ok, now I understood you concept. I will test it. >> >>>> +} >>>> + >>>> +static void cdns3_host_release(struct device *dev) >>>> +{ >>>> + struct cdns3_host *host = container_of(dev, struct cdns3_host, dev); >>>> + >>>> + kfree(host); >>>> +} >>>> + >>>> +static int cdns3_host_start(struct cdns3 *cdns) >>>> +{ >>>> + struct cdns3_host *host; >>>> + struct device *dev; >>>> + struct device *sysdev; >>>> + struct xhci_hcd *xhci; >>>> + int ret; >>>> + >>>> + host = kzalloc(sizeof(*host), GFP_KERNEL); >>>> + if (!host) >>>> + return -ENOMEM; >>>> + >>>> + dev = &host->dev; >>>> + dev->release = cdns3_host_release; >>>> + dev->parent = cdns->dev; >>>> + dev_set_name(dev, "xhci-cdns3"); >>>> + cdns->host_dev = dev; >>>> + ret = device_register(dev); >>>> + if (ret) >>>> + goto err1; >>>> + >>>> + sysdev = cdns->dev; >>>> + /* Try to set 64-bit DMA first */ >>>> + if (WARN_ON(!sysdev->dma_mask)) >>>> + /* Platform did not initialize dma_mask */ >>>> + ret = dma_coerce_mask_and_coherent(sysdev, >>>> + DMA_BIT_MASK(64)); >>>> + else >>>> + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); >>>> + >>>> + /* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */ >>>> + if (ret) { >>>> + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32)); >>>> + if (ret) >>>> + return ret; >>>> + } >>>> + >>>> + pm_runtime_set_active(dev); >>>> + pm_runtime_no_callbacks(dev); >>>> + pm_runtime_enable(dev); >>>> + host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev, >>>> + dev_name(dev), NULL); >>>> + if (!host->hcd) { >>>> + ret = -ENOMEM; >>>> + goto err2; >>>> + } >>>> + >>>> + host->hcd->regs = cdns->xhci_regs; >>>> + host->hcd->rsrc_start = cdns->xhci_res->start; >>>> + host->hcd->rsrc_len = resource_size(cdns->xhci_res); >>>> + >>>> + device_wakeup_enable(host->hcd->self.controller); >>>> + xhci = hcd_to_xhci(host->hcd); >>>> + >>>> + xhci->main_hcd = host->hcd; >>>> + xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev, >>>> + dev_name(dev), host->hcd); >>>> + if (!xhci->shared_hcd) { >>>> + ret = -ENOMEM; >>>> + goto err3; >>>> + } >>>> + >>>> + host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node); >>>> + xhci->shared_hcd->tpl_support = host->hcd->tpl_support; >>>> + ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED); >>>> + if (ret) >>>> + goto err4; >>>> + >>>> + ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED); >>>> + if (ret) >>>> + goto err5; >>>> + >>>> + device_set_wakeup_capable(dev, true); >>> >>> All this is being done by the xhci-plat.c >>> >>> You can make use of it by just creating a xhci-hcd platform device. >>> >>> e.g. >>> platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); >>> platform_device_add_resources() to add IRQ and memory resource. >>> platform_device_add_properties() to add any quirks. >>> platform_device_add() >>> >> >> If we do this in this way driver will not control the interrupt. > >Why should this driver control host interrupt when it doesn't >have access to HOST registers. > You you are right. I doesn't have to. >> This code has written by Peter Chan and I am convinced >> that this concept is only correct one. >> >>>> + >>>> + return 0; >>>> + >>>> +err5: >>>> + usb_remove_hcd(host->hcd); >>>> +err4: >>>> + usb_put_hcd(xhci->shared_hcd); >>>> +err3: >>>> + usb_put_hcd(host->hcd); >>>> +err2: >>>> + device_del(dev); >>>> +err1: >>>> + put_device(dev); >>>> + cdns->host_dev = NULL; >>>> + return ret; >>>> +} >>>> + >>>> +static void cdns3_host_stop(struct cdns3 *cdns) >>>> +{ >>>> + struct device *dev = cdns->host_dev; >>>> + struct xhci_hcd *xhci; >>>> + struct usb_hcd *hcd; >>>> + >>>> + if (dev) { >>>> + hcd = dev_get_drvdata(dev); >>>> + xhci = hcd_to_xhci(hcd); >>>> + usb_remove_hcd(xhci->shared_hcd); >>>> + usb_remove_hcd(hcd); >>>> + synchronize_irq(cdns->irq); >>>> + usb_put_hcd(xhci->shared_hcd); >>>> + usb_put_hcd(hcd); >>>> + cdns->host_dev = NULL; >>>> + pm_runtime_set_suspended(dev); >>>> + pm_runtime_disable(dev); >>>> + device_del(dev); >>>> + put_device(dev); >>>> + } >>> >>> You can replace this with just >>> platform_device_unregister(xhci_dev); >>> >>>> +} >>>> + >>>> +#if CONFIG_PM >>>> +static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup) >>>> +{ >>>> + struct device *dev = cdns->host_dev; >>>> + struct xhci_hcd *xhci; >>>> + >>>> + if (!dev) >>>> + return 0; >>>> + >>>> + xhci = hcd_to_xhci(dev_get_drvdata(dev)); >>>> + return xhci_suspend(xhci, do_wakeup); >>>> +} >>>> + >>>> +static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated) >>>> +{ >>>> + struct device *dev = cdns->host_dev; >>>> + struct xhci_hcd *xhci; >>>> + >>>> + if (!dev) >>>> + return 0; >>>> + >>>> + xhci = hcd_to_xhci(dev_get_drvdata(dev)); >>>> + return xhci_resume(xhci, hibernated); >>>> +} >>> >>> These won't be required any more as xhci-plat is doing this. >>> >>>> +#endif /* CONFIG_PM */ >>>> + >>>> +int cdns3_host_init(struct cdns3 *cdns) >>>> +{ >>>> + struct cdns3_role_driver *rdrv; >>>> + >>>> + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); >>>> + if (!rdrv) >>>> + return -ENOMEM; >>>> + >>>> + rdrv->start = cdns3_host_start; >>>> + rdrv->stop = cdns3_host_stop; >>>> + rdrv->irq = cdns3_host_irq; >>>> +#if CONFIG_PM >>>> + rdrv->suspend = cdns3_host_suspend; >>>> + rdrv->resume = cdns3_host_resume; >>>> +#endif /* CONFIG_PM */ >>>> + rdrv->name = "host"; >>>> + cdns->roles[CDNS3_ROLE_HOST] = rdrv; >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +void cdns3_host_remove(struct cdns3 *cdns) >>>> +{ >>>> + cdns3_host_stop(cdns); >>> >>> calling cdns3_host_stop() here can lead to problems as Controller might be in >>> peripheral mode at this point. The core driver needs to ensure that relevant role >>> is stopped before calling cdns3_host_remove(). >>> >>> Here you need to unregister the role driver though. >>> >>> cdns->roles[CDNS3_ROLE_HOST] = NULL; >>> >> This function can be called only in host mode/role. It operate on host registers. >> This checking is provided in core.c file. >>>> +} >>>> + >>>> +void __init cdns3_host_driver_init(void) >>>> +{ >>>> + xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides); >>>> +} >>>> >>> > >cheers, >-roger > >-- >Texas Instruments Finland Oy, Porkkalankatu 22, 00180 Helsinki. >Y-tunnus/Business ID: 0615521-4. Kotipaikka/Domicile: Helsinki