blob: 9f601cba071b0dee2beb0c5a2044239eff1a29e8 [file] [log] [blame]
/**
* core.c - Cadence USB3 DRD Controller Core file
*
* Copyright 2017 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"
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;
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");
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 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_remove(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;
}
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;
}
/**
* 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;
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_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED,
dev_name(dev), cdns);
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);
pm_runtime_get_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
cdns3_remove_roles(cdns);
usb_phy_shutdown(cdns->usbphy);
cdns3_disable_unprepare_clks(&pdev->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 void 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;
if (cdns->role != CDNS3_ROLE_HOST)
return;
if (suspend) {
value = readl(otg_regs + OTGREFCLK);
value |= OTG_STB_CLK_SWITCH_EN;
writel(value, otg_regs + OTGREFCLK);
value = readl(xhci_regs + XECP_PORT_CAP_REG);
value |= LPM_2_STB_SWITCH_EN;
writel(value, xhci_regs + XECP_PORT_CAP_REG);
if (cdns3_role(cdns)->suspend)
cdns3_role(cdns)->suspend(cdns, wakeup);
/*
* SW should ensure LPM_2_STB_SWITCH_EN and RXDET_IN_P3_32KHZ
* are aligned before setting 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);
/* SW request low power when all usb ports allow to it ??? */
value = readl(xhci_regs + XECP_PM_PMCSR);
value |= PS_D0;
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_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");
}
}
#ifdef CONFIG_PM_SLEEP
static int cdns3_suspend(struct device *dev)
{
struct cdns3 *cdns = dev_get_drvdata(dev);
bool wakeup = device_may_wakeup(dev);
u32 value;
dev_dbg(dev, "at %s\n", __func__);
if (pm_runtime_status_suspended(dev))
pm_runtime_resume(dev);
if (cdns->role == CDNS3_ROLE_HOST)
cdns3_enter_suspend(cdns, true, wakeup);
else if (cdns->role == CDNS3_ROLE_GADGET) {
/* When at device mode, always 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);
}
if (wakeup)
enable_irq_wake(cdns->irq);
usb_phy_set_suspend(cdns->usbphy, 1);
cdns3_disable_unprepare_clks(dev);
cdns->in_lpm = true;
return 0;
}
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 {
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);
dev_dbg(dev, "at the begin of %s\n", __func__);
if (cdns->in_lpm) {
WARN_ON(1);
return 0;
}
cdns3_enter_suspend(cdns, true, true);
usb_phy_set_suspend(cdns->usbphy, 1);
cdns3_disable_unprepare_clks(dev);
cdns->in_lpm = true;
dev_dbg(dev, "at the end of %s\n", __func__);
return 0;
}
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);
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:cdns3");
MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");