1
0
Fork 0
alistair23-linux/drivers/usb/cdns3/core.c

1140 lines
29 KiB
C
Raw Normal View History

/**
* core.c - Cadence USB3 DRD Controller Core file
*
* Copyright 2017-2019 NXP
*
* Authors: Peter Chen <peter.chen@nxp.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 of
* the License as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/usb/of.h>
#include <linux/usb/phy.h>
#include <linux/extcon.h>
#include <linux/pm_runtime.h>
#include "cdns3-nxp-reg-def.h"
#include "core.h"
#include "host-export.h"
#include "gadget-export.h"
/**
* cdns3_handshake - spin reading until handshake completes or fails
* @ptr: address of device controller register to be read
* @mask: bits to look at in result of read
* @done: value of those bits when handshake succeeds
* @usec: timeout in microseconds
*
* Returns negative errno, or zero on success
*
* Success happens when the "mask" bits have the specified value (hardware
* handshake done). There are two failure modes: "usec" have passed (major
* hardware flakeout), or the register reads as all-ones (hardware removed).
*/
int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec)
{
u32 result;
do {
result = readl(ptr);
if (result == ~(u32)0) /* card removed */
return -ENODEV;
result &= mask;
if (result == done)
return 0;
udelay(1);
usec--;
} while (usec > 0);
return -ETIMEDOUT;
}
static void cdns3_usb_phy_init(void __iomem *regs)
{
u32 value;
pr_debug("begin of %s\n", __func__);
writel(0x0830, regs + PHY_PMA_CMN_CTRL1);
writel(0x10, regs + TB_ADDR_CMN_DIAG_HSCLK_SEL);
writel(0x00F0, regs + TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR);
writel(0x0018, regs + TB_ADDR_CMN_PLL0_VCOCAL_ITER_TMR);
writel(0x00D0, regs + TB_ADDR_CMN_PLL0_INTDIV);
writel(0x4aaa, regs + TB_ADDR_CMN_PLL0_FRACDIV);
writel(0x0034, regs + TB_ADDR_CMN_PLL0_HIGH_THR);
writel(0x1ee, regs + TB_ADDR_CMN_PLL0_SS_CTRL1);
writel(0x7F03, regs + TB_ADDR_CMN_PLL0_SS_CTRL2);
writel(0x0020, regs + TB_ADDR_CMN_PLL0_DSM_DIAG);
writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_OVRD);
writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBH_OVRD);
writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBL_OVRD);
writel(0x0007, regs + TB_ADDR_CMN_DIAG_PLL0_V2I_TUNE);
writel(0x0027, regs + TB_ADDR_CMN_DIAG_PLL0_CP_TUNE);
writel(0x0008, regs + TB_ADDR_CMN_DIAG_PLL0_LF_PROG);
writel(0x0022, regs + TB_ADDR_CMN_DIAG_PLL0_TEST_MODE);
writel(0x000a, regs + TB_ADDR_CMN_PSM_CLK_CTRL);
writel(0x139, regs + TB_ADDR_XCVR_DIAG_RX_LANE_CAL_RST_TMR);
writel(0xbefc, regs + TB_ADDR_XCVR_PSM_RCTRL);
writel(0x7799, regs + TB_ADDR_TX_PSC_A0);
writel(0x7798, regs + TB_ADDR_TX_PSC_A1);
writel(0x509b, regs + TB_ADDR_TX_PSC_A2);
writel(0x3, regs + TB_ADDR_TX_DIAG_ECTRL_OVRD);
writel(0x509b, regs + TB_ADDR_TX_PSC_A3);
writel(0x2090, regs + TB_ADDR_TX_PSC_CAL);
writel(0x2090, regs + TB_ADDR_TX_PSC_RDY);
writel(0xA6FD, regs + TB_ADDR_RX_PSC_A0);
writel(0xA6FD, regs + TB_ADDR_RX_PSC_A1);
writel(0xA410, regs + TB_ADDR_RX_PSC_A2);
writel(0x2410, regs + TB_ADDR_RX_PSC_A3);
writel(0x23FF, regs + TB_ADDR_RX_PSC_CAL);
writel(0x2010, regs + TB_ADDR_RX_PSC_RDY);
writel(0x0020, regs + TB_ADDR_TX_TXCC_MGNLS_MULT_000);
writel(0x00ff, regs + TB_ADDR_TX_DIAG_BGREF_PREDRV_DELAY);
writel(0x0002, regs + TB_ADDR_RX_SLC_CU_ITER_TMR);
writel(0x0013, regs + TB_ADDR_RX_SIGDET_HL_FILT_TMR);
writel(0x0000, regs + TB_ADDR_RX_SAMP_DAC_CTRL);
writel(0x1004, regs + TB_ADDR_RX_DIAG_SIGDET_TUNE);
writel(0x4041, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE2);
writel(0x0480, regs + TB_ADDR_RX_DIAG_BS_TM);
writel(0x8006, regs + TB_ADDR_RX_DIAG_DFE_CTRL1);
writel(0x003f, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM4);
writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_E_TRIM0);
writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_IQ_TRIM0);
writel(0x0000, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM6);
writel(0x8000, regs + TB_ADDR_RX_DIAG_RXFE_TM3);
writel(0x0003, regs + TB_ADDR_RX_DIAG_RXFE_TM4);
writel(0x2408, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE);
writel(0x05ca, regs + TB_ADDR_RX_DIAG_DFE_CTRL3);
writel(0x0258, regs + TB_ADDR_RX_DIAG_SC2C_DELAY);
writel(0x1fff, regs + TB_ADDR_RX_REE_VGA_GAIN_NODFE);
writel(0x02c6, regs + TB_ADDR_XCVR_PSM_CAL_TMR);
writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0BYP_TMR);
writel(0x02c6, regs + TB_ADDR_XCVR_PSM_A0IN_TMR);
writel(0x0010, regs + TB_ADDR_XCVR_PSM_A1IN_TMR);
writel(0x0010, regs + TB_ADDR_XCVR_PSM_A2IN_TMR);
writel(0x0010, regs + TB_ADDR_XCVR_PSM_A3IN_TMR);
writel(0x0010, regs + TB_ADDR_XCVR_PSM_A4IN_TMR);
writel(0x0010, regs + TB_ADDR_XCVR_PSM_A5IN_TMR);
writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0OUT_TMR);
writel(0x0002, regs + TB_ADDR_XCVR_PSM_A1OUT_TMR);
writel(0x0002, regs + TB_ADDR_XCVR_PSM_A2OUT_TMR);
writel(0x0002, regs + TB_ADDR_XCVR_PSM_A3OUT_TMR);
writel(0x0002, regs + TB_ADDR_XCVR_PSM_A4OUT_TMR);
writel(0x0002, regs + TB_ADDR_XCVR_PSM_A5OUT_TMR);
/* Change rx detect parameter */
writel(0x960, regs + TB_ADDR_TX_RCVDET_EN_TMR);
writel(0x01e0, regs + TB_ADDR_TX_RCVDET_ST_TMR);
writel(0x0090, regs + TB_ADDR_XCVR_DIAG_LANE_FCM_EN_MGN_TMR);
/* RXDET_IN_P3_32KHZ, Receiver detect slow clock enable */
value = readl(regs + TB_ADDR_TX_RCVDETSC_CTRL);
value |= RXDET_IN_P3_32KHZ;
writel(value, regs + TB_ADDR_TX_RCVDETSC_CTRL);
udelay(10);
pr_debug("end of %s\n", __func__);
}
static void cdns_set_role(struct cdns3 *cdns, enum cdns3_roles role)
{
u32 value;
int timeout_us = 100000;
void __iomem *xhci_regs = cdns->xhci_regs;
if (role == CDNS3_ROLE_END)
return;
/* Wait clk value */
value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
writel(value, cdns->none_core_regs + USB3_SSPHY_STATUS);
udelay(1);
value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
while ((value & 0xf0000000) != 0xf0000000 && timeout_us-- > 0) {
value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS);
dev_dbg(cdns->dev, "clkvld:0x%x\n", value);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait clkvld timeout\n");
/* Set all Reset bits */
value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
value |= ALL_SW_RESET;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
udelay(1);
if (role == CDNS3_ROLE_HOST) {
value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
value = (value & ~MODE_STRAP_MASK) | HOST_MODE | OC_DISABLE;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
value &= ~PHYAHB_SW_RESET;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
mdelay(1);
cdns3_usb_phy_init(cdns->phy_regs);
/* Force B Session Valid as 1 */
writel(0x0060, cdns->phy_regs + 0x380a4);
mdelay(1);
value = readl(cdns->none_core_regs + USB3_INT_REG);
value |= HOST_INT1_EN;
writel(value, cdns->none_core_regs + USB3_INT_REG);
value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
value &= ~ALL_SW_RESET;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
dev_dbg(cdns->dev, "wait xhci_power_on_ready\n");
value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
timeout_us = 100000;
while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) {
value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n");
value = readl(xhci_regs + XECP_PORT_CAP_REG);
value |= LPM_2_STB_SWITCH_EN;
writel(value, xhci_regs + XECP_PORT_CAP_REG);
mdelay(1);
dev_dbg(cdns->dev, "switch to host role successfully\n");
} else { /* gadget mode */
value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
value = (value & ~MODE_STRAP_MASK) | DEV_MODE;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
value &= ~PHYAHB_SW_RESET;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
cdns3_usb_phy_init(cdns->phy_regs);
/* Force B Session Valid as 1 */
writel(0x0060, cdns->phy_regs + 0x380a4);
value = readl(cdns->none_core_regs + USB3_INT_REG);
value |= DEV_INT_EN;
writel(value, cdns->none_core_regs + USB3_INT_REG);
value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
value &= ~ALL_SW_RESET;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
dev_dbg(cdns->dev, "wait gadget_power_on_ready\n");
value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
timeout_us = 100000;
while (!(value & DEV_POWER_ON_READY) && timeout_us-- > 0) {
value = readl(cdns->none_core_regs + USB3_CORE_STATUS);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev,
"wait gadget_power_on_ready timeout\n");
mdelay(1);
dev_dbg(cdns->dev, "switch to gadget role successfully\n");
}
}
static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns)
{
if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) {
if (extcon_get_state(cdns->extcon, EXTCON_USB_HOST))
return CDNS3_ROLE_HOST;
else if (extcon_get_state(cdns->extcon, EXTCON_USB))
return CDNS3_ROLE_GADGET;
else
return CDNS3_ROLE_END;
} else {
return cdns->roles[CDNS3_ROLE_HOST]
? CDNS3_ROLE_HOST
: CDNS3_ROLE_GADGET;
}
}
/**
* cdns3_core_init_role - initialize role of operation
* @cdns: Pointer to cdns3 structure
*
* Returns 0 on success otherwise negative errno
*/
static int cdns3_core_init_role(struct cdns3 *cdns)
{
struct device *dev = cdns->dev;
enum usb_dr_mode dr_mode = usb_get_dr_mode(dev);
cdns->role = CDNS3_ROLE_END;
if (dr_mode == USB_DR_MODE_UNKNOWN)
dr_mode = USB_DR_MODE_OTG;
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
if (cdns3_host_init(cdns))
dev_info(dev, "doesn't support host\n");
}
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
if (cdns3_gadget_init(cdns))
dev_info(dev, "doesn't support gadget\n");
}
if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) {
dev_err(dev, "no supported roles\n");
return -ENODEV;
}
return 0;
}
/**
* cdns3_irq - interrupt handler for cdns3 core device
*
* @irq: irq number for cdns3 core device
* @data: structure of cdns3
*
* Returns IRQ_HANDLED or IRQ_NONE
*/
static irqreturn_t cdns3_irq(int irq, void *data)
{
struct cdns3 *cdns = data;
irqreturn_t ret = IRQ_NONE;
if (cdns->in_lpm) {
disable_irq_nosync(cdns->irq);
cdns->wakeup_int = true;
pm_runtime_get(cdns->dev);
return IRQ_HANDLED;
}
/* Handle device/host interrupt */
if (cdns->role != CDNS3_ROLE_END)
ret = cdns3_role(cdns)->irq(cdns);
return ret;
}
static irqreturn_t cdns3_thread_irq(int irq, void *data)
{
struct cdns3 *cdns = data;
irqreturn_t ret = IRQ_NONE;
/* Handle device/host interrupt */
if (cdns->role != CDNS3_ROLE_END && cdns3_role(cdns)->thread_irq)
ret = cdns3_role(cdns)->thread_irq(cdns);
return ret;
}
static int cdns3_get_clks(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
int ret = 0;
cdns->cdns3_clks[0] = devm_clk_get(dev, "usb3_lpm_clk");
if (IS_ERR(cdns->cdns3_clks[0])) {
ret = PTR_ERR(cdns->cdns3_clks[0]);
dev_err(dev, "Failed to get usb3_lpm_clk, err=%d\n", ret);
return ret;
}
cdns->cdns3_clks[1] = devm_clk_get(dev, "usb3_bus_clk");
if (IS_ERR(cdns->cdns3_clks[1])) {
ret = PTR_ERR(cdns->cdns3_clks[1]);
dev_err(dev, "Failed to get usb3_bus_clk, err=%d\n", ret);
return ret;
}
cdns->cdns3_clks[2] = devm_clk_get(dev, "usb3_aclk");
if (IS_ERR(cdns->cdns3_clks[2])) {
ret = PTR_ERR(cdns->cdns3_clks[2]);
dev_err(dev, "Failed to get usb3_aclk, err=%d\n", ret);
return ret;
}
cdns->cdns3_clks[3] = devm_clk_get(dev, "usb3_ipg_clk");
if (IS_ERR(cdns->cdns3_clks[3])) {
ret = PTR_ERR(cdns->cdns3_clks[3]);
dev_err(dev, "Failed to get usb3_ipg_clk, err=%d\n", ret);
return ret;
}
cdns->cdns3_clks[4] = devm_clk_get(dev, "usb3_core_pclk");
if (IS_ERR(cdns->cdns3_clks[4])) {
ret = PTR_ERR(cdns->cdns3_clks[4]);
dev_err(dev, "Failed to get usb3_core_pclk, err=%d\n", ret);
return ret;
}
return 0;
}
static int cdns3_prepare_enable_clks(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
int i, j, ret = 0;
for (i = 0; i < CDNS3_NUM_OF_CLKS; i++) {
ret = clk_prepare_enable(cdns->cdns3_clks[i]);
if (ret) {
dev_err(dev,
"Failed to prepare/enable cdns3 clk, err=%d\n",
ret);
goto err;
}
}
return ret;
err:
for (j = i; j > 0; j--)
clk_disable_unprepare(cdns->cdns3_clks[j - 1]);
return ret;
}
static void cdns3_disable_unprepare_clks(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
int i;
for (i = CDNS3_NUM_OF_CLKS - 1; i >= 0; i--)
clk_disable_unprepare(cdns->cdns3_clks[i]);
}
static void cdns3_remove_roles(struct cdns3 *cdns)
{
cdns3_gadget_exit(cdns);
cdns3_host_remove(cdns);
}
static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role)
{
int ret = 0;
enum cdns3_roles current_role;
dev_dbg(cdns->dev, "current role is %d, switch to %d\n",
cdns->role, role);
if (cdns->role == role)
return 0;
pm_runtime_get_sync(cdns->dev);
current_role = cdns->role;
cdns3_role_stop(cdns);
if (role == CDNS3_ROLE_END) {
/* Force B Session Valid as 0 */
writel(0x0040, cdns->phy_regs + 0x380a4);
pm_runtime_put_sync(cdns->dev);
return 0;
}
/*
* WORKAROUND: Mass storage gadget calls .ep_disable after
* disconnect with host, wait some time for .ep_disable completion.
*/
msleep(20);
cdns_set_role(cdns, role);
ret = cdns3_role_start(cdns, role);
if (ret) {
/* Back to current role */
dev_err(cdns->dev, "set %d has failed, back to %d\n",
role, current_role);
cdns_set_role(cdns, current_role);
ret = cdns3_role_start(cdns, current_role);
}
pm_runtime_put_sync(cdns->dev);
return ret;
}
/**
* cdns3_role_switch - work queue handler for role switch
*
* @work: work queue item structure
*
* Handles below events:
* - Role switch for dual-role devices
* - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices
*/
static void cdns3_role_switch(struct work_struct *work)
{
struct cdns3 *cdns = container_of(work, struct cdns3,
role_switch_wq);
bool device, host;
host = extcon_get_state(cdns->extcon, EXTCON_USB_HOST);
device = extcon_get_state(cdns->extcon, EXTCON_USB);
if (host) {
if (cdns->roles[CDNS3_ROLE_HOST])
cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST);
return;
}
if (device)
cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET);
else
cdns3_do_role_switch(cdns, CDNS3_ROLE_END);
}
static int cdns3_extcon_notifier(struct notifier_block *nb, unsigned long event,
void *ptr)
{
struct cdns3 *cdns = container_of(nb, struct cdns3, extcon_nb);
queue_work(system_freezable_wq, &cdns->role_switch_wq);
return NOTIFY_DONE;
}
static int cdns3_register_extcon(struct cdns3 *cdns)
{
struct extcon_dev *extcon;
struct device *dev = cdns->dev;
int ret;
if (of_property_read_bool(dev->of_node, "extcon")) {
extcon = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(extcon))
return PTR_ERR(extcon);
ret = devm_extcon_register_notifier(dev, extcon,
EXTCON_USB_HOST, &cdns->extcon_nb);
if (ret < 0) {
dev_err(dev, "register Host Connector failed\n");
return ret;
}
ret = devm_extcon_register_notifier(dev, extcon,
EXTCON_USB, &cdns->extcon_nb);
if (ret < 0) {
dev_err(dev, "register Device Connector failed\n");
return ret;
}
cdns->extcon = extcon;
cdns->extcon_nb.notifier_call = cdns3_extcon_notifier;
}
return 0;
}
static ssize_t cdns3_role_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
if (cdns->role != CDNS3_ROLE_END)
return sprintf(buf, "%s\n", cdns3_role(cdns)->name);
else
return sprintf(buf, "%s\n", "none");
}
static ssize_t cdns3_role_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t n)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
enum cdns3_roles role;
int ret;
if (!(cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET])) {
dev_warn(dev, "Current configuration is not dual-role, quit\n");
return -EPERM;
}
for (role = CDNS3_ROLE_HOST; role <= CDNS3_ROLE_GADGET; role++)
if (!strncmp(buf, cdns->roles[role]->name,
strlen(cdns->roles[role]->name)))
break;
if (role == CDNS3_ROLE_END)
return -EINVAL;
if (role == cdns->role)
return n;
disable_irq(cdns->irq);
ret = cdns3_do_role_switch(cdns, role);
enable_irq(cdns->irq);
return (ret == 0) ? n : ret;
}
static DEVICE_ATTR(role, 0644, cdns3_role_show, cdns3_role_store);
static struct attribute *cdns3_attrs[] = {
&dev_attr_role.attr,
NULL,
};
static const struct attribute_group cdns3_attr_group = {
.attrs = cdns3_attrs,
};
/**
* cdns3_probe - probe for cdns3 core device
* @pdev: Pointer to cdns3 core platform device
*
* Returns 0 on success otherwise negative errno
*/
static int cdns3_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct cdns3 *cdns;
void __iomem *regs;
int ret;
cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
if (!cdns)
return -ENOMEM;
cdns->dev = dev;
platform_set_drvdata(pdev, cdns);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(dev, "missing IRQ\n");
return -ENODEV;
}
cdns->irq = res->start;
/*
* Request memory region
* region-0: nxp wrap registers
* region-1: xHCI
* region-2: Peripheral
* region-3: PHY registers
* region-4: OTG registers
*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
cdns->none_core_regs = regs;
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
cdns->xhci_regs = regs;
cdns->xhci_res = res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
cdns->dev_regs = regs;
res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
cdns->phy_regs = regs;
res = platform_get_resource(pdev, IORESOURCE_MEM, 4);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
cdns->otg_regs = regs;
MLK-18817 usb: cdns3: gadget: add mutex for role_start and role_stop The usb_add_hcd and usb_remove_hcd can't be called together, otherwise, there is oops like below. To fix it, we add mutex to avoid calling role_start and role_stop at the same time. [3.336786] Can't support > 32 bit dma. [3.340676] xhci-cdns3: xHCI Host Controller [3.345052] xhci-cdns3: new USB bus registered, assigned bus number 1 [3.363462] xhci-cdns3: hcc params 0x200073c8 hci version 0x100 quirks 0x60010010 [3.374555] hub 1-0:1.0: USB hub found [3.378349] hub 1-0:1.0: 1 port detected [3.382573] xhci-cdns3: xHCI Host Controller [3.386948] xhci-cdns3: new USB bus registered, assigned bus number 2 [3.387707] cdns-usb3 5b110000.cdns3: current role is 0, switch to 1 [3.387711] xhci-cdns3: remove, state 0 [3.387721] Unable to handle kernel NULL pointer dereference at virtual address 000000d8 [3.387723] pgd = ffff0000095f5000 [3.387731] [000000d8] *pgd=00000008bfffe003, *pud=00000008bfffd003, *pmd=0000000000000000 [3.387735] Internal error: Oops: 96000004 [#1] PREEMPT SMP [3.387739] Modules linked in: [3.387746] CPU: 0 PID: 745 Comm: kworker/0:1 Not tainted 4.9.88-05224-geb65e0981da1 #2087 [3.387748] Hardware name: Freescale i.MX8QXP MEK (DT) [3.387765] Workqueue: events_freezable cdns3_role_switch [3.387768] task: ffff80083a1d7080 task.stack: ffff80083aa5c000 [3.387775] PC is at sysfs_remove_group+0x18/0x98 [3.387781] LR is at usb_remove_hcd+0x58/0x200 [3.387784] pc : [<ffff000008265688>] lr : [<ffff00000885ac28>] pstate: 80000145 [3.387786] sp : ffff80083aa5fc70 [3.387791] x29: ffff80083aa5fc70 x28: 0000000000000000 [3.387795] x27: 0000000000000000 x26: ffff0000093cb2a8 [3.387800] x25: ffff000009506191 x24: ffff80083ff37280 [3.387804] x23: ffff80083b1b0138 x22: ffff80083b238000 [3.387809] x21: ffff80083b238000 x20: ffff0000094a1610 [3.387813] x19: ffff80083b1b0000 x18: 0000000000000020 [3.387817] x17: 0000000000000001 x16: 0000000000000019 [3.387822] x15: ffffffffffffffff x14: 00000000fffffff0 [3.387827] x13: ffff0000095a2068 x12: ffff0000093ce600 [3.387831] x11: ffff0000093ce000 x10: ffff00000959f000 [3.387836] x9 : 0000000000000000 x8 : ffff80083fe4244c [3.387840] x7 : 0000000000000000 x6 : 0000000007cb5702 [3.387844] x5 : 00ffffffffffffff x4 : 0000000000000000 [3.387848] x3 : 0000000000000140 x2 : 00000000000008a6 [3.387852] x1 : 0000000000000000 x0 : 00000000000000a8 [3.387853] [3.387856] Process kworker/0:1 (pid: 745, stack limit = 0xffff80083aa5c020) [3.387860] Stack: (0xffff80083aa5fc70 to 0xffff80083aa60000) [3.387865] fc60: ffff80083aa5fca0 ffff00000885ac28 [3.387870] fc80: ffff80083b1b0000 ffff0000094a1000 ffff80083b238000 0000000000000000 [3.387875] fca0: ffff80083aa5fcf0 ffff00000889c5c8 ffff80083b1bf400 ffff80083b23e018 [3.387880] fcc0: ffff80083b238000 ffff80083b238000 ffff80083ff3bb00 ffff80083a1d7080 [3.387885] fce0: ffff000008ccc25c 0000000000000000 ffff80083aa5fd20 ffff000008897130 [3.387890] fd00: ffff80083b23e018 0000000000000001 0000000000000000 0000000000000000 [3.387895] fd20: ffff80083aa5fd50 ffff0000088972ec ffff80083b23e0d0 0000000000000000 [3.387901] fd40: ffff80083b23e018 ffff000008ccc25c ffff80083aa5fd80 ffff0000080dbc38 [3.387906] fd60: 0000000000000000 ffff80083ac3a600 ffff80083b23e0d0 ffff0000093b6000 [3.387911] fd80: ffff80083aa5fdc0 ffff0000080dbe40 ffff80083ac3a600 ffff80083ff37280 [3.387916] fda0: ffff80083ff37280 ffff80083ac3a630 ffff80083ff372a0 ffff0000093b6000 [3.387921] fdc0: ffff80083aa5fe20 ffff0000080e1c78 ffff80083a0fee80 ffff80083aa5c000 [3.387926] fde0: ffff00000908e2c8 ffff80083ac3a600 ffff0000080dbdf0 0000000000000000 [3.387931] fe00: 0000000000000000 0000000000000000 0000000000000000 ffff80083ac3a600 [3.387936] fe20: 0000000000000000 ffff000008083820 ffff0000080e1b98 ffff80083a0fee80 [3.387941] fe40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387946] fe60: ffff80083aa5fea0 0000000000000000 ffff0000080e1b98 ffff80083ac3a600 [3.387951] fe80: 0000000000000000 0000000000000000 ffff80083aa5fe90 ffff80083aa5fe90 [3.387956] fea0: 0000000000000000 ffff000000000000 ffff80083aa5feb0 ffff80083aa5feb0 [3.387960] fec0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387965] fee0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387970] ff00: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387974] ff20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387979] ff40: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387983] ff60: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387988] ff80: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387992] ffa0: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 [3.387997] ffc0: 0000000000000000 0000000000000005 0000000000000000 0000000000000000 [3.388002] ffe0: 0000000000000000 0000000000000000 fefffac7ef9fea6e f6ef6c26eeee7272 [3.388004] Call trace: [3.388008] Exception stack(0xffff80083aa5faa0 to 0xffff80083aa5fbd0) [3.388013] faa0: ffff80083b1b0000 0000ffffffffffff ffff80083aa5fc70 ffff000008265688 [3.388019] fac0: ffff80083b238000 ffff80083b238000 ffff80083b1b0138 ffff80083ff37280 [3.388024] fae0: ffff000009506191 ffff0000093cb2a8 0000000000000000 ffff80083aa5fcb0 [3.388029] fb00: ffff80083aa5fc20 ffff80083aa5fc20 ffff80083aa5fbf0 00000000ffffffd8 [3.388034] fb20: ffff000009506191 ffff0000093cb2a8 0000000000000000 ffff80083a11f408 [3.388038] fb40: 00000000000000a8 0000000000000000 00000000000008a6 0000000000000140 [3.388043] fb60: 0000000000000000 00ffffffffffffff 0000000007cb5702 0000000000000000 [3.388048] fb80: ffff80083fe4244c 0000000000000000 ffff00000959f000 ffff0000093ce000 [3.388054] fba0: ffff0000093ce600 ffff0000095a2068 00000000fffffff0 ffffffffffffffff [3.388057] fbc0: 0000000000000019 0000000000000001 [3.388061] [<ffff000008265688>] sysfs_remove_group+0x18/0x98 [3.388066] [<ffff00000885ac28>] usb_remove_hcd+0x58/0x200 [3.388072] [<ffff00000889c5c8>] cdns3_host_stop+0x38/0xa0 [3.388077] [<ffff000008897130>] cdns3_do_role_switch+0x58/0x188 [3.388082] [<ffff0000088972ec>] cdns3_role_switch+0x8c/0xa0 [3.388089] [<ffff0000080dbc38>] process_one_work+0x1c8/0x380 [3.388094] [<ffff0000080dbe40>] worker_thread+0x50/0x4c0 [3.388099] [<ffff0000080e1c78>] kthread+0xe0/0xf8 [3.388104] [<ffff000008083820>] ret_from_fork+0x10/0x30 Signed-off-by: Peter Chen <peter.chen@nxp.com>
2018-07-10 02:23:20 -06:00
mutex_init(&cdns->mutex);
ret = cdns3_get_clks(dev);
if (ret)
return ret;
ret = cdns3_prepare_enable_clks(dev);
if (ret)
return ret;
cdns->usbphy = devm_usb_get_phy_by_phandle(dev, "cdns3,usbphy", 0);
if (IS_ERR(cdns->usbphy)) {
ret = PTR_ERR(cdns->usbphy);
if (ret == -ENODEV)
ret = -EINVAL;
goto err1;
}
ret = usb_phy_init(cdns->usbphy);
if (ret)
goto err1;
ret = cdns3_core_init_role(cdns);
if (ret)
goto err2;
if (cdns->roles[CDNS3_ROLE_GADGET]) {
INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch);
ret = cdns3_register_extcon(cdns);
if (ret)
goto err3;
}
cdns->role = cdns3_get_role(cdns);
dev_dbg(dev, "the init role is %d\n", cdns->role);
cdns_set_role(cdns, cdns->role);
ret = cdns3_role_start(cdns, cdns->role);
if (ret) {
dev_err(dev, "can't start %s role\n",
cdns3_role(cdns)->name);
goto err3;
}
ret = devm_request_threaded_irq(dev, cdns->irq, cdns3_irq,
cdns3_thread_irq, IRQF_SHARED, dev_name(dev), cdns);
if (ret)
goto err4;
ret = sysfs_create_group(&dev->kobj, &cdns3_attr_group);
if (ret)
goto err4;
device_set_wakeup_capable(dev, true);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
/*
* The controller needs less time between bus and controller suspend,
* and we also needs a small delay to avoid frequently entering low
* power mode.
*/
pm_runtime_set_autosuspend_delay(dev, 20);
pm_runtime_mark_last_busy(dev);
pm_runtime_use_autosuspend(dev);
dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
return 0;
err4:
cdns3_role_stop(cdns);
err3:
cdns3_remove_roles(cdns);
err2:
usb_phy_shutdown(cdns->usbphy);
err1:
cdns3_disable_unprepare_clks(dev);
return ret;
}
/**
* cdns3_remove - unbind our drd driver and clean up
* @pdev: Pointer to Linux platform device
*
* Returns 0 on success otherwise negative errno
*/
static int cdns3_remove(struct platform_device *pdev)
{
struct cdns3 *cdns = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
pm_runtime_get_sync(dev);
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
sysfs_remove_group(&dev->kobj, &cdns3_attr_group);
cdns3_remove_roles(cdns);
usb_phy_shutdown(cdns->usbphy);
cdns3_disable_unprepare_clks(dev);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id of_cdns3_match[] = {
{ .compatible = "Cadence,usb3" },
{ },
};
MODULE_DEVICE_TABLE(of, of_cdns3_match);
#endif
#ifdef CONFIG_PM
static inline bool controller_power_is_lost(struct cdns3 *cdns)
{
u32 value;
value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
if ((value & SW_RESET_MASK) == ALL_SW_RESET)
return true;
else
return false;
}
static void cdns3_set_wakeup(void *none_core_regs, bool enable)
{
u32 value;
if (enable) {
/* Enable wakeup and phy_refclk_req */
value = readl(none_core_regs + USB3_INT_REG);
value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN;
writel(value, none_core_regs + USB3_INT_REG);
} else {
/* disable wakeup and phy_refclk_req */
value = readl(none_core_regs + USB3_INT_REG);
value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN);
writel(value, none_core_regs + USB3_INT_REG);
}
}
static int cdns3_enter_suspend(struct cdns3 *cdns, bool suspend, bool wakeup)
{
void __iomem *otg_regs = cdns->otg_regs;
void __iomem *xhci_regs = cdns->xhci_regs;
void __iomem *none_core_regs = cdns->none_core_regs;
u32 value;
int timeout_us = 100000;
int ret = 0;
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
if (cdns->role == CDNS3_ROLE_GADGET) {
if (suspend) {
/* When at device mode, set controller at reset mode */
value = readl(cdns->none_core_regs + USB3_CORE_CTRL1);
value |= ALL_SW_RESET;
writel(value, cdns->none_core_regs + USB3_CORE_CTRL1);
}
return 0;
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
} else if (cdns->role == CDNS3_ROLE_END) {
return 0;
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
}
if (suspend) {
if (cdns3_role(cdns)->suspend)
ret = cdns3_role(cdns)->suspend(cdns, wakeup);
if (ret)
return ret;
/* SW request low power when all usb ports allow to it ??? */
value = readl(xhci_regs + XECP_PM_PMCSR);
value &= ~PS_MASK;
value |= PS_D1;
writel(value, xhci_regs + XECP_PM_PMCSR);
/* mdctrl_clk_sel */
value = readl(none_core_regs + USB3_CORE_CTRL1);
value |= MDCTRL_CLK_SEL;
writel(value, none_core_regs + USB3_CORE_CTRL1);
/* wait for mdctrl_clk_status */
value = readl(none_core_regs + USB3_CORE_STATUS);
while (!(value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) {
value = readl(none_core_regs + USB3_CORE_STATUS);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n");
dev_dbg(cdns->dev, "mdctrl_clk_status is set\n");
/* wait lpm_clk_req to be 0 */
value = readl(none_core_regs + USB3_INT_REG);
timeout_us = 100000;
while ((value & LPM_CLK_REQ) && timeout_us-- > 0) {
value = readl(none_core_regs + USB3_INT_REG);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait lpm_clk_req timeout\n");
dev_dbg(cdns->dev, "lpm_clk_req cleared\n");
/* wait phy_refclk_req to be 0 */
value = readl(none_core_regs + USB3_SSPHY_STATUS);
timeout_us = 100000;
while ((value & PHY_REFCLK_REQ) && timeout_us-- > 0) {
value = readl(none_core_regs + USB3_SSPHY_STATUS);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait phy_refclk_req timeout\n");
dev_dbg(cdns->dev, "phy_refclk_req cleared\n");
cdns3_set_wakeup(none_core_regs, true);
} else {
value = readl(none_core_regs + USB3_INT_REG);
/* wait CLK_125_REQ to be 1 */
value = readl(none_core_regs + USB3_INT_REG);
while (!(value & CLK_125_REQ) && timeout_us-- > 0) {
value = readl(none_core_regs + USB3_INT_REG);
udelay(1);
}
cdns3_set_wakeup(none_core_regs, false);
/* SW request D0 */
value = readl(xhci_regs + XECP_PM_PMCSR);
value &= ~PS_MASK;
value |= PS_D0;
writel(value, xhci_regs + XECP_PM_PMCSR);
/* clr CFG_RXDET_P3_EN */
value = readl(xhci_regs + XECP_AUX_CTRL_REG1);
value &= ~CFG_RXDET_P3_EN;
writel(value, xhci_regs + XECP_AUX_CTRL_REG1);
/* clear mdctrl_clk_sel */
value = readl(none_core_regs + USB3_CORE_CTRL1);
value &= ~MDCTRL_CLK_SEL;
writel(value, none_core_regs + USB3_CORE_CTRL1);
/* wait for mdctrl_clk_status is cleared */
value = readl(none_core_regs + USB3_CORE_STATUS);
timeout_us = 100000;
while ((value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) {
value = readl(none_core_regs + USB3_CORE_STATUS);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n");
dev_dbg(cdns->dev, "mdctrl_clk_status cleared\n");
/* Wait until OTG_NRDY is 0 */
value = readl(otg_regs + OTGSTS);
timeout_us = 100000;
while ((value & OTG_NRDY) && timeout_us-- > 0) {
value = readl(otg_regs + OTGSTS);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait OTG ready timeout\n");
value = readl(none_core_regs + USB3_CORE_STATUS);
timeout_us = 100000;
while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) {
value = readl(none_core_regs + USB3_CORE_STATUS);
udelay(1);
}
if (timeout_us <= 0)
dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n");
}
return ret;
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
}
static int cdns3_controller_suspend(struct cdns3 *cdns, bool wakeup)
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
{
int ret = 0;
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
disable_irq(cdns->irq);
ret = cdns3_enter_suspend(cdns, true, wakeup);
if (ret) {
enable_irq(cdns->irq);
return ret;
}
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
usb_phy_set_suspend(cdns->usbphy, 1);
cdns->in_lpm = true;
MLK-17323 usb: cdns3: fix the kernel dump during the reboot stress test During the reboot stress test, there may "nobody cared irq" for CDNS3, the interrupts are occurred during the controller is entering low power mode. In fact, we do no expect interrupt during this period, so disable controller interrupt for it. With this patch, the kernel dump does not occur again during reboot stress test. [ 18.460516] irq 44: nobody cared (try booting with the "irqpoll" option) [ 18.467222] CPU: 0 PID: 112 Comm: kworker/0:1 Not tainted 4.9.51-04150-ge84b58d-dirty #1198 [ 18.475573] Hardware name: Freescale i.MX8QXP MEK (DT) [ 18.480720] Workqueue: pm pm_runtime_work [ 18.484739] Call trace: [ 18.487186] [<ffff0000080884e0>] dump_backtrace+0x0/0x1e0 [ 18.492588] [<ffff0000080886d4>] show_stack+0x14/0x20 [ 18.497644] [<ffff0000083e571c>] dump_stack+0x94/0xb8 [ 18.502701] [<ffff0000081062c0>] __report_bad_irq+0x38/0xe8 [ 18.508276] [<ffff000008106644>] note_interrupt+0x20c/0x2e0 [ 18.513853] [<ffff00000810386c>] handle_irq_event_percpu+0x44/0x58 [ 18.520038] [<ffff0000081038c8>] handle_irq_event+0x48/0x78 [ 18.525608] [<ffff000008107260>] handle_fasteoi_irq+0xb8/0x1b0 [ 18.531445] [<ffff000008102894>] generic_handle_irq+0x24/0x38 [ 18.537187] [<ffff000008102f04>] __handle_domain_irq+0x5c/0xb8 [ 18.543017] [<ffff00000808163c>] gic_handle_irq+0xbc/0x168 [ 18.548506] Exception stack(0xffff80083ff4ae00 to 0xffff80083ff4af30) [ 18.554946] ae00: ffff80083ff4ae30 0001000000000000 ffff80083ff4af60 ffff0000080c1fec [ 18.562781] ae20: 0000000040000145 ffff80083a474000 0000000000000000 0000000000000000 [ 18.570617] ae40: 00000000fffeecc3 ffff000008a2d368 00000000020c49ba 0000000000000020 [ 18.578454] ae60: 000000000f78257c 7fffffffffffffff 0000000000000020 ffff80083a403f00 [ 18.586290] ae80: ffff80083ff51ab0 ffff000008cb0b68 00000000000003ef 0000000000000000 [ 18.594125] aea0: 0000000000000011 00000000000003ef 0000000000000019 0000000000000001 [ 18.601962] aec0: 0000000000000007 ffff0000092f9000 ffff0000092f5b08 0000000000000000 [ 18.609798] aee0: ffff000009501180 ffff80083a002a80 ffff80083a474000 00000000fffeecc2 [ 18.617634] af00: ffff80083ff4b090 ffff000009336000 0000000000000002 ffff80083ff4af60 [ 18.625469] af20: ffff0000080c243c ffff80083ff4af60 [ 18.630343] [<ffff0000080827b0>] el1_irq+0xb0/0x124 [ 18.635220] [<ffff0000080c243c>] irq_exit+0xac/0xf0 [ 18.640099] [<ffff000008102f08>] __handle_domain_irq+0x60/0xb8 [ 18.645928] [<ffff00000808163c>] gic_handle_irq+0xbc/0x168 [ 18.651409] Exception stack(0xffff80083a477ad0 to 0xffff80083a477c00) Acked-by: Jun Li <jun.li@nxp.com> Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 006891a403f6925cb03e1d1e7c7bc029d0217b20)
2018-01-14 19:16:03 -07:00
enable_irq(cdns->irq);
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int cdns3_suspend(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
bool wakeup = device_may_wakeup(dev);
int ret;
dev_dbg(dev, "at %s\n", __func__);
if (pm_runtime_status_suspended(dev))
pm_runtime_resume(dev);
ret = cdns3_controller_suspend(cdns, wakeup);
if (ret)
return ret;
MLK-21073 usb: cdns3: core: fix the oops when the interrupt occurs during suspend When the interrupt occurs during the USB is entering suspend, the cdns->lpm flag may not be updated well, the below oops may occur. We treat above interrupt as wakeup interrupt, it should be handled after lpm flag is set. irq 120: nobody cared (try booting with the "irqpoll" option) CPU: 0 PID: 107 Comm: kworker/0:1 Tainted: G O 4.14.78 #1 Hardware name: Freescale i.MX8QM MEK (DT) Workqueue: pm pm_runtime_work Call trace: [<ffff000008083230>] el1_irq+0xb0/0x124 [<ffff000009028fcc>] _raw_spin_unlock_irqrestore+0x18/0x48 [<ffff000008147a6c>] __irq_put_desc_unlock+0x1c/0x44 [<ffff000008149e4c>] enable_irq+0x54/0x90 [<ffff0000089cb08c>] cdns3_enter_suspend+0x30c/0x3ac [<ffff0000089cb274>] cdns3_runtime_suspend+0x40/0x78 [<ffff000008796cd8>] pm_generic_runtime_suspend+0x28/0x48 [<ffff0000087a7400>] genpd_runtime_suspend+0x90/0x21c [<ffff00000879a14c>] __rpm_callback+0x130/0x264 [<ffff00000879a2a4>] rpm_callback+0x24/0x78 [<ffff000008798ec8>] rpm_suspend+0x10c/0x668 [<ffff0000087996b4>] rpm_idle+0x1c0/0x390 [<ffff00000879aa6c>] pm_runtime_work+0x94/0xe0 [<ffff0000080fac88>] process_one_work+0x140/0x3f8 [<ffff0000080fb078>] worker_thread+0x138/0x3e4 [<ffff0000081014e0>] kthread+0x104/0x130 [<ffff00000808552c>] ret_from_fork+0x10/0x18 Signed-off-by: Peter Chen <peter.chen@nxp.com> (cherry picked from commit 1befe1d04e4efa9853059c8bfcc56d4c3cdb0940) (cherry picked from commit 35574d63d913b19781759c578f447b1893cdb8a9)
2019-02-22 02:12:52 -07:00
cdns3_disable_unprepare_clks(dev);
if (wakeup)
enable_irq_wake(cdns->irq);
return ret;
}
static int cdns3_resume(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
int ret;
bool power_lost;
dev_dbg(dev, "at %s\n", __func__);
if (!cdns->in_lpm) {
WARN_ON(1);
return 0;
}
ret = cdns3_prepare_enable_clks(dev);
if (ret)
return ret;
usb_phy_set_suspend(cdns->usbphy, 0);
cdns->in_lpm = false;
if (device_may_wakeup(dev))
disable_irq_wake(cdns->irq);
power_lost = controller_power_is_lost(cdns);
if (power_lost) {
dev_dbg(dev, "power is lost, the role is %d\n", cdns->role);
cdns_set_role(cdns, cdns->role);
if ((cdns->role != CDNS3_ROLE_END)
&& cdns3_role(cdns)->resume) {
/* Force B Session Valid as 1 */
writel(0x0060, cdns->phy_regs + 0x380a4);
cdns3_role(cdns)->resume(cdns, true);
}
} else {
/* At resume path, never return error */
cdns3_enter_suspend(cdns, false, false);
if (cdns->wakeup_int) {
cdns->wakeup_int = false;
pm_runtime_mark_last_busy(cdns->dev);
pm_runtime_put_autosuspend(cdns->dev);
enable_irq(cdns->irq);
}
if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume)
cdns3_role(cdns)->resume(cdns, false);
}
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
if (cdns->role == CDNS3_ROLE_HOST) {
/*
* There is no PM APIs for cdns->host_dev, we can only do
* it at its parent PM APIs
*/
pm_runtime_disable(cdns->host_dev);
pm_runtime_set_active(cdns->host_dev);
pm_runtime_enable(cdns->host_dev);
}
dev_dbg(dev, "at end of %s\n", __func__);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
static int cdns3_runtime_suspend(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
int ret;
dev_dbg(dev, "at the begin of %s\n", __func__);
if (cdns->in_lpm) {
WARN_ON(1);
return 0;
}
ret = cdns3_controller_suspend(cdns, true);
if (ret)
return ret;
cdns3_disable_unprepare_clks(dev);
dev_dbg(dev, "at the end of %s\n", __func__);
return ret;
}
static int cdns3_runtime_resume(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
int ret;
if (!cdns->in_lpm) {
WARN_ON(1);
return 0;
}
ret = cdns3_prepare_enable_clks(dev);
if (ret)
return ret;
usb_phy_set_suspend(cdns->usbphy, 0);
/* At resume path, never return error */
cdns3_enter_suspend(cdns, false, false);
cdns->in_lpm = 0;
if (cdns->role == CDNS3_ROLE_HOST) {
if (cdns->wakeup_int) {
cdns->wakeup_int = false;
pm_runtime_mark_last_busy(cdns->dev);
pm_runtime_put_autosuspend(cdns->dev);
enable_irq(cdns->irq);
}
if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume)
cdns3_role(cdns)->resume(cdns, false);
}
dev_dbg(dev, "at %s\n", __func__);
return 0;
}
#endif /* CONFIG_PM */
static const struct dev_pm_ops cdns3_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume)
SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL)
};
static struct platform_driver cdns3_driver = {
.probe = cdns3_probe,
.remove = cdns3_remove,
.driver = {
.name = "cdns-usb3",
.of_match_table = of_match_ptr(of_cdns3_match),
.pm = &cdns3_pm_ops,
},
};
static int __init cdns3_driver_platform_register(void)
{
cdns3_host_driver_init();
return platform_driver_register(&cdns3_driver);
}
module_init(cdns3_driver_platform_register);
static void __exit cdns3_driver_platform_unregister(void)
{
platform_driver_unregister(&cdns3_driver);
}
module_exit(cdns3_driver_platform_unregister);
MODULE_ALIAS("platform:cdns-usb3");
MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");