Commit e9365099 authored by Li Jun's avatar Li Jun Committed by Felipe Balbi

usb: phy: mxs: add usb charger type detection

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: default avatarBaolin Wang <baolin.wang@linaro.org>
Signed-off-by: default avatarLi Jun <jun.li@nxp.com>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent 7fdca766
......@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment