diff options
Diffstat (limited to 'drivers/usb/imx/chipidea-imx.c')
-rw-r--r-- | drivers/usb/imx/chipidea-imx.c | 239 |
1 files changed, 108 insertions, 131 deletions
diff --git a/drivers/usb/imx/chipidea-imx.c b/drivers/usb/imx/chipidea-imx.c index 6c60c383f0..c5e6ce61e9 100644 --- a/drivers/usb/imx/chipidea-imx.c +++ b/drivers/usb/imx/chipidea-imx.c @@ -1,15 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2012 Sascha Hauer <s.hauer@pengutronix.de> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. */ #include <common.h> @@ -18,43 +9,47 @@ #include <of.h> #include <errno.h> #include <driver.h> -#include <usb/usb.h> -#include <usb/ehci.h> +#include <linux/usb/usb.h> +#include <linux/usb/ehci.h> #include <regulator.h> -#include <usb/chipidea-imx.h> -#include <usb/phy.h> -#include <usb/ulpi.h> -#include <usb/fsl_usb2.h> +#include <linux/usb/chipidea-imx.h> +#include <linux/usb/phy.h> +#include <linux/usb/ulpi.h> +#include <linux/usb/fsl_usb2.h> #include <linux/err.h> #include <linux/phy/phy.h> #include <linux/clk.h> #define MXC_EHCI_PORTSC_MASK ((0xf << 28) | (1 << 25)) +struct imx_chipidea_data { + bool have_usb_misc; +}; + struct imx_chipidea { - struct device_d *dev; + struct device *dev; void __iomem *base; struct ehci_data data; unsigned long flags; - uint32_t mode; + enum usb_dr_mode mode; int portno; - struct device_d *usbmisc; + struct device *usbmisc; enum usb_phy_interface phymode; struct param_d *param_mode; - int role_registered; struct regulator *vbus; struct phy *phy; struct usb_phy *usbphy; struct clk *clk; struct ehci_host *ehci; struct fsl_udc *udc; + bool have_usb_misc; }; static int imx_chipidea_port_init(void *drvdata) { struct imx_chipidea *ci = drvdata; uint32_t portsc; - int ret; + int ret = 0; if ((ci->flags & MXC_EHCI_PORTSC_MASK) == MXC_EHCI_MODE_ULPI) { dev_dbg(ci->dev, "using ULPI phy\n"); @@ -73,9 +68,11 @@ static int imx_chipidea_port_init(void *drvdata) return ret; } - ret = imx_usbmisc_port_init(ci->usbmisc, ci->portno, ci->flags); - if (ret) - dev_err(ci->dev, "misc init failed: %s\n", strerror(-ret)); + if (ci->have_usb_misc) { + ret = imx_usbmisc_port_init(ci->usbmisc, ci->portno, ci->flags); + if (ret) + dev_err(ci->dev, "misc init failed: %s\n", strerror(-ret)); + } /* PFSC bit is reset by ehci_reset(), thus have to set it not in * probe but here, after ehci_reset() is already called */ @@ -91,11 +88,13 @@ static int imx_chipidea_port_init(void *drvdata) static int imx_chipidea_port_post_init(void *drvdata) { struct imx_chipidea *ci = drvdata; - int ret; + int ret = 0; - ret = imx_usbmisc_port_post_init(ci->usbmisc, ci->portno, ci->flags); - if (ret) - dev_err(ci->dev, "post misc init failed: %s\n", strerror(-ret)); + if (ci->have_usb_misc) { + ret = imx_usbmisc_port_post_init(ci->usbmisc, ci->portno, ci->flags); + if (ret) + dev_err(ci->dev, "post misc init failed: %s\n", strerror(-ret)); + } return ret; } @@ -103,45 +102,35 @@ static int imx_chipidea_port_post_init(void *drvdata) static int imx_chipidea_probe_dt(struct imx_chipidea *ci) { struct of_phandle_args out_args; - enum usb_dr_mode mode; - if (of_parse_phandle_with_args(ci->dev->device_node, "fsl,usbmisc", - "#index-cells", 0, &out_args)) - return -ENODEV; + if (ci->have_usb_misc) { + if (of_parse_phandle_with_args(ci->dev->of_node, "fsl,usbmisc", + "#index-cells", 0, &out_args)) + return -ENODEV; + + ci->usbmisc = of_find_device_by_node(out_args.np); + if (!ci->usbmisc) + return -ENODEV; - ci->usbmisc = of_find_device_by_node(out_args.np); - if (!ci->usbmisc) - return -ENODEV; + ci->portno = out_args.args[0]; + } - ci->portno = out_args.args[0]; ci->flags = MXC_EHCI_MODE_UTMI_8BIT; - mode = of_usb_get_dr_mode(ci->dev->device_node, NULL); + ci->mode = of_usb_get_dr_mode(ci->dev->of_node, NULL); - switch (mode) { - case USB_DR_MODE_HOST: - default: - ci->mode = IMX_USB_MODE_HOST; - break; - case USB_DR_MODE_PERIPHERAL: - ci->mode = IMX_USB_MODE_DEVICE; - break; - case USB_DR_MODE_OTG: - ci->mode = IMX_USB_MODE_OTG; - break; - case USB_DR_MODE_UNKNOWN: + if (ci->mode == USB_DR_MODE_UNKNOWN) { /* * No dr_mode specified. This means it can either be OTG * for port 0 or host mode for the other host-only ports. */ if (ci->portno == 0) - ci->mode = IMX_USB_MODE_OTG; + ci->mode = USB_DR_MODE_OTG; else - ci->mode = IMX_USB_MODE_HOST; - break; + ci->mode = USB_DR_MODE_HOST; } - ci->phymode = of_usb_get_phy_mode(ci->dev->device_node, NULL); + ci->phymode = of_usb_get_phy_mode(ci->dev->of_node, NULL); switch (ci->phymode) { case USBPHY_INTERFACE_MODE_UTMI: ci->flags = MXC_EHCI_MODE_UTMI_8BIT; @@ -162,40 +151,33 @@ static int imx_chipidea_probe_dt(struct imx_chipidea *ci) dev_dbg(ci->dev, "no phy_type setting. Relying on reset default\n"); } - if (of_find_property(ci->dev->device_node, + if (of_find_property(ci->dev->of_node, "disable-over-current", NULL)) ci->flags |= MXC_EHCI_DISABLE_OVERCURRENT; - else if (!of_find_property(ci->dev->device_node, + else if (!of_find_property(ci->dev->of_node, "over-current-active-high", NULL)) ci->flags |= MXC_EHCI_OC_PIN_ACTIVE_LOW; - if (of_usb_get_maximum_speed(ci->dev->device_node, NULL) == + if (of_find_property(ci->dev->of_node, "power-active-high", NULL)) + ci->flags |= MXC_EHCI_PWR_PIN_ACTIVE_HIGH; + + if (of_usb_get_maximum_speed(ci->dev->of_node, NULL) == USB_SPEED_FULL) ci->flags |= MXC_EHCI_PFSC; return 0; } -static int ci_ehci_detect(struct device_d *dev) -{ - struct imx_chipidea *ci = dev->priv; - - return ehci_detect(ci->ehci); -} - -static int ci_register_role(struct imx_chipidea *ci) +static int ci_set_mode(void *ctx, enum usb_dr_mode mode) { + struct imx_chipidea *ci = ctx; int ret; - if (ci->role_registered != IMX_USB_MODE_OTG) - return -EBUSY; - - if (ci->mode == IMX_USB_MODE_HOST) { + if (mode == USB_DR_MODE_HOST) { if (IS_ENABLED(CONFIG_USB_EHCI)) { struct ehci_host *ehci; - ci->role_registered = IMX_USB_MODE_HOST; ret = regulator_enable(ci->vbus); if (ret) return ret; @@ -207,18 +189,15 @@ static int ci_register_role(struct imx_chipidea *ci) } ci->ehci = ehci; - - ci->dev->detect = ci_ehci_detect; } else { dev_err(ci->dev, "Host support not available\n"); return -ENODEV; } } - if (ci->mode == IMX_USB_MODE_DEVICE) { + if (mode == USB_DR_MODE_PERIPHERAL) { if (IS_ENABLED(CONFIG_USB_GADGET_DRIVER_ARC)) { struct fsl_udc *udc; - ci->role_registered = IMX_USB_MODE_DEVICE; udc = ci_udc_register(ci->dev, ci->base); if (IS_ERR(udc)) @@ -234,52 +213,12 @@ static int ci_register_role(struct imx_chipidea *ci) return 0; } -static int ci_set_mode(struct param_d *param, void *priv) -{ - struct imx_chipidea *ci = priv; - - if (ci->role_registered != IMX_USB_MODE_OTG) { - if (ci->role_registered == ci->mode) - return 0; - else - return -EBUSY; - } - - return ci_register_role(ci); -} - -static const char *ci_mode_names[] = { - "host", "peripheral", "otg" -}; - -static struct device_d imx_otg_device = { - .name = "otg", - .id = DEVICE_ID_SINGLE, -}; - -static int ci_register_otg_device(struct imx_chipidea *ci) -{ - int ret; - - if (imx_otg_device.parent) - return -EBUSY; - - imx_otg_device.parent = ci->dev; - - ret = register_device(&imx_otg_device); - if (ret) - return ret; - - ci->param_mode = dev_add_param_enum(&imx_otg_device, "mode", - ci_set_mode, NULL, &ci->mode, - ci_mode_names, ARRAY_SIZE(ci_mode_names), ci); - return 0; -} - -static int imx_chipidea_probe(struct device_d *dev) +static int imx_chipidea_probe(struct device *dev) { struct resource *iores; + struct imx_chipidea_data *imx_data; struct imxusb_platformdata *pdata = dev->platform_data; + char const *phynode_name; int ret; void __iomem *base; struct imx_chipidea *ci; @@ -288,9 +227,12 @@ static int imx_chipidea_probe(struct device_d *dev) ci = xzalloc(sizeof(*ci)); ci->dev = dev; dev->priv = ci; - ci->role_registered = IMX_USB_MODE_OTG; - if (IS_ENABLED(CONFIG_OFDEVICE) && dev->device_node) { + ret = dev_get_drvdata(dev, (const void **)&imx_data); + if (!ret) + ci->have_usb_misc = imx_data->have_usb_misc; + + if (IS_ENABLED(CONFIG_OFDEVICE) && dev->of_node) { ret = imx_chipidea_probe_dt(ci); if (ret) return ret; @@ -306,8 +248,11 @@ static int imx_chipidea_probe(struct device_d *dev) } ci->vbus = regulator_get(dev, "vbus"); - if (IS_ERR(ci->vbus)) + if (IS_ERR(ci->vbus)) { + dev_warn(dev, "Cannot get vbus regulator: %pe (ignoring)\n", + ci->vbus); ci->vbus = NULL; + } /* * Some devices have more than one clock, in this case they are enabled @@ -315,15 +260,23 @@ static int imx_chipidea_probe(struct device_d *dev) * devices which have only one. */ ci->clk = clk_get(dev, NULL); - if (!IS_ERR(ci->clk)) - clk_enable(ci->clk); - if (of_property_read_bool(dev->device_node, "fsl,usbphy")) { - ci->phy = of_phy_get_by_phandle(dev, "fsl,usbphy", 0); + /* Device trees are using both "phys" and "fsl,usbphy". Prefer the + * more modern former one but fall back to the old one. + * + * Code should be removed when all devicetrees are using "phys" */ + if (of_property_read_bool(dev->of_node, "phys")) + phynode_name = "phys"; + else if (of_property_read_bool(dev->of_node, "fsl,usbphy")) + phynode_name = "fsl,usbphy"; + else + phynode_name = NULL; + + if (phynode_name) { + ci->phy = of_phy_get_by_phandle(dev, phynode_name, 0); if (IS_ERR(ci->phy)) { - ret = PTR_ERR(ci->phy); - dev_err(dev, "Cannot get phy: %s\n", strerror(-ret)); - return ret; + dev_err(dev, "Cannot get phy: %pe\n", ci->phy); + return PTR_ERR(ci->phy); } else { ci->usbphy = phy_to_usbphy(ci->phy); if (IS_ERR(ci->usbphy)) @@ -347,6 +300,14 @@ static int imx_chipidea_probe(struct device_d *dev) ci->data.drvdata = ci; ci->data.usbphy = ci->usbphy; + /* + * Enable the clock after we ensured that all resources are available. + * This is crucial since the phy can be missing which and so the + * usb-controller <-> usb-phy communication is only partly initialized. + * This can trigger strange system hangs at least on i.MX8M SoCs. + */ + clk_enable(ci->clk); + if ((ci->flags & MXC_EHCI_PORTSC_MASK) == MXC_EHCI_MODE_HSIC) imx_chipidea_port_init(ci); @@ -361,15 +322,15 @@ static int imx_chipidea_probe(struct device_d *dev) ci->data.hcor = base + 0x140; ci->data.flags = EHCI_HAS_TT; - if (ci->mode == IMX_USB_MODE_OTG) - ret = ci_register_otg_device(ci); + if (ci->mode == USB_DR_MODE_OTG) + ret = usb_register_otg_device(ci->dev, ci_set_mode, ci); else - ret = ci_register_role(ci); + ret = ci_set_mode(ci, ci->mode); return ret; }; -static void imx_chipidea_remove(struct device_d *dev) +static void imx_chipidea_remove(struct device *dev) { struct imx_chipidea *ci = dev->priv; @@ -380,15 +341,31 @@ static void imx_chipidea_remove(struct device_d *dev) ci_udc_unregister(ci->udc); } +static const struct imx_chipidea_data imx_data = { + .have_usb_misc = 1, +}; + +static const struct imx_chipidea_data imx28_data = { + .have_usb_misc = 0, +}; + static __maybe_unused struct of_device_id imx_chipidea_dt_ids[] = { { .compatible = "fsl,imx27-usb", + .data = &imx_data, + }, { + .compatible = "fsl,imx28-usb", + .data = &imx28_data, + }, { + .compatible = "fsl,imx7d-usb", + .data = &imx_data, }, { /* sentinel */ }, }; +MODULE_DEVICE_TABLE(of, imx_chipidea_dt_ids); -static struct driver_d imx_chipidea_driver = { +static struct driver imx_chipidea_driver = { .name = "imx-usb", .probe = imx_chipidea_probe, .of_compatible = DRV_OF_COMPAT(imx_chipidea_dt_ids), |