/* Do disconnection between PHY and controller without vbus */ #define MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS BIT(0)
/* * The PHY will be in messy if there is a wakeup after putting * bus to suspend (set portsc.suspendM) but before setting PHY to low * power mode (set portsc.phcd).
*/ #define MXS_PHY_ABNORMAL_IN_SUSPEND BIT(1)
/* * The SOF sends too fast after resuming, it will cause disconnection * between host and high speed device.
*/ #define MXS_PHY_SENDING_SOF_TOO_FAST BIT(2)
/* * IC has bug fixes logic, they include * MXS_PHY_ABNORMAL_IN_SUSPEND and MXS_PHY_SENDING_SOF_TOO_FAST * which are described at above flags, the RTL will handle it * according to different versions.
*/ #define MXS_PHY_NEED_IP_FIX BIT(3)
/* Minimum and maximum values for device tree entries */ #define MXS_PHY_TX_CAL45_MIN 35 #define MXS_PHY_TX_CAL45_MAX 54 #define MXS_PHY_TX_D_CAL_MIN 79 #define MXS_PHY_TX_D_CAL_MAX 119
/* * At imx6q/6sl/6sx, the PHY2's clock is controlled by hardware directly, * eg, according to PHY's suspend status. In these PHYs, we only need to * open the clock at the initialization and close it at its shutdown routine. * These PHYs can send resume signal without software interfere if not * gate clock.
*/ #define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4)
/* * PHY needs some 32K cycles to switch from 32K clock to * bus (such as AHB/AXI, etc) clock.
*/ staticvoid mxs_phy_clock_switch_delay(void)
{
usleep_range(300, 400);
}
/* Update TX register if there is anything to write */ if (mxs_phy->tx_reg_mask) {
phytx = readl(base + HW_USBPHY_TX);
phytx &= ~mxs_phy->tx_reg_mask;
phytx |= mxs_phy->tx_reg_set;
writel(phytx, base + HW_USBPHY_TX);
}
}
staticint mxs_phy_pll_enable(void __iomem *base, bool enable)
{ int ret = 0;
if (enable) {
u32 value;
writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_SET);
writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_CLR);
writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_SET);
ret = readl_poll_timeout(base + HW_USBPHY_PLL_SIC,
value, (value & BM_USBPHY_PLL_LOCK) != 0,
100, 10000); if (ret) return ret;
writel(BM_USBPHY_PLL_EN_USB_CLKS, base +
HW_USBPHY_PLL_SIC_SET);
} else {
writel(BM_USBPHY_PLL_EN_USB_CLKS, base +
HW_USBPHY_PLL_SIC_CLR);
writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_CLR);
writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_SET);
writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_CLR);
}
if (is_imx7ulp_phy(mxs_phy)) {
ret = mxs_phy_pll_enable(base, true); if (ret) return ret;
}
ret = stmp_reset_block(base + HW_USBPHY_CTRL); if (ret) goto disable_pll;
if (mxs_phy->phy_3p0) {
ret = regulator_enable(mxs_phy->phy_3p0); if (ret) {
dev_err(mxs_phy->phy.dev, "Failed to enable 3p0 regulator, ret=%d\n",
ret); return ret;
}
}
/* Power up the PHY */
writel(0, base + HW_USBPHY_PWD);
/* * USB PHY Ctrl Setting * - Auto clock/power on * - Enable full/low speed support
*/
writel(BM_USBPHY_CTRL_ENAUTOSET_USBCLKS |
BM_USBPHY_CTRL_ENAUTOCLR_USBCLKGATE |
BM_USBPHY_CTRL_ENAUTOCLR_PHY_PWD |
BM_USBPHY_CTRL_ENAUTOCLR_CLKGATE |
BM_USBPHY_CTRL_ENAUTO_PWRON_PLL |
BM_USBPHY_CTRL_ENUTMILEVEL2 |
BM_USBPHY_CTRL_ENUTMILEVEL3,
base + HW_USBPHY_CTRL_SET);
if (mxs_phy->data->flags & MXS_PHY_NEED_IP_FIX)
writel(BM_USBPHY_IP_FIX, base + HW_USBPHY_IP_SET);
if (mxs_phy->regmap_anatop) { unsignedint reg = mxs_phy->port_id ?
ANADIG_USB1_CHRG_DETECT_SET :
ANADIG_USB2_CHRG_DETECT_SET; /* * The external charger detector needs to be disabled, * or the signal at DP will be poor
*/
regmap_write(mxs_phy->regmap_anatop, reg,
ANADIG_USB1_CHRG_DETECT_EN_B |
ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
}
mxs_phy_tx_init(mxs_phy);
return 0;
disable_pll: if (is_imx7ulp_phy(mxs_phy))
mxs_phy_pll_enable(base, false); return ret;
}
/* Return true if the vbus is there */ staticbool mxs_phy_get_vbus_status(struct mxs_phy *mxs_phy)
{ unsignedint vbus_value = 0;
if (is_imx7ulp_phy(mxs_phy))
mxs_phy_pll_enable(phy->io_priv, false);
if (mxs_phy->phy_3p0)
regulator_disable(mxs_phy->phy_3p0);
clk_disable_unprepare(mxs_phy->clk);
}
staticbool mxs_phy_is_low_speed_connection(struct mxs_phy *mxs_phy)
{ unsignedint line_state; /* bit definition is the same for all controllers */ unsignedint dp_bit = BM_ANADIG_USB1_MISC_RX_VPIN_FS,
dm_bit = BM_ANADIG_USB1_MISC_RX_VMIN_FS; unsignedint reg = ANADIG_USB1_MISC;
/* If the SoCs don't have anatop, quit */ if (!mxs_phy->regmap_anatop) returnfalse;
if (suspend) { /* * FIXME: Do not power down RXPWD1PT1 bit for low speed * connect. The low speed connection will have problem at * very rare cases during usb suspend and resume process.
*/ if (low_speed_connection & vbus_is_on) { /* * If value to be set as pwd value is not 0xffffffff, * several 32Khz cycles are needed.
*/
mxs_phy_clock_switch_delay();
writel(0xffbfffff, x->io_priv + HW_USBPHY_PWD);
} else {
writel(0xffffffff, x->io_priv + HW_USBPHY_PWD);
}
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_SET); if (!(mxs_phy->port_id == 1 &&
(mxs_phy->data->flags &
MXS_PHY_HARDWARE_CONTROL_PHY2_CLK)))
clk_disable_unprepare(mxs_phy->clk);
} else {
mxs_phy_clock_switch_delay(); if (!(mxs_phy->port_id == 1 &&
(mxs_phy->data->flags &
MXS_PHY_HARDWARE_CONTROL_PHY2_CLK))) {
ret = clk_prepare_enable(mxs_phy->clk); if (ret) return ret;
}
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_CLR);
writel(0, x->io_priv + HW_USBPHY_PWD);
}
/* Sometimes, the speed is not high speed when the error occurs */ if (readl(phy->io_priv + HW_USBPHY_CTRL) &
BM_USBPHY_CTRL_ENHOSTDISCONDETECT)
writel(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
phy->io_priv + HW_USBPHY_CTRL_CLR);
return 0;
}
#define MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT 100 staticint mxs_charger_data_contact_detect(struct mxs_phy *x)
{ struct regmap *regmap = x->regmap_anatop; int i, stable_contact_count = 0;
u32 val;
/* Check if vbus is valid */
regmap_read(regmap, ANADIG_USB1_VBUS_DET_STAT, &val); if (!(val & ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) {
dev_err(x->phy.dev, "vbus is not valid\n"); return -EINVAL;
}
/* Enable charger detector */
regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
ANADIG_USB1_CHRG_DETECT_EN_B); /* * - Do not check whether a charger is connected to the USB port * - Check whether the USB plug has been in contact with each other
*/
regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
/* Check if plug is connected */ for (i = 0; i < MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT; i++) {
regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); if (val & ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) {
stable_contact_count++; if (stable_contact_count > 5) /* Data pin makes contact */ break; else
usleep_range(5000, 10000);
} else {
stable_contact_count = 0;
usleep_range(5000, 6000);
}
}
if (i == MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT) {
dev_err(x->phy.dev, "Data pin can't make good contact.\n"); /* Disable charger detector */
regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET,
ANADIG_USB1_CHRG_DETECT_EN_B |
ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); return -ENXIO;
}
/* * - Do check whether a charger is connected to the USB port * - Do not Check whether the USB plug has been in contact with * each other
*/
regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR,
ANADIG_USB1_CHRG_DETECT_CHK_CONTACT |
ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B);
msleep(100);
/* Check if it is a charger */
regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) {
chgr_type = SDP_TYPE;
dev_dbg(x->phy.dev, "It is a standard downstream port\n");
}
/* * It must be called after DP is pulled up, which is used to * differentiate DCP and CDP.
*/ staticenum usb_charger_type mxs_charger_secondary_detection(struct mxs_phy *x)
{ struct regmap *regmap = x->regmap_anatop; int val;
msleep(80);
regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); if (val & ANADIG_USB1_CHRG_DET_STAT_DM_STATE) {
dev_dbg(x->phy.dev, "It is a dedicate charging port\n"); return DCP_TYPE;
} else {
dev_dbg(x->phy.dev, "It is a charging downstream port\n"); return CDP_TYPE;
}
}
if (chgr_type != SDP_TYPE) { /* Pull up DP via test */
writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
base + HW_USBPHY_DEBUG_CLR);
regmap_write(regmap, ANADIG_USB1_LOOPBACK_SET,
ANADIG_USB1_LOOPBACK_UTMI_TESTSTART);
/* Stop the test */
regmap_write(regmap, ANADIG_USB1_LOOPBACK_CLR,
ANADIG_USB1_LOOPBACK_UTMI_TESTSTART);
writel_relaxed(BM_USBPHY_DEBUG_CLKGATE,
base + HW_USBPHY_DEBUG_SET);
}
base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) return PTR_ERR(base);
clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(clk)) return dev_err_probe(&pdev->dev, PTR_ERR(clk), "can't get the clock\n");
mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL); if (!mxs_phy) return -ENOMEM;
/* Some SoCs don't have anatop registers */ if (of_property_present(np, "fsl,anatop")) {
mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
(np, "fsl,anatop"); if (IS_ERR(mxs_phy->regmap_anatop)) {
dev_dbg(&pdev->dev, "failed to find regmap for anatop\n"); return PTR_ERR(mxs_phy->regmap_anatop);
}
}
/* Currently, only imx7ulp has SIM module */ if (of_get_property(np, "nxp,sim", NULL)) {
mxs_phy->regmap_sim = syscon_regmap_lookup_by_phandle
(np, "nxp,sim"); if (IS_ERR(mxs_phy->regmap_sim)) {
dev_dbg(&pdev->dev, "failed to find regmap for sim\n"); return PTR_ERR(mxs_phy->regmap_sim);
}
}
/* Precompute which bits of the TX register are to be updated, if any */ if (!of_property_read_u32(np, "fsl,tx-cal-45-dn-ohms", &val) &&
val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) { /* Scale to a 4-bit value */
val = (MXS_PHY_TX_CAL45_MAX - val) * 0xF
/ (MXS_PHY_TX_CAL45_MAX - MXS_PHY_TX_CAL45_MIN);
mxs_phy->tx_reg_mask |= GM_USBPHY_TX_TXCAL45DN(~0);
mxs_phy->tx_reg_set |= GM_USBPHY_TX_TXCAL45DN(val);
}
if (!of_property_read_u32(np, "fsl,tx-cal-45-dp-ohms", &val) &&
val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) { /* Scale to a 4-bit value. */
val = (MXS_PHY_TX_CAL45_MAX - val) * 0xF
/ (MXS_PHY_TX_CAL45_MAX - MXS_PHY_TX_CAL45_MIN);
mxs_phy->tx_reg_mask |= GM_USBPHY_TX_TXCAL45DP(~0);
mxs_phy->tx_reg_set |= GM_USBPHY_TX_TXCAL45DP(val);
}
if (!of_property_read_u32(np, "fsl,tx-d-cal", &val) &&
val >= MXS_PHY_TX_D_CAL_MIN && val <= MXS_PHY_TX_D_CAL_MAX) { /* Scale to a 4-bit value. Round up the values and heavily * weight the rounding by adding 2/3 of the denominator.
*/
val = ((MXS_PHY_TX_D_CAL_MAX - val) * 0xF
+ (MXS_PHY_TX_D_CAL_MAX - MXS_PHY_TX_D_CAL_MIN) * 2/3)
/ (MXS_PHY_TX_D_CAL_MAX - MXS_PHY_TX_D_CAL_MIN);
mxs_phy->tx_reg_mask |= GM_USBPHY_TX_D_CAL(~0);
mxs_phy->tx_reg_set |= GM_USBPHY_TX_D_CAL(val);
}
ret = of_alias_get_id(np, "usbphy"); if (ret < 0)
dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret);
mxs_phy->port_id = ret;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.