2017-11-03 18:28:30 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2012-05-11 22:25:54 +08:00
|
|
|
/*
|
|
|
|
* host.c - ChipIdea USB host controller driver
|
|
|
|
*
|
|
|
|
* Copyright (c) 2012 Intel Corporation
|
|
|
|
*
|
|
|
|
* Author: Alexander Shishkin
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
2012-11-04 00:39:27 +08:00
|
|
|
#include <linux/io.h>
|
2012-05-11 22:25:54 +08:00
|
|
|
#include <linux/usb.h>
|
|
|
|
#include <linux/usb/hcd.h>
|
|
|
|
#include <linux/usb/chipidea.h>
|
usb: chipidea: host: add vbus regulator control
For boards which have board level vbus control (eg, through gpio), we
need to vbus operation according to below rules:
- For host, we need open vbus before start hcd, and close it
after remove hcd.
- For otg, the vbus needs to be on/off when usb role switches.
When the host roles begins, it opens vbus; when the host role
finishes, it closes vbus.
We put vbus operation to host as host is the only vbus user,
When we are at host mode, the vbus is on, when we are not at
host mode, vbus should be off.
Tested-by: Marek Vasut <marex@denx.de>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-08-14 17:44:04 +08:00
|
|
|
#include <linux/regulator/consumer.h>
|
2018-09-04 23:18:55 +08:00
|
|
|
#include <linux/pinctrl/consumer.h>
|
2012-05-11 22:25:54 +08:00
|
|
|
|
2012-11-03 00:34:41 +08:00
|
|
|
#include "../host/ehci.h"
|
2012-05-11 22:25:54 +08:00
|
|
|
|
|
|
|
#include "ci.h"
|
|
|
|
#include "bits.h"
|
|
|
|
#include "host.h"
|
|
|
|
|
2012-11-03 00:34:41 +08:00
|
|
|
static struct hc_driver __read_mostly ci_ehci_hc_driver;
|
2015-02-11 12:44:59 +08:00
|
|
|
static int (*orig_bus_suspend)(struct usb_hcd *hcd);
|
2012-11-03 00:34:41 +08:00
|
|
|
|
2014-10-13 09:53:04 +08:00
|
|
|
struct ehci_ci_priv {
|
|
|
|
struct regulator *reg_vbus;
|
2024-06-11 20:08:33 +08:00
|
|
|
bool enabled;
|
2014-10-13 09:53:04 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
|
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
|
|
|
struct ehci_ci_priv *priv = (struct ehci_ci_priv *)ehci->priv;
|
|
|
|
struct device *dev = hcd->self.controller;
|
2015-07-14 15:43:02 +08:00
|
|
|
struct ci_hdrc *ci = dev_get_drvdata(dev);
|
2014-10-13 09:53:04 +08:00
|
|
|
int ret = 0;
|
|
|
|
int port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
|
2024-06-11 20:08:33 +08:00
|
|
|
if (priv->reg_vbus && enable != priv->enabled) {
|
2014-10-13 09:53:04 +08:00
|
|
|
if (port > 1) {
|
|
|
|
dev_warn(dev,
|
|
|
|
"Not support multi-port regulator control\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (enable)
|
|
|
|
ret = regulator_enable(priv->reg_vbus);
|
|
|
|
else
|
|
|
|
ret = regulator_disable(priv->reg_vbus);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev,
|
|
|
|
"Failed to %s vbus regulator, ret=%d\n",
|
|
|
|
enable ? "enable" : "disable", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2024-06-11 20:08:33 +08:00
|
|
|
priv->enabled = enable;
|
2014-10-13 09:53:04 +08:00
|
|
|
}
|
2015-05-30 00:38:46 +08:00
|
|
|
|
|
|
|
if (enable && (ci->platdata->phy_mode == USBPHY_INTERFACE_MODE_HSIC)) {
|
|
|
|
/*
|
|
|
|
* Marvell 28nm HSIC PHY requires forcing the port to HS mode.
|
|
|
|
* As HSIC is always HS, this should be safe for others.
|
|
|
|
*/
|
|
|
|
hw_port_test_set(ci, 5);
|
|
|
|
hw_port_test_set(ci, 0);
|
|
|
|
}
|
2014-10-13 09:53:04 +08:00
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
2015-08-05 10:16:19 +08:00
|
|
|
static int ehci_ci_reset(struct usb_hcd *hcd)
|
|
|
|
{
|
|
|
|
struct device *dev = hcd->self.controller;
|
|
|
|
struct ci_hdrc *ci = dev_get_drvdata(dev);
|
2016-08-15 22:09:07 +08:00
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
2015-08-05 10:16:19 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = ehci_setup(hcd);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-08-15 22:09:07 +08:00
|
|
|
ehci->need_io_watchdog = 0;
|
|
|
|
|
2016-12-29 06:57:06 +08:00
|
|
|
if (ci->platdata->notify_event) {
|
|
|
|
ret = ci->platdata->notify_event(ci,
|
|
|
|
CI_HDRC_CONTROLLER_RESET_EVENT);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2016-12-29 06:56:53 +08:00
|
|
|
|
2015-08-05 10:16:19 +08:00
|
|
|
ci_platform_configure(ci);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-10-13 09:53:04 +08:00
|
|
|
static const struct ehci_driver_overrides ehci_ci_overrides = {
|
|
|
|
.extra_priv_size = sizeof(struct ehci_ci_priv),
|
|
|
|
.port_power = ehci_ci_portpower,
|
2015-08-05 10:16:19 +08:00
|
|
|
.reset = ehci_ci_reset,
|
2014-10-13 09:53:04 +08:00
|
|
|
};
|
|
|
|
|
2013-06-24 19:46:36 +08:00
|
|
|
static irqreturn_t host_irq(struct ci_hdrc *ci)
|
2012-05-11 22:25:54 +08:00
|
|
|
{
|
|
|
|
return usb_hcd_irq(ci->irq, ci->hcd);
|
|
|
|
}
|
|
|
|
|
2013-06-24 19:46:36 +08:00
|
|
|
static int host_start(struct ci_hdrc *ci)
|
2012-05-11 22:25:54 +08:00
|
|
|
{
|
|
|
|
struct usb_hcd *hcd;
|
|
|
|
struct ehci_hcd *ehci;
|
2014-10-13 09:53:04 +08:00
|
|
|
struct ehci_ci_priv *priv;
|
2012-05-11 22:25:54 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (usb_disabled())
|
|
|
|
return -ENODEV;
|
|
|
|
|
2017-03-13 10:18:42 +08:00
|
|
|
hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent,
|
|
|
|
ci->dev, dev_name(ci->dev), NULL);
|
2012-05-11 22:25:54 +08:00
|
|
|
if (!hcd)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2014-12-24 11:33:17 +08:00
|
|
|
dev_set_drvdata(ci->dev, ci);
|
2012-05-11 22:25:54 +08:00
|
|
|
hcd->rsrc_start = ci->hw_bank.phys;
|
|
|
|
hcd->rsrc_len = ci->hw_bank.size;
|
|
|
|
hcd->regs = ci->hw_bank.abs;
|
|
|
|
hcd->has_tt = 1;
|
|
|
|
|
2012-06-29 17:48:53 +08:00
|
|
|
hcd->power_budget = ci->platdata->power_budget;
|
2014-08-19 09:51:56 +08:00
|
|
|
hcd->tpl_support = ci->platdata->tpl_support;
|
2018-06-06 09:43:27 +08:00
|
|
|
if (ci->phy || ci->usb_phy) {
|
2018-03-04 05:43:03 +08:00
|
|
|
hcd->skip_phy_initialization = 1;
|
2018-06-06 09:43:27 +08:00
|
|
|
if (ci->usb_phy)
|
|
|
|
hcd->usb_phy = ci->usb_phy;
|
|
|
|
}
|
2012-05-11 22:25:55 +08:00
|
|
|
|
2012-05-11 22:25:54 +08:00
|
|
|
ehci = hcd_to_ehci(hcd);
|
|
|
|
ehci->caps = ci->hw_bank.cap;
|
|
|
|
ehci->has_hostpc = ci->hw_bank.lpm;
|
2013-08-12 21:06:49 +08:00
|
|
|
ehci->has_tdi_phy_lpm = ci->hw_bank.lpm;
|
2014-01-10 13:51:27 +08:00
|
|
|
ehci->imx28_write_fix = ci->imx28_write_fix;
|
2012-05-11 22:25:54 +08:00
|
|
|
|
2014-10-13 09:53:04 +08:00
|
|
|
priv = (struct ehci_ci_priv *)ehci->priv;
|
|
|
|
priv->reg_vbus = NULL;
|
|
|
|
|
2015-02-11 12:45:02 +08:00
|
|
|
if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) {
|
|
|
|
if (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON) {
|
|
|
|
ret = regulator_enable(ci->platdata->reg_vbus);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(ci->dev,
|
|
|
|
"Failed to enable vbus regulator, ret=%d\n",
|
|
|
|
ret);
|
|
|
|
goto put_hcd;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
priv->reg_vbus = ci->platdata->reg_vbus;
|
|
|
|
}
|
|
|
|
}
|
usb: chipidea: host: add vbus regulator control
For boards which have board level vbus control (eg, through gpio), we
need to vbus operation according to below rules:
- For host, we need open vbus before start hcd, and close it
after remove hcd.
- For otg, the vbus needs to be on/off when usb role switches.
When the host roles begins, it opens vbus; when the host role
finishes, it closes vbus.
We put vbus operation to host as host is the only vbus user,
When we are at host mode, the vbus is on, when we are not at
host mode, vbus should be off.
Tested-by: Marek Vasut <marex@denx.de>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-08-14 17:44:04 +08:00
|
|
|
|
2018-09-04 23:18:55 +08:00
|
|
|
if (ci->platdata->pins_host)
|
|
|
|
pinctrl_select_state(ci->platdata->pctl,
|
|
|
|
ci->platdata->pins_host);
|
|
|
|
|
2012-05-11 22:25:54 +08:00
|
|
|
ret = usb_add_hcd(hcd, 0, 0);
|
2014-04-23 15:56:46 +08:00
|
|
|
if (ret) {
|
2015-02-11 12:45:02 +08:00
|
|
|
goto disable_reg;
|
2014-04-23 15:56:46 +08:00
|
|
|
} else {
|
2014-10-31 01:41:16 +08:00
|
|
|
struct usb_otg *otg = &ci->otg;
|
2014-04-23 15:56:46 +08:00
|
|
|
|
2012-05-11 22:25:54 +08:00
|
|
|
ci->hcd = hcd;
|
2014-10-31 01:41:16 +08:00
|
|
|
|
|
|
|
if (ci_otg_is_fsm_mode(ci)) {
|
2014-04-23 15:56:46 +08:00
|
|
|
otg->host = &hcd->self;
|
|
|
|
hcd->self.otg_port = 1;
|
|
|
|
}
|
2018-10-15 17:02:57 +08:00
|
|
|
|
|
|
|
if (ci->platdata->notify_event &&
|
|
|
|
(ci->platdata->flags & CI_HDRC_IMX_IS_HSIC))
|
|
|
|
ci->platdata->notify_event
|
|
|
|
(ci, CI_HDRC_IMX_HSIC_ACTIVE_EVENT);
|
2014-04-23 15:56:46 +08:00
|
|
|
}
|
2012-05-11 22:25:54 +08:00
|
|
|
|
|
|
|
return ret;
|
usb: chipidea: host: add vbus regulator control
For boards which have board level vbus control (eg, through gpio), we
need to vbus operation according to below rules:
- For host, we need open vbus before start hcd, and close it
after remove hcd.
- For otg, the vbus needs to be on/off when usb role switches.
When the host roles begins, it opens vbus; when the host role
finishes, it closes vbus.
We put vbus operation to host as host is the only vbus user,
When we are at host mode, the vbus is on, when we are not at
host mode, vbus should be off.
Tested-by: Marek Vasut <marex@denx.de>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-08-14 17:44:04 +08:00
|
|
|
|
2015-02-11 12:45:02 +08:00
|
|
|
disable_reg:
|
|
|
|
if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
|
|
|
|
(ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
|
|
|
|
regulator_disable(ci->platdata->reg_vbus);
|
usb: chipidea: host: add vbus regulator control
For boards which have board level vbus control (eg, through gpio), we
need to vbus operation according to below rules:
- For host, we need open vbus before start hcd, and close it
after remove hcd.
- For otg, the vbus needs to be on/off when usb role switches.
When the host roles begins, it opens vbus; when the host role
finishes, it closes vbus.
We put vbus operation to host as host is the only vbus user,
When we are at host mode, the vbus is on, when we are not at
host mode, vbus should be off.
Tested-by: Marek Vasut <marex@denx.de>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Signed-off-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-08-14 17:44:04 +08:00
|
|
|
put_hcd:
|
|
|
|
usb_put_hcd(hcd);
|
|
|
|
|
|
|
|
return ret;
|
2012-05-11 22:25:54 +08:00
|
|
|
}
|
|
|
|
|
2013-06-24 19:46:36 +08:00
|
|
|
static void host_stop(struct ci_hdrc *ci)
|
2012-05-11 22:25:54 +08:00
|
|
|
{
|
|
|
|
struct usb_hcd *hcd = ci->hcd;
|
|
|
|
|
2013-10-16 20:45:15 +08:00
|
|
|
if (hcd) {
|
2016-12-29 06:56:53 +08:00
|
|
|
if (ci->platdata->notify_event)
|
|
|
|
ci->platdata->notify_event(ci,
|
|
|
|
CI_HDRC_CONTROLLER_STOPPED_EVENT);
|
2013-10-16 20:45:15 +08:00
|
|
|
usb_remove_hcd(hcd);
|
2016-09-10 20:53:21 +08:00
|
|
|
ci->role = CI_ROLE_END;
|
|
|
|
synchronize_irq(ci->irq);
|
2013-10-16 20:45:15 +08:00
|
|
|
usb_put_hcd(hcd);
|
2015-02-11 12:45:02 +08:00
|
|
|
if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
|
|
|
|
(ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
|
|
|
|
regulator_disable(ci->platdata->reg_vbus);
|
2013-10-16 20:45:15 +08:00
|
|
|
}
|
2015-12-15 17:47:47 +08:00
|
|
|
ci->hcd = NULL;
|
|
|
|
ci->otg.host = NULL;
|
2018-09-04 23:18:55 +08:00
|
|
|
|
|
|
|
if (ci->platdata->pins_host && ci->platdata->pins_default)
|
|
|
|
pinctrl_select_state(ci->platdata->pctl,
|
|
|
|
ci->platdata->pins_default);
|
2012-05-11 22:25:54 +08:00
|
|
|
}
|
|
|
|
|
2013-08-14 17:44:07 +08:00
|
|
|
|
|
|
|
void ci_hdrc_host_destroy(struct ci_hdrc *ci)
|
|
|
|
{
|
2013-10-22 11:13:41 +08:00
|
|
|
if (ci->role == CI_ROLE_HOST && ci->hcd)
|
2013-08-14 17:44:07 +08:00
|
|
|
host_stop(ci);
|
|
|
|
}
|
|
|
|
|
2018-10-16 10:11:42 +08:00
|
|
|
/* The below code is based on tegra ehci driver */
|
|
|
|
static int ci_ehci_hub_control(
|
|
|
|
struct usb_hcd *hcd,
|
|
|
|
u16 typeReq,
|
|
|
|
u16 wValue,
|
|
|
|
u16 wIndex,
|
|
|
|
char *buf,
|
|
|
|
u16 wLength
|
|
|
|
)
|
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
2024-06-12 13:13:20 +08:00
|
|
|
unsigned int ports = HCS_N_PORTS(ehci->hcs_params);
|
2018-10-16 10:11:42 +08:00
|
|
|
u32 __iomem *status_reg;
|
2024-06-12 13:13:20 +08:00
|
|
|
u32 temp, port_index;
|
2018-10-16 10:11:42 +08:00
|
|
|
unsigned long flags;
|
|
|
|
int retval = 0;
|
|
|
|
struct device *dev = hcd->self.controller;
|
|
|
|
struct ci_hdrc *ci = dev_get_drvdata(dev);
|
|
|
|
|
2024-06-12 13:13:20 +08:00
|
|
|
port_index = wIndex & 0xff;
|
|
|
|
port_index -= (port_index > 0);
|
|
|
|
status_reg = &ehci->regs->port_status[port_index];
|
2018-10-16 10:11:42 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&ehci->lock, flags);
|
|
|
|
|
|
|
|
if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) {
|
2024-06-12 13:13:20 +08:00
|
|
|
if (!wIndex || wIndex > ports) {
|
|
|
|
retval = -EPIPE;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2018-10-16 10:11:42 +08:00
|
|
|
temp = ehci_readl(ehci, status_reg);
|
|
|
|
if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) {
|
|
|
|
retval = -EPIPE;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
temp &= ~(PORT_RWC_BITS | PORT_WKCONN_E);
|
|
|
|
temp |= PORT_WKDISC_E | PORT_WKOC_E;
|
|
|
|
ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If a transaction is in progress, there may be a delay in
|
|
|
|
* suspending the port. Poll until the port is suspended.
|
|
|
|
*/
|
|
|
|
if (ehci_handshake(ehci, status_reg, PORT_SUSPEND,
|
|
|
|
PORT_SUSPEND, 5000))
|
|
|
|
ehci_err(ehci, "timeout waiting for SUSPEND\n");
|
|
|
|
|
|
|
|
if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) {
|
|
|
|
if (ci->platdata->notify_event)
|
|
|
|
ci->platdata->notify_event(ci,
|
|
|
|
CI_HDRC_IMX_HSIC_SUSPEND_EVENT);
|
|
|
|
|
|
|
|
temp = ehci_readl(ehci, status_reg);
|
|
|
|
temp &= ~(PORT_WKDISC_E | PORT_WKCONN_E);
|
|
|
|
ehci_writel(ehci, temp, status_reg);
|
|
|
|
}
|
|
|
|
|
2024-06-12 13:13:20 +08:00
|
|
|
set_bit(port_index, &ehci->suspended_ports);
|
2018-10-16 10:11:42 +08:00
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* After resume has finished, it needs do some post resume
|
|
|
|
* operation for some SoCs.
|
|
|
|
*/
|
|
|
|
else if (typeReq == ClearPortFeature &&
|
|
|
|
wValue == USB_PORT_FEAT_C_SUSPEND) {
|
|
|
|
/* Make sure the resume has finished, it should be finished */
|
|
|
|
if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 25000))
|
|
|
|
ehci_err(ehci, "timeout waiting for resume\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&ehci->lock, flags);
|
|
|
|
|
|
|
|
/* Handle the hub control events here */
|
|
|
|
return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
|
|
|
|
done:
|
|
|
|
spin_unlock_irqrestore(&ehci->lock, flags);
|
|
|
|
return retval;
|
|
|
|
}
|
2015-02-11 12:44:59 +08:00
|
|
|
static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
|
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
2018-10-15 17:02:57 +08:00
|
|
|
struct device *dev = hcd->self.controller;
|
|
|
|
struct ci_hdrc *ci = dev_get_drvdata(dev);
|
2015-02-11 12:44:59 +08:00
|
|
|
int port;
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
int ret = orig_bus_suspend(hcd);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
u32 __iomem *reg = &ehci->regs->port_status[port];
|
|
|
|
u32 portsc = ehci_readl(ehci, reg);
|
|
|
|
|
|
|
|
if (portsc & PORT_CONNECT) {
|
|
|
|
/*
|
|
|
|
* For chipidea, the resume signal will be ended
|
|
|
|
* automatically, so for remote wakeup case, the
|
|
|
|
* usbcmd.rs may not be set before the resume has
|
|
|
|
* ended if other resume paths consumes too much
|
|
|
|
* time (~24ms), in that case, the SOF will not
|
|
|
|
* send out within 3ms after resume ends, then the
|
|
|
|
* high speed device will enter full speed mode.
|
|
|
|
*/
|
|
|
|
|
|
|
|
tmp = ehci_readl(ehci, &ehci->regs->command);
|
|
|
|
tmp |= CMD_RUN;
|
|
|
|
ehci_writel(ehci, tmp, &ehci->regs->command);
|
|
|
|
/*
|
|
|
|
* It needs a short delay between set RS bit and PHCD.
|
|
|
|
*/
|
|
|
|
usleep_range(150, 200);
|
2018-10-15 17:02:57 +08:00
|
|
|
/*
|
|
|
|
* Need to clear WKCN and WKOC for imx HSIC,
|
|
|
|
* otherwise, there will be wakeup event.
|
|
|
|
*/
|
|
|
|
if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) {
|
|
|
|
tmp = ehci_readl(ehci, reg);
|
|
|
|
tmp &= ~(PORT_WKDISC_E | PORT_WKCONN_E);
|
|
|
|
ehci_writel(ehci, tmp, reg);
|
|
|
|
}
|
|
|
|
|
2015-02-11 12:44:59 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-06-24 19:46:36 +08:00
|
|
|
int ci_hdrc_host_init(struct ci_hdrc *ci)
|
2012-05-11 22:25:54 +08:00
|
|
|
{
|
|
|
|
struct ci_role_driver *rdrv;
|
|
|
|
|
|
|
|
if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_HC))
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL);
|
|
|
|
if (!rdrv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
rdrv->start = host_start;
|
|
|
|
rdrv->stop = host_stop;
|
|
|
|
rdrv->irq = host_irq;
|
|
|
|
rdrv->name = "host";
|
|
|
|
ci->roles[CI_ROLE_HOST] = rdrv;
|
|
|
|
|
usb: chipidea: ehci_init_driver is intended to call one time
The ehci_init_driver is used to initialize hcd APIs for each
ehci controller driver, it is designed to be called only one time
and before driver register is called. The current design will
cause ehci_init_driver is called multiple times at probe process,
it will cause hc_driver's initialization affect current running hcd.
We run out NULL pointer dereference problem when one hcd is started
by module_init, and the other is started by otg thread at SMP platform.
The reason for this problem is ehci_init_driver will do memory copy
for current uniform hc_driver, and this memory copy will do memset (as 0)
first, so when the first hcd is running usb_add_hcd, and the second
hcd may clear the uniform hc_driver's space (at ehci_init_driver),
then the first hcd will meet NULL pointer at the same time.
See below two logs:
LOG_1:
ci_hdrc ci_hdrc.0: EHCI Host Controller
ci_hdrc ci_hdrc.0: new USB bus registered, assigned bus number 1
ci_hdrc ci_hdrc.1: doesn't support gadget
Unable to handle kernel NULL pointer dereference at virtual address 00000014
pgd = 80004000
[00000014] *pgd=00000000
Internal error: Oops: 805 [#1] PREEMPT SMP ARM
Modules linked in:
CPU: 0 PID: 108 Comm: kworker/u8:2 Not tainted 3.14.38-222193-g24b2734-dirty #25
Workqueue: ci_otg ci_otg_work
task: d839ec00 ti: d8400000 task.ti: d8400000
PC is at ehci_run+0x4c/0x284
LR is at _raw_spin_unlock_irqrestore+0x28/0x54
pc : [<8041f9a0>] lr : [<8070ea84>] psr: 60000113
sp : d8401e30 ip : 00000000 fp : d8004400
r10: 00000001 r9 : 00000001 r8 : 00000000
r7 : 00000000 r6 : d8419940 r5 : 80dd24c0 r4 : d8419800
r3 : 8001d060 r2 : 00000000 r1 : 00000001 r0 : 00000000
Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: 10c53c7d Table: 1000404a DAC: 00000015
Process kworker/u8:2 (pid: 108, stack limit = 0xd8400238)
Stack: (0xd8401e30 to 0xd8402000)
1e20: d87523c0 d8401e48 66667562 d8419800
1e40: 00000000 00000000 d8419800 00000000 00000000 00000000 d84198b0 8040fcdc
1e60: 00000000 80dd320c d8477610 d8419c00 d803d010 d8419800 00000000 00000000
1e80: d8004400 00000000 d8400008 80431494 80431374 d803d100 d803d010 d803d1ac
1ea0: 00000000 80432428 804323d4 d803d100 00000001 80435eb8 80e0d0bc d803d100
1ec0: 00000006 80436458 00000000 d803d100 80e92ec8 80436f44 d803d010 d803d100
1ee0: d83fde00 8043292c d8752710 d803d1f4 d803d010 8042ddfc 8042ddb8 d83f3b00
1f00: d803d1f4 80042b60 00000000 00000003 00000001 00000001 80054598 d83f3b00
1f20: d8004400 d83f3b18 d8004414 d8400000 80e3957b 00000089 d8004400 80043814
1f40: d839ec00 00000000 d83fcd80 d83f3b00 800436e4 00000000 00000000 00000000
1f60: 00000000 80048f34 00000000 00000000 00000000 d83f3b00 00000000 00000000
1f80: d8401f80 d8401f80 00000000 00000000 d8401f90 d8401f90 d8401fac d83fcd80
1fa0: 80048e68 00000000 00000000 8000e538 00000000 00000000 00000000 00000000
1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
1fe0: 00000000 00000000 00000000 00000000 00000013 00000000 00000000 00000000
[<8041f9a0>] (ehci_run) from [<8040fcdc>] (usb_add_hcd+0x248/0x6e8)
[<8040fcdc>] (usb_add_hcd) from [<80431494>] (host_start+0x120/0x2e4)
[<80431494>] (host_start) from [<80432428>] (ci_otg_start_host+0x54/0xbc)
[<80432428>] (ci_otg_start_host) from [<80435eb8>] (otg_set_protocol+0xa4/0xd0)
[<80435eb8>] (otg_set_protocol) from [<80436458>] (otg_set_state+0x574/0xc58)
[<80436458>] (otg_set_state) from [<80436f44>] (otg_statemachine+0x408/0x46c)
[<80436f44>] (otg_statemachine) from [<8043292c>] (ci_otg_fsm_work+0x3c/0x190)
[<8043292c>] (ci_otg_fsm_work) from [<8042ddfc>] (ci_otg_work+0x44/0x1c4)
[<8042ddfc>] (ci_otg_work) from [<80042b60>] (process_one_work+0xf4/0x35c)
[<80042b60>] (process_one_work) from [<80043814>] (worker_thread+0x130/0x3bc)
[<80043814>] (worker_thread) from [<80048f34>] (kthread+0xcc/0xe4)
[<80048f34>] (kthread) from [<8000e538>] (ret_from_fork+0x14/0x3c)
Code: e5953018 e3530000 0a000000 e12fff33 (e5878014)
LOG_2:
ci_hdrc ci_hdrc.0: EHCI Host Controller
ci_hdrc ci_hdrc.0: new USB bus registered, assigned bus number 1
ci_hdrc ci_hdrc.1: doesn't support gadget
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = 80004000
[00000000] *pgd=00000000
In Online 00:00ternal e Offline rror: Oops: 80000005 [#1] PREEMPT SMP ARM
Modules linked in:
CPU: 0 PID: 108 Comm: kworker/u8:2 Not tainted 3.14.38-02007-g24b2734-dirty #127
Workque Online 00:00ue: ci_o Offline tg ci_otg_work
Online 00:00task: d8 Offline 39ec00 ti: d83ea000 task.ti: d83ea000
PC is at 0x0
LR is at usb_add_hcd+0x248/0x6e8
pc : [<00000000>] lr : [<8040f644>] psr: 60000113
sp : d83ebe60 ip : 00000000 fp : d8004400
r10: 00000001 r9 : 00000001 r8 : d85fd4b0
r7 : 00000000 r6 : 00000000 r5 : 00000000 r4 : d85fd400
r3 : 00000000 r2 : d85fd4f4 r1 : 80410178 r0 : d85fd400
Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: 10c53c7d Table: 1000404a DAC: 00000015
Process kworker/u8:2 (pid: 108, stack limit = 0xd83ea238)
Stack: (0xd83ebe60 to 0xd83ec000)
be60: 00000000 80dd920c d8654e10 d85fd800 d803e010 d85fd400 00000000 00000000
be80: d8004400 00000000 d83ea008 80430e34 80430d14 d803e100 d803e010 d803e1ac
bea0: 00000000 80431dc8 80431d74 d803e100 00000001 80435858 80e130bc d803e100
bec0: 00000006 80435df8 00000000 d803e100 80e98ec8 804368e4 d803e010 d803e100
bee0: d86e8100 804322cc d86cf050 d803e1f4 d803e010 8042d79c 8042d758 d83cf900
bf00: d803e1f4 80042b78 00000000 00000003 00000001 00000001 800545e8 d83cf900
bf20: d8004400 d83cf918 d8004414 d83ea000 80e3f57b 00000089 d8004400 8004382c
bf40: d839ec00 00000000 d8393780 d83cf900 800436fc 00000000 00000000 00000000
bf60: 00000000 80048f50 80e019f4 00000000 0000264c d83cf900 00000000 00000000
bf80: d83ebf80 d83ebf80 00000000 00000000 d83ebf90 d83ebf90 d83ebfac d8393780
bfa0: 80048e84 00000000 00000000 8000e538 00000000 00000000 00000000 00000000
bfc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
bfe0: 00000000 00000000 00000000 00000000 00000013 00000000 ee66e85d 133ebd03
[<804 Online 00:000f644>] Offline (usb_add_hcd) from [<80430e34>] (host_start+0x120/0x2e4)
[<80430e34>] (host_start) from [<80431dc8>] (ci_otg_start_host+0x54/0xbc)
[<80431dc8>] (ci_otg_start_host) from [<80435858>] (otg_set_protocol+0xa4/0xd0)
[<80435858>] (otg_set_protocol) from [<80435df8>] (otg_set_state+0x574/0xc58)
[<80435df8>] (otg_set_state) from [<804368e4>] (otg_statemachine+0x408/0x46c)
[<804368e4>] (otg_statemachine) from [<804322cc>] (ci_otg_fsm_work+0x3c/0x190)
[<804322cc>] (ci_otg_fsm_work) from [<8042d79c>] (ci_otg_work+0x44/0x1c4)
[<8042d79c>] (ci_otg_work) from [<80042b78>] (process_one_work+0xf4/0x35c)
[<80042b78>] (process_one_work) from [<8004382c>] (worker_thread+0x130/0x3bc)
[<8004382c>] (worker_thread) from [<80048f50>] (kthread+0xcc/0xe4)
[<80048f50>] (kthread) from [<8000e538>] (ret_from_fork+0x14/0x3c)
Code: bad PC value
Cc: Jun Li <jun.li@freescale.com>
Cc: <stable@vger.kernel.org>
Cc: Alan Stern <stern@rowland.harvard.edu>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
2015-07-21 09:51:29 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ci_hdrc_host_driver_init(void)
|
|
|
|
{
|
2014-10-13 09:53:04 +08:00
|
|
|
ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides);
|
2015-02-11 12:44:59 +08:00
|
|
|
orig_bus_suspend = ci_ehci_hc_driver.bus_suspend;
|
|
|
|
ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend;
|
2018-10-16 10:11:42 +08:00
|
|
|
ci_ehci_hc_driver.hub_control = ci_ehci_hub_control;
|
2012-05-11 22:25:54 +08:00
|
|
|
}
|