/** * core.c - Cadence USB3 DRD Controller Core file * * Copyright 2017-2019 NXP * * Authors: Peter Chen * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #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; } 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; 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; 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; } else if (cdns->role == CDNS3_ROLE_END) { return 0; } 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; } static int cdns3_controller_suspend(struct cdns3 *cdns, bool wakeup) { int ret = 0; disable_irq(cdns->irq); ret = cdns3_enter_suspend(cdns, true, wakeup); if (ret) { enable_irq(cdns->irq); return ret; } usb_phy_set_suspend(cdns->usbphy, 1); cdns->in_lpm = true; 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; 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 "); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");