diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 0e2f1a36d315..269fc030656e 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -67,11 +67,26 @@ #define ANADIG_ANA_MISC0_SET 0x154 #define ANADIG_ANA_MISC0_CLR 0x158 +#define ANADIG_USB1_CHRG_DETECT_SET 0x1b4 +#define ANADIG_USB1_CHRG_DETECT_CLR 0x1b8 +#define ANADIG_USB1_CHRG_DETECT_EN_B BIT(20) +#define ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B BIT(19) +#define ANADIG_USB1_CHRG_DETECT_CHK_CONTACT BIT(18) + #define ANADIG_USB1_VBUS_DET_STAT 0x1c0 +#define ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3) + +#define ANADIG_USB1_CHRG_DET_STAT 0x1d0 +#define ANADIG_USB1_CHRG_DET_STAT_DM_STATE BIT(2) +#define ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED BIT(1) +#define ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT BIT(0) + #define ANADIG_USB2_VBUS_DET_STAT 0x220 #define ANADIG_USB1_LOOPBACK_SET 0x1e4 #define ANADIG_USB1_LOOPBACK_CLR 0x1e8 +#define ANADIG_USB1_LOOPBACK_UTMI_TESTSTART BIT(0) + #define ANADIG_USB2_LOOPBACK_SET 0x244 #define ANADIG_USB2_LOOPBACK_CLR 0x248 @@ -479,6 +494,144 @@ static int mxs_phy_on_disconnect(struct usb_phy *phy, return 0; } +#define MXS_USB_CHARGER_DATA_CONTACT_TIMEOUT 100 +static int 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; + } + + return 0; +} + +static enum usb_charger_type mxs_charger_primary_detection(struct mxs_phy *x) +{ + struct regmap *regmap = x->regmap_anatop; + enum usb_charger_type chgr_type = UNKNOWN_TYPE; + u32 val; + + /* + * - 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 stardard downstream port\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 chgr_type; +} + +/* + * It must be called after DP is pulled up, which is used to + * differentiate DCP and CDP. + */ +enum 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; + } +} + +static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + struct regmap *regmap = mxs_phy->regmap_anatop; + void __iomem *base = phy->io_priv; + enum usb_charger_type chgr_type = UNKNOWN_TYPE; + + if (mxs_charger_data_contact_detect(mxs_phy)) + return chgr_type; + + chgr_type = mxs_charger_primary_detection(mxs_phy); + + 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); + + chgr_type = mxs_charger_secondary_detection(mxs_phy); + + /* 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); + } + + return chgr_type; +} + static int mxs_phy_probe(struct platform_device *pdev) { struct resource *res; @@ -567,6 +720,7 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect; mxs_phy->phy.type = USB_PHY_TYPE_USB2; mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup; + mxs_phy->phy.charger_detect = mxs_phy_charger_detect; mxs_phy->clk = clk; mxs_phy->data = of_id->data;