| /** |
| * 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; |
| 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 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; |
| |
| 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_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_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; |
| } else if (cdns->role == CDNS3_ROLE_END) { |
| return; |
| } |
| |
| if (suspend) { |
| if (cdns3_role(cdns)->suspend) |
| cdns3_role(cdns)->suspend(cdns, wakeup); |
| |
| /* 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"); |
| } |
| } |
| |
| static void cdns3_controller_suspend(struct cdns3 *cdns, bool wakeup) |
| { |
| disable_irq(cdns->irq); |
| cdns3_enter_suspend(cdns, true, wakeup); |
| usb_phy_set_suspend(cdns->usbphy, 1); |
| cdns->in_lpm = true; |
| enable_irq(cdns->irq); |
| } |
| |
| #ifdef CONFIG_PM_SLEEP |
| static int cdns3_suspend(struct device *dev) |
| { |
| struct cdns3 *cdns = dev_get_drvdata(dev); |
| bool wakeup = device_may_wakeup(dev); |
| |
| dev_dbg(dev, "at %s\n", __func__); |
| |
| if (pm_runtime_status_suspended(dev)) |
| pm_runtime_resume(dev); |
| |
| cdns3_controller_suspend(cdns, wakeup); |
| cdns3_disable_unprepare_clks(dev); |
| if (wakeup) |
| enable_irq_wake(cdns->irq); |
| |
| 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_controller_suspend(cdns, true); |
| cdns3_disable_unprepare_clks(dev); |
| |
| 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:cdns-usb3"); |
| MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver"); |