static int msm_hsphy_remove(struct platform_device *pdev) { struct msm_hsphy *phy = platform_get_drvdata(pdev); if (!phy) return 0; usb_remove_phy(&phy->phy); clk_disable_unprepare(phy->sleep_clk); /* Undo the additional regulator enable */ if (phy->vdda_force_on) msm_hsusb_ldo_enable(phy, 0); msm_hsusb_ldo_enable(phy, 0); regulator_disable(phy->vdd); msm_hsusb_config_vdd(phy, 0); if (!phy->suspended) atomic_dec(&hsphy_active_count); kfree(phy); return 0; }
static int msm_hsphy_probe(struct platform_device *pdev) { struct msm_hsphy *phy; struct device *dev = &pdev->dev; struct resource *res; int ret = 0; phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); if (!phy) { ret = -ENOMEM; goto err_ret; } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); if (!res) { dev_err(dev, "missing memory base resource\n"); ret = -ENODEV; goto err_ret; } phy->base = devm_ioremap_nocache(dev, res->start, resource_size(res)); if (!phy->base) { dev_err(dev, "ioremap failed\n"); ret = -ENODEV; goto err_ret; } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr"); if (res) { phy->tcsr = devm_ioremap_nocache(dev, res->start, resource_size(res)); if (!phy->tcsr) { dev_err(dev, "tcsr ioremap failed\n"); return -ENODEV; } /* switch MUX to let SNPS controller use the primary HSPHY */ writel_relaxed(readl_relaxed(phy->tcsr) | TCSR_USB30_CONTROL, phy->tcsr); } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_csr"); if (res) { phy->csr = devm_ioremap_nocache(dev, res->start, resource_size(res)); if (!phy->csr) { dev_err(dev, "phy_csr ioremap failed\n"); return -ENODEV; } } if (of_get_property(dev->of_node, "qcom,primary-phy", NULL)) { dev_dbg(dev, "secondary HSPHY\n"); phy->phy.flags |= ENABLE_SECONDARY_PHY; } ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", (u32 *) phy->vdd_levels, ARRAY_SIZE(phy->vdd_levels)); if (ret) { dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); goto err_ret; } phy->ext_vbus_id = of_property_read_bool(dev->of_node, "qcom,ext-vbus-id"); phy->phy.dev = dev; phy->vdd = devm_regulator_get(dev, "vdd"); if (IS_ERR(phy->vdd)) { dev_err(dev, "unable to get vdd supply\n"); ret = PTR_ERR(phy->vdd); goto err_ret; } if (of_get_property(dev->of_node, "vddcx-supply", NULL)) { phy->vddcx = devm_regulator_get(dev, "vddcx"); if (IS_ERR(phy->vddcx)) { dev_err(dev, "unable to get vddcx supply\n"); ret = PTR_ERR(phy->vddcx); goto err_ret; } } phy->vdda33 = devm_regulator_get(dev, "vdda33"); if (IS_ERR(phy->vdda33)) { dev_err(dev, "unable to get vdda33 supply\n"); ret = PTR_ERR(phy->vdda33); goto err_ret; } phy->vdda18 = devm_regulator_get(dev, "vdda18"); if (IS_ERR(phy->vdda18)) { dev_err(dev, "unable to get vdda18 supply\n"); ret = PTR_ERR(phy->vdda18); goto err_ret; } ret = msm_hsusb_config_vdd(phy, 1); if (ret) { dev_err(dev, "hsusb vdd_dig configuration failed\n"); goto err_ret; } ret = regulator_enable(phy->vdd); if (ret) { dev_err(dev, "unable to enable the hsusb vdd_dig\n"); goto unconfig_hs_vdd; } if (phy->vddcx) { ret = regulator_enable(phy->vddcx); if (ret) { dev_err(dev, "unable to enable vddcx\n"); goto unconfig_hs_vdd; } } ret = msm_hsusb_ldo_enable(phy, 1); if (ret) { dev_err(dev, "hsusb vreg enable failed\n"); goto disable_hs_vdd; } phy->sleep_clk = devm_clk_get(&pdev->dev, "phy_sleep_clk"); if (IS_ERR(phy->sleep_clk)) { dev_err(&pdev->dev, "failed to get phy_sleep_clk\n"); ret = PTR_ERR(phy->sleep_clk); goto disable_hs_ldo; } clk_prepare_enable(phy->sleep_clk); phy->sleep_clk_reset = of_property_read_bool(dev->of_node, "qcom,sleep-clk-reset"); if (of_property_read_u32(dev->of_node, "qcom,hsphy-init", &phy->hsphy_init_seq)) dev_dbg(dev, "unable to read hsphy init seq\n"); else if (!phy->hsphy_init_seq) dev_warn(dev, "hsphy init seq cannot be 0. Using POR value\n"); if (of_property_read_u32(dev->of_node, "qcom,hsphy-host-init", &phy->hsphy_host_init_seq)) dev_dbg(dev, "unable to read hsphy host init seq\n"); else if (!phy->hsphy_host_init_seq) dev_warn(dev, "hsphy host init seq cannot be 0. Using POR value\n"); if (of_property_read_u32(dev->of_node, "qcom,num-ports", &phy->num_ports)) phy->num_ports = 1; else if (phy->num_ports > 3) { dev_err(dev, " number of ports more that 3 is not supported\n"); goto disable_clk; } phy->set_pllbtune = of_property_read_bool(dev->of_node, "qcom,set-pllbtune"); /* * If this workaround flag is enabled, the HW requires the 1.8 and 3.x * regulators to be kept ON when entering suspend. The easiest way to * do that is to call regulator_enable() an additional time here, * since it will keep the regulators' reference counts nonzero. */ phy->vdda_force_on = of_property_read_bool(dev->of_node, "qcom,vdda-force-on"); if (phy->vdda_force_on) { ret = msm_hsusb_ldo_enable(phy, 1); if (ret) goto disable_clk; } platform_set_drvdata(pdev, phy); if (of_property_read_bool(dev->of_node, "qcom,vbus-valid-override")) phy->phy.flags |= PHY_VBUS_VALID_OVERRIDE; phy->phy.init = msm_hsphy_init; phy->phy.set_suspend = msm_hsphy_set_suspend; phy->phy.notify_connect = msm_hsphy_notify_connect; phy->phy.notify_disconnect = msm_hsphy_notify_disconnect; phy->phy.reset = msm_hsphy_reset; /*FIXME: this conflicts with dwc3_otg */ /*phy->phy.type = USB_PHY_TYPE_USB2; */ phy->phy.set_params = msm_hsphy_set_params; ret = usb_add_phy_dev(&phy->phy); if (ret) goto disable_clk; atomic_inc(&hsphy_active_count); return 0; disable_clk: clk_disable_unprepare(phy->sleep_clk); disable_hs_ldo: msm_hsusb_ldo_enable(phy, 0); disable_hs_vdd: if (phy->vddcx) regulator_disable(phy->vddcx); regulator_disable(phy->vdd); unconfig_hs_vdd: msm_hsusb_config_vdd(phy, 0); err_ret: return ret; }
static int msm_hsphy_set_suspend(struct usb_phy *uphy, int suspend) { struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); bool host = uphy->flags & PHY_HOST_MODE; bool chg_connected = uphy->flags & PHY_CHARGER_CONNECTED; int i, count; if (!!suspend == phy->suspended) { dev_dbg(uphy->dev, "%s\n", suspend ? "already suspended" : "already resumed"); return 0; } if (suspend) { for (i = 0; i < phy->num_ports; i++) { /* Clear interrupt latch register */ writel_relaxed(ALT_INTERRUPT_MASK, phy->base + HS_PHY_IRQ_STAT_REG(i)); /* Enable DP and DM HV interrupts */ if (phy->core_ver >= MSM_CORE_VER_120) msm_usb_write_readback(phy->base, ALT_INTERRUPT_EN_REG(i), (LINESTATE_INTEN | DPINTEN | DMINTEN), (LINESTATE_INTEN | DPINTEN | DMINTEN)); else msm_usb_write_readback(phy->base, ALT_INTERRUPT_EN_REG(i), DPDMHV_INT_MASK, DPDMHV_INT_MASK); if (!host) { /* set the following: * OTGDISABLE0=1 * USB2_SUSPEND_N_SEL=1, USB2_SUSPEND_N=0 */ if (phy->core_ver >= MSM_CORE_VER_120) msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, COMMON_OTGDISABLE0, COMMON_OTGDISABLE0); else msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(i), OTGDISABLE0, OTGDISABLE0); msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(i), (USB2_SUSPEND_N_SEL | USB2_SUSPEND_N), USB2_SUSPEND_N_SEL); } if (!phy->ext_vbus_id) /* Enable PHY-based IDHV and *OTGSESSVLD HV interrupts */ msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(i), (OTGSESSVLDHV_INTEN | IDHV_INTEN), (OTGSESSVLDHV_INTEN | IDHV_INTEN)); } /* Enable PHY retention */ if (!host && !chg_connected) { if (phy->core_ver == MSM_CORE_VER_120 && phy->set_pllbtune) /* * On this particular revision the PLLITUNE[1] * bit acts as the control for the RETENABLEN * PHY signal. */ msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, COMMON_PLLITUNE_1, COMMON_PLLITUNE_1); else if (phy->core_ver >= MSM_CORE_VER_120) msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, COMMON_RETENABLEN, 0); else msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0), RETENABLEN, 0); if (phy->csr) { /* switch PHY control to USB2PHY CSRs */ msm_usb_write_readback(phy->csr, USB2PHY_USB_PHY_CFG0, USB2PHY_OVERRIDE_EN, USB2PHY_OVERRIDE_EN); /* clear suspend_n */ msm_usb_write_readback(phy->csr, USB2PHY_HS_PHY_CTRL2, USB2PHY_SUSPEND_N_SEL | USB2PHY_SUSPEND_N, USB2PHY_SUSPEND_N_SEL); /* enable retention */ msm_usb_write_readback(phy->csr, USB2PHY_HS_PHY_CTRL_COMMON0, USB2PHY_COMMONONN | USB2PHY_RETENABLEN, 0); /* disable internal ref clock buffer */ msm_usb_write_readback(phy->csr, USB2PHY_USB_PHY_REFCLK_CTRL, REFCLK_RXTAP_EN, 0); /* power down PHY */ msm_usb_write_readback(phy->csr, USB2PHY_USB_PHY_PWRDOWN_CTRL, PWRDN_B, 0); } phy->lpm_flags |= PHY_RETENTIONED; } /* can turn off regulators if disconnected in device mode */ if (phy->lpm_flags & PHY_RETENTIONED && !phy->cable_connected) { if (phy->ext_vbus_id) { msm_hsusb_ldo_enable(phy, 0); phy->lpm_flags |= PHY_PWR_COLLAPSED; } msm_hsusb_config_vdd(phy, 0); } count = atomic_dec_return(&hsphy_active_count); if (count < 0) { dev_WARN(uphy->dev, "hsphy_active_count=%d, something wrong?\n", count); atomic_set(&hsphy_active_count, 0); } } else { atomic_inc(&hsphy_active_count); if (phy->lpm_flags & PHY_RETENTIONED && !phy->cable_connected) { msm_hsusb_config_vdd(phy, 1); if (phy->ext_vbus_id) { msm_hsusb_ldo_enable(phy, 1); phy->lpm_flags &= ~PHY_PWR_COLLAPSED; } if (phy->csr) { /* power on PHY */ msm_usb_write_readback(phy->csr, USB2PHY_USB_PHY_PWRDOWN_CTRL, PWRDN_B, PWRDN_B); /* enable internal ref clock buffer */ msm_usb_write_readback(phy->csr, USB2PHY_USB_PHY_REFCLK_CTRL, REFCLK_RXTAP_EN, REFCLK_RXTAP_EN); /* disable retention */ msm_usb_write_readback(phy->csr, USB2PHY_HS_PHY_CTRL_COMMON0, USB2PHY_COMMONONN | USB2PHY_RETENABLEN, USB2PHY_COMMONONN | USB2PHY_RETENABLEN); /* switch suspend_n_sel back to HW */ msm_usb_write_readback(phy->csr, USB2PHY_HS_PHY_CTRL2, USB2PHY_SUSPEND_N_SEL | USB2PHY_SUSPEND_N, 0); msm_usb_write_readback(phy->csr, USB2PHY_USB_PHY_CFG0, USB2PHY_OVERRIDE_EN, 0); } /* Disable PHY retention */ if (phy->core_ver == MSM_CORE_VER_120 && phy->set_pllbtune) msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, COMMON_PLLITUNE_1, 0); else if (phy->core_ver >= MSM_CORE_VER_120) msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, COMMON_RETENABLEN, COMMON_RETENABLEN); else msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(0), RETENABLEN, RETENABLEN); phy->lpm_flags &= ~PHY_RETENTIONED; } if (phy->core_ver >= MSM_CORE_VER_120) { if (phy->set_pllbtune) { msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, FSEL_MASK, 0); } else { msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, FSEL_MASK, FSEL_DEFAULT); } } for (i = 0; i < phy->num_ports; i++) { if (!phy->ext_vbus_id) /* Disable HV interrupts */ msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(i), (OTGSESSVLDHV_INTEN | IDHV_INTEN), 0); /* Clear interrupt latch register */ writel_relaxed(ALT_INTERRUPT_MASK, phy->base + HS_PHY_IRQ_STAT_REG(i)); /* Disable DP and DM HV interrupt */ if (phy->core_ver >= MSM_CORE_VER_120) msm_usb_write_readback(phy->base, ALT_INTERRUPT_EN_REG(i), LINESTATE_INTEN, 0); else msm_usb_write_readback(phy->base, ALT_INTERRUPT_EN_REG(i), DPDMHV_INT_MASK, 0); if (!host) { /* Bring PHY out of suspend */ msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(i), USB2_SUSPEND_N_SEL, 0); if (phy->core_ver >= MSM_CORE_VER_120) msm_usb_write_readback(phy->base, HS_PHY_CTRL_COMMON_REG, COMMON_OTGDISABLE0, 0); else msm_usb_write_readback(phy->base, HS_PHY_CTRL_REG(i), OTGDISABLE0, 0); } } /* * write HSPHY init value to QSCRATCH reg to set HSPHY * parameters like VBUS valid threshold, disconnect valid * threshold, DC voltage level,preempasis and rise/fall time */ dev_dbg(uphy->dev, "%s set params\n", __func__); msm_hsphy_set_params(uphy); } phy->suspended = !!suspend; /* double-NOT coerces to bool value */ return 0; }