1
0
Fork 0

MLK-19850-1 usb: phy: mxs: add DCD implementation

The DCD is a hardware IP in USB PHY which is used for USB charger
detection, we use polling method for charger detection in this
design to avoid unknown USB PHY interrupt. Currently, the
imx8qm, imx8qxp and imx7ulp have DCD module.

Reviewed-by: Jun Li <jun.li@nxp.com>
Signed-off-by: Peter Chen <peter.chen@nxp.com>
pull/10/head
Peter Chen 2018-10-08 17:43:00 +08:00 committed by Jason Liu
parent b6e17b7a25
commit f9e7bb437e
1 changed files with 146 additions and 1 deletions

View File

@ -134,6 +134,32 @@
#define BM_ANADIG_PLL_USB2_HOLD_RING_OFF BIT(11)
/* DCD module, the offset is 0x800 */
#define DCD_CONTROL 0x800
#define DCD_CLOCK (DCD_CONTROL + 0x4)
#define DCD_STATUS (DCD_CONTROL + 0x8)
#define DCD_CONTROL_SR BIT(25)
#define DCD_CONTROL_START BIT(24)
#define DCD_CONTROL_BC12 BIT(17)
#define DCD_CONTROL_IE BIT(16)
#define DCD_CONTROL_IF BIT(8)
#define DCD_CONTROL_IACK BIT(0)
#define DCD_CLOCK_MHZ BIT(0)
#define DCD_STATUS_ACTIVE BIT(22)
#define DCD_STATUS_TO BIT(21)
#define DCD_STATUS_ERR BIT(20)
#define DCD_STATUS_SEQ_STAT (BIT(18) | BIT(19))
#define DCD_CHG_PORT BIT(19)
#define DCD_CHG_DET (BIT(18) | BIT(19))
#define DCD_CHG_DPIN BIT(18)
#define DCD_STATUS_SEQ_RES (BIT(16) | BIT(17))
#define DCD_SDP_PORT BIT(16)
#define DCD_CDP_PORT BIT(17)
#define DCD_DCP_PORT (BIT(16) | BIT(17))
#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
/* Do disconnection between PHY and controller without vbus */
@ -176,6 +202,9 @@
*/
#define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4)
/* The MXS PHYs which have DCD module for charger detection */
#define MXS_PHY_HAS_DCD BIT(5)
struct mxs_phy_data {
unsigned int flags;
};
@ -213,6 +242,7 @@ static const struct mxs_phy_data imx6ul_phy_data = {
};
static const struct mxs_phy_data imx7ulp_phy_data = {
.flags = MXS_PHY_HAS_DCD,
};
static const struct of_device_id mxs_phy_dt_ids[] = {
@ -239,6 +269,7 @@ struct mxs_phy {
struct regulator *phy_3p0;
bool hardware_control_phy2_clk;
enum usb_current_mode mode;
unsigned long clk_rate;
};
static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
@ -831,6 +862,115 @@ static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy)
return chgr_type;
}
static int mxs_phy_dcd_start(struct mxs_phy *mxs_phy)
{
void __iomem *base = mxs_phy->phy.io_priv;
u32 value;
value = readl(base + DCD_CONTROL);
writel(value | DCD_CONTROL_SR, base + DCD_CONTROL);
if (!mxs_phy->clk_rate)
return -EINVAL;
value = readl(base + DCD_CONTROL);
writel(((mxs_phy->clk_rate / 1000000) << 2) | DCD_CLOCK_MHZ,
base + DCD_CLOCK);
value = readl(base + DCD_CONTROL);
value &= ~DCD_CONTROL_IE;
writel(value | DCD_CONTROL_BC12, base + DCD_CONTROL);
value = readl(base + DCD_CONTROL);
writel(value | DCD_CONTROL_START, base + DCD_CONTROL);
return 0;
}
#define DCD_CHARGING_DURTION 1000 /* One second according to BC 1.2 */
static enum usb_charger_type mxs_phy_dcd_flow(struct usb_phy *phy)
{
struct mxs_phy *mxs_phy = to_mxs_phy(phy);
void __iomem *base = mxs_phy->phy.io_priv;
u32 value;
int i = 0;
enum usb_charger_type chgr_type;
if (mxs_phy_dcd_start(mxs_phy))
return UNKNOWN_TYPE;
while (i++ <= (DCD_CHARGING_DURTION / 50)) {
value = readl(base + DCD_CONTROL);
if (value & DCD_CONTROL_IF) {
value = readl(base + DCD_STATUS);
if (value & DCD_STATUS_ACTIVE) {
dev_err(phy->dev, "still detecting\n");
chgr_type = UNKNOWN_TYPE;
break;
}
if (value & DCD_STATUS_TO) {
dev_err(phy->dev, "detect timeout\n");
chgr_type = UNKNOWN_TYPE;
break;
}
if (value & DCD_STATUS_ERR) {
dev_err(phy->dev, "detect error\n");
chgr_type = UNKNOWN_TYPE;
break;
}
if ((value & DCD_STATUS_SEQ_STAT) <= DCD_CHG_DPIN) {
dev_err(phy->dev, "error occurs\n");
chgr_type = UNKNOWN_TYPE;
break;
}
/* SDP */
if (((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_PORT) &&
((value & DCD_STATUS_SEQ_RES)
== DCD_SDP_PORT)) {
dev_dbg(phy->dev, "SDP\n");
chgr_type = SDP_TYPE;
break;
}
if ((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_DET) {
if ((value & DCD_STATUS_SEQ_RES) ==
DCD_CDP_PORT) {
dev_dbg(phy->dev, "CDP\n");
chgr_type = CDP_TYPE;
break;
}
if ((value & DCD_STATUS_SEQ_RES) ==
DCD_DCP_PORT) {
dev_dbg(phy->dev, "DCP\n");
chgr_type = DCP_TYPE;
break;
}
}
dev_err(phy->dev, "unknown error occurs\n");
chgr_type = UNKNOWN_TYPE;
break;
}
msleep(50);
}
if (i > 20) {
dev_err(phy->dev, "charger detecting timeout\n");
chgr_type = UNKNOWN_TYPE;
}
/* disable dcd module */
readl(base + DCD_STATUS);
writel(DCD_CONTROL_IACK, base + DCD_CONTROL);
writel(DCD_CONTROL_SR, base + DCD_CONTROL);
return chgr_type;
}
static int mxs_phy_probe(struct platform_device *pdev)
{
struct resource *res;
@ -862,6 +1002,7 @@ static int mxs_phy_probe(struct platform_device *pdev)
if (!mxs_phy)
return -ENOMEM;
mxs_phy->clk_rate = clk_get_rate(clk);
/* Some SoCs don't have anatop registers */
if (of_get_property(np, "fsl,anatop", NULL)) {
mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
@ -926,7 +1067,11 @@ static int mxs_phy_probe(struct platform_device *pdev)
mxs_phy->phy.notify_suspend = mxs_phy_on_suspend;
mxs_phy->phy.notify_resume = mxs_phy_on_resume;
}
mxs_phy->phy.charger_detect = mxs_phy_charger_detect;
if (mxs_phy->data->flags & MXS_PHY_HAS_DCD)
mxs_phy->phy.charger_detect = mxs_phy_dcd_flow;
else
mxs_phy->phy.charger_detect = mxs_phy_charger_detect;
mxs_phy->phy_3p0 = devm_regulator_get(&pdev->dev, "phy-3p0");
if (PTR_ERR(mxs_phy->phy_3p0) == -EPROBE_DEFER) {