OSDN Git Service

usb: phy: mxs: add usb charger type detection
authorLi Jun <jun.li@nxp.com>
Mon, 4 Sep 2017 15:14:02 +0000 (23:14 +0800)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Thu, 19 Oct 2017 07:38:06 +0000 (10:38 +0300)
mxs phy has data pin contact and usb charger detector blocks which
can be controlled by software to detect charger type for SDP, CDP and
DCP.

Reviewed-by: Baolin Wang <baolin.wang@linaro.org>
Signed-off-by: Li Jun <jun.li@nxp.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/phy/phy-mxs-usb.c

index 0e2f1a3..269fc03 100644 (file)
 #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;