From 9b9596c7330ee9677c6c44b24b2c8d09a7d20c13 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 16 Sep 2013 16:31:24 +0800 Subject: [PATCH 01/14] MLK-11340-26 usb: phy: add notify suspend and resume callback They are used to notify PHY that the controller enters suspend or finishes resume. Signed-off-by: Peter Chen --- include/linux/usb/phy.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index e4de6bc1f69b..953e2547401b 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -155,6 +155,12 @@ struct usb_phy { * manually detect the charger type. */ enum usb_charger_type (*charger_detect)(struct usb_phy *x); + + int (*notify_suspend)(struct usb_phy *x, + enum usb_device_speed speed); + int (*notify_resume)(struct usb_phy *x, + enum usb_device_speed speed); + }; /* for board-specific init logic */ @@ -334,6 +340,24 @@ usb_phy_notify_disconnect(struct usb_phy *x, enum usb_device_speed speed) return 0; } +static inline int usb_phy_notify_suspend + (struct usb_phy *x, enum usb_device_speed speed) +{ + if (x && x->notify_suspend) + return x->notify_suspend(x, speed); + else + return 0; +} + +static inline int usb_phy_notify_resume + (struct usb_phy *x, enum usb_device_speed speed) +{ + if (x && x->notify_resume) + return x->notify_resume(x, speed); + else + return 0; +} + /* notifiers */ static inline int usb_register_notifier(struct usb_phy *x, struct notifier_block *nb) From d03a83b7bb71a1a682ac2db5fdb13b4e2c07c781 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 18 Sep 2013 13:57:59 +0800 Subject: [PATCH 02/14] MLK-11340-27 usb: phy-mxs: Add implementation of nofity_suspend{resume} Implementation of notify_suspend and notify_resume will be different according to mxs_phy_data->flags. Signed-off-by: Peter Chen (cherry picked from commit d1ce766d9aabdfb823131d38056ff67c94e7e20a) --- drivers/usb/phy/phy-mxs-usb.c | 48 ++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 70b8c8248caf..c85d7facb720 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright 2012-2014 Freescale Semiconductor, Inc. + * Copyright 2012-2015 Freescale Semiconductor, Inc. * Copyright (C) 2012 Marek Vasut * on behalf of DENX Software Engineering GmbH */ @@ -708,6 +708,48 @@ static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy) return chgr_type; } +static int mxs_phy_on_suspend(struct usb_phy *phy, + enum usb_device_speed speed) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + + dev_dbg(phy->dev, "%s device has suspended\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); + + /* delay 4ms to wait bus entering idle */ + usleep_range(4000, 5000); + + if (mxs_phy->data->flags & MXS_PHY_ABNORMAL_IN_SUSPEND) { + writel_relaxed(0xffffffff, phy->io_priv + HW_USBPHY_PWD); + writel_relaxed(0, phy->io_priv + HW_USBPHY_PWD); + } + + if (speed == USB_SPEED_HIGH) + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_CLR); + + return 0; +} + +/* + * The resume signal must be finished here. + */ +static int mxs_phy_on_resume(struct usb_phy *phy, + enum usb_device_speed speed) +{ + dev_dbg(phy->dev, "%s device has resumed\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); + + if (speed == USB_SPEED_HIGH) { + /* Make sure the device has switched to High-Speed mode */ + udelay(500); + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_SET); + } + + return 0; +} + static int mxs_phy_probe(struct platform_device *pdev) { struct resource *res; @@ -800,6 +842,10 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->clk = clk; mxs_phy->data = of_id->data; + if (mxs_phy->data->flags & MXS_PHY_SENDING_SOF_TOO_FAST) { + mxs_phy->phy.notify_suspend = mxs_phy_on_suspend; + mxs_phy->phy.notify_resume = mxs_phy_on_resume; + } platform_set_drvdata(pdev, mxs_phy); From af486fb671d4d74672c5653517aa66f93ba7e3b5 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 3 Feb 2015 10:10:53 +0800 Subject: [PATCH 03/14] MLK-10196-4 usb: phy: mxs: Using regulator phy-3p0 It is one of PHY's power, and we need to enable it to keep signal quality good, and pass eye diagram test. Signed-off-by: Peter Chen (cherry picked from commit 3a8670ee7ff698521369e8292bba7ef288a12335) --- drivers/usb/phy/phy-mxs-usb.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index c85d7facb720..284aed0c90a0 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -18,6 +18,7 @@ #include #include #include +#include #define DRIVER_NAME "mxs_phy" @@ -204,6 +205,7 @@ struct mxs_phy { int port_id; u32 tx_reg_set; u32 tx_reg_mask; + struct regulator *phy_3p0; }; static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy) @@ -288,6 +290,16 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) 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); @@ -453,6 +465,9 @@ static void mxs_phy_shutdown(struct usb_phy *phy) 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); } @@ -847,6 +862,20 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->phy.notify_resume = mxs_phy_on_resume; } + mxs_phy->phy_3p0 = devm_regulator_get(&pdev->dev, "phy-3p0"); + if (PTR_ERR(mxs_phy->phy_3p0) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (PTR_ERR(mxs_phy->phy_3p0) == -ENODEV) { + /* not exist */ + mxs_phy->phy_3p0 = NULL; + } else if (IS_ERR(mxs_phy->phy_3p0)) { + dev_err(&pdev->dev, "Getting regulator error: %ld\n", + PTR_ERR(mxs_phy->phy_3p0)); + return PTR_ERR(mxs_phy->phy_3p0); + } + if (mxs_phy->phy_3p0) + regulator_set_voltage(mxs_phy->phy_3p0, 3200000, 3200000); + platform_set_drvdata(pdev, mxs_phy); device_set_wakeup_capable(&pdev->dev, true); From 28e487f5c6d2dc3596b31f270b10d61f65839149 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 3 Feb 2015 16:13:31 +0800 Subject: [PATCH 04/14] MLK-10170 usb: phy: mxs: keep USBPHY2's clk always on Per IC engineer request, we need to keep USBPHY2's clk always on, in this way, the USBPHY2 (PLL7) power can be controlled by hardware suspend signal totally. It is benefit of USB remote wakeup case which needs the resume signal be sent out as soon as possible (without software interfere). It is intended to fix the issue which this ticket describes, the reason for this issue is the host does not send resume in time. Signed-off-by: Peter Chen (cherry picked from commit 98888b352377f9ebaee03bedce8c239691f45262) --- drivers/usb/phy/phy-mxs-usb.c | 36 ++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 284aed0c90a0..673130609851 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -150,6 +150,16 @@ #define MXS_PHY_TX_D_CAL_MIN 79 #define MXS_PHY_TX_D_CAL_MAX 119 +/* + * At some versions, 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. + * It will be benefit for remote wakeup case which needs to send resume + * signal as soon as possible, and in this case, the resume signal can be sent + * out without software interfere. + */ +#define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4) + struct mxs_phy_data { unsigned int flags; }; @@ -161,12 +171,14 @@ static const struct mxs_phy_data imx23_phy_data = { static const struct mxs_phy_data imx6q_phy_data = { .flags = MXS_PHY_SENDING_SOF_TOO_FAST | MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | - MXS_PHY_NEED_IP_FIX, + MXS_PHY_NEED_IP_FIX | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data imx6sl_phy_data = { .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | - MXS_PHY_NEED_IP_FIX, + MXS_PHY_NEED_IP_FIX | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data vf610_phy_data = { @@ -175,7 +187,8 @@ static const struct mxs_phy_data vf610_phy_data = { }; static const struct mxs_phy_data imx6sx_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data imx6ul_phy_data = { @@ -206,6 +219,7 @@ struct mxs_phy { u32 tx_reg_set; u32 tx_reg_mask; struct regulator *phy_3p0; + bool hardware_control_phy2_clk; }; static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy) @@ -523,12 +537,17 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) } writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); - clk_disable_unprepare(mxs_phy->clk); + if (!(mxs_phy->port_id == 1 && + mxs_phy->hardware_control_phy2_clk)) + clk_disable_unprepare(mxs_phy->clk); } else { mxs_phy_clock_switch_delay(); - ret = clk_prepare_enable(mxs_phy->clk); - if (ret) - return ret; + if (!(mxs_phy->port_id == 1 && + 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); @@ -876,6 +895,9 @@ static int mxs_phy_probe(struct platform_device *pdev) if (mxs_phy->phy_3p0) regulator_set_voltage(mxs_phy->phy_3p0, 3200000, 3200000); + if (mxs_phy->data->flags & MXS_PHY_HARDWARE_CONTROL_PHY2_CLK) + mxs_phy->hardware_control_phy2_clk = true; + platform_set_drvdata(pdev, mxs_phy); device_set_wakeup_capable(&pdev->dev, true); From 245652b2e6359294e63610a6a2d06b39e56d6111 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Thu, 15 Mar 2018 16:56:41 +0800 Subject: [PATCH 05/14] MLK-20158-3 usb: phy: mxs: add wakeup enable for imx7ulp This wakeup setting can enable USB wakeup function even the controller's power is lost, and both A7 and M4 are in VLLS mode. Signed-off-by: Peter Chen --- drivers/usb/phy/phy-mxs-usb.c | 41 +++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 673130609851..0c23ed345aaa 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -118,6 +118,11 @@ #define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29) #define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28) +/* System Integration Module (SIM) Registers */ +#define SIM_GPR1 0x30 + +#define USB_PHY_VLLS_WAKEUP_EN BIT(0) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -215,6 +220,7 @@ struct mxs_phy { struct clk *clk; const struct mxs_phy_data *data; struct regmap *regmap_anatop; + struct regmap *regmap_sim; int port_id; u32 tx_reg_set; u32 tx_reg_mask; @@ -826,6 +832,17 @@ static int mxs_phy_probe(struct platform_device *pdev) } } + /* 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) { @@ -915,6 +932,22 @@ static int mxs_phy_remove(struct platform_device *pdev) } #ifdef CONFIG_PM_SLEEP +static void mxs_phy_wakeup_enable(struct mxs_phy *mxs_phy, bool on) +{ + u32 mask = USB_PHY_VLLS_WAKEUP_EN; + + /* If the SoCs don't have SIM, quit */ + if (!mxs_phy->regmap_sim) + return; + + if (on) { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, mask); + udelay(500); + } else { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, 0); + } +} + static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on) { unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; @@ -935,8 +968,10 @@ static int mxs_phy_system_suspend(struct device *dev) { struct mxs_phy *mxs_phy = dev_get_drvdata(dev); - if (device_may_wakeup(dev)) + if (device_may_wakeup(dev)) { mxs_phy_enable_ldo_in_suspend(mxs_phy, true); + mxs_phy_wakeup_enable(mxs_phy, true); + } return 0; } @@ -945,8 +980,10 @@ static int mxs_phy_system_resume(struct device *dev) { struct mxs_phy *mxs_phy = dev_get_drvdata(dev); - if (device_may_wakeup(dev)) + if (device_may_wakeup(dev)) { mxs_phy_enable_ldo_in_suspend(mxs_phy, false); + mxs_phy_wakeup_enable(mxs_phy, false); + } return 0; } From 5bbaeb9792127a65c6a1234abaab485c0c4f8669 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Tue, 18 Oct 2016 16:36:39 +0800 Subject: [PATCH 06/14] MLK-13308-2 usb: phy: phy-mxs-usb: handle USB PHY event For mxs PHY, if there is a vbus but the bus is not enumerated, force the dp/dm as SE0 from the consider side. If not, there is possible USB wakeup due to unstable dp/dm, since there is possible no pull on dp/dm, eg, there is a USB charger on the port. Note, the vbus event is only occurred at device mode, and sent by udc driver. Signed-off-by: Peter Chen --- drivers/usb/phy/phy-mxs-usb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 0c23ed345aaa..7dec4e403d9a 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -433,6 +433,7 @@ static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy) static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) { bool vbus_is_on = false; + enum usb_phy_events last_event = mxs_phy->phy.last_event; /* If the SoCs don't need to disconnect line without vbus, quit */ if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS)) @@ -444,7 +445,8 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); - if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) + if (on && ((!vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) || + (last_event == USB_EVENT_VBUS))) __mxs_phy_disconnect_line(mxs_phy, true); else __mxs_phy_disconnect_line(mxs_phy, false); From 1747e6ecc75a2b264940c817a199ece1987c74ef Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 7 Sep 2016 12:16:59 +0800 Subject: [PATCH 07/14] MLK-13125 usb: phy: phy-mxs-usb: enable weak 1p1 regulator for imx6ul during suspend For imx6ul PHY, when the system enters suspend, its 1p1 is off by default, that may cause the PHY get inaccurate USB DP/DM value. If the USB wakeup is enabled at this time, the unexpected wakeup may occur when the system enters suspend. In this patch, when the vbus is there, we enable weak 1p1 during the PHY suspend API, in that case, the USB DP/DM will be accurate for USB PHY, then unexpected usb wakeup will not be occurred, especially for the USB charger is connected scenario. The user needs to enable PHY wakeup for USB wakeup function using below setting. echo enabled > /sys/devices/platform/soc/2000000.aips-bus/20c9000.usbphy /power/wakeup Cc: Shaojun Wang Cc: Anson Huang Signed-off-by: Peter Chen --- drivers/usb/phy/phy-mxs-usb.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 7dec4e403d9a..a1b240449b54 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -71,6 +71,9 @@ #define BM_USBPHY_PLL_EN_USB_CLKS BIT(6) /* Anatop Registers */ +#define ANADIG_REG_1P1_SET 0x114 +#define ANADIG_REG_1P1_CLR 0x118 + #define ANADIG_ANA_MISC0 0x150 #define ANADIG_ANA_MISC0_SET 0x154 #define ANADIG_ANA_MISC0_CLR 0x158 @@ -123,6 +126,9 @@ #define USB_PHY_VLLS_WAKEUP_EN BIT(0) +#define BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG BIT(18) +#define BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP BIT(19) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -197,7 +203,8 @@ static const struct mxs_phy_data imx6sx_phy_data = { }; static const struct mxs_phy_data imx6ul_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data imx7ulp_phy_data = { @@ -243,6 +250,11 @@ static inline bool is_imx7ulp_phy(struct mxs_phy *mxs_phy) return mxs_phy->data == &imx7ulp_phy_data; } +static inline bool is_imx6ul_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6ul_phy_data; +} + /* * PHY needs some 32K cycles to switch from 32K clock to * bus (such as AHB/AXI, etc) clock. @@ -952,18 +964,30 @@ static void mxs_phy_wakeup_enable(struct mxs_phy *mxs_phy, bool on) static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on) { - unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; + unsigned int reg; + u32 value; /* If the SoCs don't have anatop, quit */ if (!mxs_phy->regmap_anatop) return; - if (is_imx6q_phy(mxs_phy)) + if (is_imx6q_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; regmap_write(mxs_phy->regmap_anatop, reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG); - else if (is_imx6sl_phy(mxs_phy)) + } else if (is_imx6sl_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; regmap_write(mxs_phy->regmap_anatop, reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL); + } else if (is_imx6ul_phy(mxs_phy)) { + reg = on ? ANADIG_REG_1P1_SET : ANADIG_REG_1P1_CLR; + value = BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG | + BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP; + if (mxs_phy_get_vbus_status(mxs_phy) && on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + else if (!on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + } } static int mxs_phy_system_suspend(struct device *dev) From f72c32985f1cf55e5b9d8b5da12c4b003a7f0297 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Wed, 12 Apr 2017 05:31:17 +0800 Subject: [PATCH 08/14] MLK-14285-1 usb: phy: add usb mode for usb_phy USB phy driver may need to know the current working mode of the controller, and does some different settings according to host mode or device mode. Signed-off-by: Li Jun (cherry picked from commit 2286cb30feedd6f4a5cb82a0f0af5aa3a04ab698) Signed-off-by: Vipul Kumar Signed-off-by: Srikanth Krishnakar --- include/linux/usb/phy.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index 953e2547401b..086f95f7b424 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -63,6 +63,13 @@ enum usb_otg_state { OTG_STATE_A_VBUS_ERR, }; +/* The usb role of phy to be working with */ +enum usb_current_mode { + CUR_USB_MODE_NONE, + CUR_USB_MODE_HOST, + CUR_USB_MODE_DEVICE, +}; + struct usb_phy; struct usb_otg; @@ -161,6 +168,9 @@ struct usb_phy { int (*notify_resume)(struct usb_phy *x, enum usb_device_speed speed); + int (*set_mode)(struct usb_phy *x, + enum usb_current_mode mode); + }; /* for board-specific init logic */ @@ -219,6 +229,15 @@ usb_phy_vbus_off(struct usb_phy *x) return x->set_vbus(x, false); } +static inline int +usb_phy_set_mode(struct usb_phy *x, enum usb_current_mode mode) +{ + if (!x || !x->set_mode) + return 0; + + return x->set_mode(x, mode); +} + /* for usb host and peripheral controller drivers */ #if IS_ENABLED(CONFIG_USB_PHY) extern struct usb_phy *usb_get_phy(enum usb_phy_type type); From b8f509e1fc280853d3aa16fedff0229b3c3b607b Mon Sep 17 00:00:00 2001 From: Li Jun Date: Wed, 12 Apr 2017 05:41:21 +0800 Subject: [PATCH 09/14] MLK-14285-3 usb: phy: mxs: optimize disconnect line condition We only have below cases to disconnect line when suspend: 1. Device mode without connection to any host/charger(no vbus). 2. Device mode connect to a charger(w/ vbus), usb suspend when system is entering suspend. This patch can fix usb phy wrongly does disconnect line in case some usb host enters suspend but vbus is off. Signed-off-by: Li Jun (cherry picked from commit 2af48913f77cec3658f5863b13f63619d8101279) --- drivers/usb/phy/phy-mxs-usb.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index a1b240449b54..d3b1187d67fd 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright 2012-2015 Freescale Semiconductor, Inc. + * Copyright 2012-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP * Copyright (C) 2012 Marek Vasut * on behalf of DENX Software Engineering GmbH */ @@ -233,6 +234,7 @@ struct mxs_phy { u32 tx_reg_mask; struct regulator *phy_3p0; bool hardware_control_phy2_clk; + enum usb_current_mode mode; }; static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy) @@ -430,18 +432,6 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect) usleep_range(500, 1000); } -static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy) -{ - void __iomem *base = mxs_phy->phy.io_priv; - u32 phyctrl = readl(base + HW_USBPHY_CTRL); - - if (IS_ENABLED(CONFIG_USB_OTG) && - !(phyctrl & BM_USBPHY_CTRL_OTG_ID_VALUE)) - return true; - - return false; -} - static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) { bool vbus_is_on = false; @@ -457,7 +447,7 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); - if (on && ((!vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) || + if (on && ((!vbus_is_on && mxs_phy->mode != CUR_USB_MODE_HOST) || (last_event == USB_EVENT_VBUS))) __mxs_phy_disconnect_line(mxs_phy, true); else @@ -804,6 +794,19 @@ static int mxs_phy_on_resume(struct usb_phy *phy, return 0; } +/* + * Set the usb current role for phy. + */ +static int mxs_phy_set_mode(struct usb_phy *phy, + enum usb_current_mode mode) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + + mxs_phy->mode = mode; + + return 0; +} + static int mxs_phy_probe(struct platform_device *pdev) { struct resource *res; @@ -907,6 +910,7 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->clk = clk; mxs_phy->data = of_id->data; + mxs_phy->phy.set_mode = mxs_phy_set_mode; if (mxs_phy->data->flags & MXS_PHY_SENDING_SOF_TOO_FAST) { mxs_phy->phy.notify_suspend = mxs_phy_on_suspend; mxs_phy->phy.notify_resume = mxs_phy_on_resume; From a01c80c32a9e9f0f0a57b8a763db5008e2f8a4be Mon Sep 17 00:00:00 2001 From: Li Jun Date: Fri, 19 May 2017 00:05:44 +0800 Subject: [PATCH 10/14] MLK-14947-2 usb: phy: add mxs phy driver dependency for ARM64 Add mxs phy driver dependency on ARCH_MXC_ARM64. Acked-by: Peter Chen Signed-off-by: Li Jun --- drivers/usb/phy/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 24b4f091acb8..4b2002dabcdf 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -152,7 +152,7 @@ config USB_MV_OTG config USB_MXS_PHY tristate "Freescale MXS USB PHY support" - depends on ARCH_MXC || ARCH_MXS + depends on ARCH_MXC || ARCH_MXS || ARCH_MXC_ARM64 select STMP_DEVICE select USB_PHY help From 5840b174e85e66b502b4887026ce44c5665c29b1 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 16 Oct 2017 23:13:19 +0800 Subject: [PATCH 11/14] MLK-16576 usb: phy: mxs: set hold_ring_off for USB2 PLL power up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USB2 PLL use ring VCO, when the PLL power up, the ring VCO’s supply also ramp up. There is a possibility that the ring VCO start oscillation at multi nodes in this phase, especially for VCO which has many stages, then the multiwave will kept until PLL power down. Hold_ring_off(bit11) can force the VCO in one determined state when VCO supply start ramp up, to avoid this multiwave issue. Per IC design's suggestion it's better this bit can be off from 25us after pll power up to 25us before USB TX/RX. Acked-by: Peter Chen Signed-off-by: Li Jun (cherry picked from commit a094377f04c9ed2c8e702ee7bfab843caa03eb96) --- drivers/usb/phy/phy-mxs-usb.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index d3b1187d67fd..73cb7c42e69f 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -72,6 +72,9 @@ #define BM_USBPHY_PLL_EN_USB_CLKS BIT(6) /* Anatop Registers */ +#define ANADIG_PLL_USB2 0x20 +#define ANADIG_PLL_USB2_SET 0x24 +#define ANADIG_PLL_USB2_CLR 0x28 #define ANADIG_REG_1P1_SET 0x114 #define ANADIG_REG_1P1_CLR 0x118 @@ -130,6 +133,8 @@ #define BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG BIT(18) #define BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP BIT(19) +#define BM_ANADIG_PLL_USB2_HOLD_RING_OFF BIT(11) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -545,6 +550,22 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) } else { writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); } + + /* + * USB2 PLL use ring VCO, when the PLL power up, the ring + * VCO’s supply also ramp up. There is a possibility that + * the ring VCO start oscillation at multi nodes in this + * phase, especially for VCO which has many stages, then + * the multiwave will be kept until PLL power down. the bit + * hold_ring_off can force the VCO in one determined state + * to avoid the multiwave issue when VCO supply start ramp + * up. + */ + if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop) + regmap_write(mxs_phy->regmap_anatop, + ANADIG_PLL_USB2_SET, + BM_ANADIG_PLL_USB2_HOLD_RING_OFF); + writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); if (!(mxs_phy->port_id == 1 && @@ -558,6 +579,20 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) if (ret) return ret; } + + /* + * Per IC design's requirement, hold_ring_off bit can be + * cleared 25us after PLL power up and 25us before any USB + * TX/RX. + */ + if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop) { + udelay(25); + regmap_write(mxs_phy->regmap_anatop, + ANADIG_PLL_USB2_CLR, + BM_ANADIG_PLL_USB2_HOLD_RING_OFF); + udelay(25); + } + writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_CLR); writel(0, x->io_priv + HW_USBPHY_PWD); From f3791970f819c8ff0fa7b98fc0fae4003391668a Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 8 Oct 2018 17:43:00 +0800 Subject: [PATCH 12/14] 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 Signed-off-by: Peter Chen --- drivers/usb/phy/phy-mxs-usb.c | 149 +++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 3 deletions(-) diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 73cb7c42e69f..299eb6be79b8 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -135,6 +135,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 */ @@ -177,6 +203,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; }; @@ -214,6 +243,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[] = { @@ -240,6 +270,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) @@ -842,6 +873,114 @@ static int mxs_phy_set_mode(struct usb_phy *phy, return 0; } +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; @@ -873,6 +1012,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 @@ -929,6 +1069,8 @@ static int mxs_phy_probe(struct platform_device *pdev) 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->clk = clk; + mxs_phy->data = of_id->data; mxs_phy->port_id = ret; mxs_phy->phy.io_priv = base; @@ -941,10 +1083,11 @@ 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; + 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->clk = clk; - mxs_phy->data = of_id->data; mxs_phy->phy.set_mode = mxs_phy_set_mode; if (mxs_phy->data->flags & MXS_PHY_SENDING_SOF_TOO_FAST) { mxs_phy->phy.notify_suspend = mxs_phy_on_suspend; From 9ff5b5eacdaa5785371e2f2b1132558e7d7245e7 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Wed, 6 Nov 2019 17:24:29 +0800 Subject: [PATCH 13/14] usb: phy: show USB charger type for user Current USB charger framework only shows charger state for user, but the user may also need charger type for further use, add support for it. Signed-off-by: Peter Chen --- drivers/usb/phy/phy.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index 0277f62739a2..ad2554630889 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -34,6 +34,14 @@ struct phy_devm { struct notifier_block *nb; }; +static const char *const usb_chger_type[] = { + [UNKNOWN_TYPE] = "USB_CHARGER_UNKNOWN_TYPE", + [SDP_TYPE] = "USB_CHARGER_SDP_TYPE", + [CDP_TYPE] = "USB_CHARGER_CDP_TYPE", + [DCP_TYPE] = "USB_CHARGER_DCP_TYPE", + [ACA_TYPE] = "USB_CHARGER_ACA_TYPE", +}; + static struct usb_phy *__usb_find_phy(struct list_head *list, enum usb_phy_type type) { @@ -98,7 +106,8 @@ static void usb_phy_notify_charger_work(struct work_struct *work) { struct usb_phy *usb_phy = container_of(work, struct usb_phy, chg_work); char uchger_state[50] = { 0 }; - char *envp[] = { uchger_state, NULL }; + char uchger_type[50] = { 0 }; + char *envp[] = { uchger_state, uchger_type, NULL }; unsigned int min, max; switch (usb_phy->chg_state) { @@ -122,6 +131,8 @@ static void usb_phy_notify_charger_work(struct work_struct *work) return; } + snprintf(uchger_type, ARRAY_SIZE(uchger_type), + "USB_CHARGER_TYPE=%s", usb_chger_type[usb_phy->chg_type]); kobject_uevent_env(&usb_phy->dev->kobj, KOBJ_CHANGE, envp); } From 83e2195664e1079efad2573f44f4310716038ccd Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Mon, 11 Nov 2019 14:28:33 +0800 Subject: [PATCH 14/14] Doc: ABI: add usb charger uevent When the USB charger is inserted or removed, the users could get USB charger state and type through the uevent. Signed-off-by: Peter Chen --- Documentation/ABI/testing/usb-charger-uevent | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Documentation/ABI/testing/usb-charger-uevent diff --git a/Documentation/ABI/testing/usb-charger-uevent b/Documentation/ABI/testing/usb-charger-uevent new file mode 100644 index 000000000000..93ffd3a54a7f --- /dev/null +++ b/Documentation/ABI/testing/usb-charger-uevent @@ -0,0 +1,45 @@ +What: Raise a uevent when a USB charger is inserted or removed +Date: 2019-11-11 +KernelVersion: 5.5 +Contact: linux-usb@vger.kernel.org +Description: There are two USB charger states: + USB_CHARGER_ABSENT + USB_CHARGER_PRESENT + There are five USB charger types: + USB_CHARGER_UNKNOWN_TYPE + USB_CHARGER_SDP_TYPE + USB_CHARGER_CDP_TYPE + USB_CHARGER_DCP_TYPE + USB_CHARGER_ACA_TYPE + + Here are two examples taken using udevadm monitor -p when + USB charger is online: + UDEV [227.425096] change /devices/soc0/usbphynop1 (platform) + ACTION=change + DEVPATH=/devices/soc0/usbphynop1 + DRIVER=usb_phy_generic + MODALIAS=of:Nusbphynop1T(null)Cusb-nop-xceiv + OF_COMPATIBLE_0=usb-nop-xceiv + OF_COMPATIBLE_N=1 + OF_FULLNAME=/usbphynop1 + OF_NAME=usbphynop1 + SEQNUM=2493 + SUBSYSTEM=platform + USB_CHARGER_STATE=USB_CHARGER_PRESENT + USB_CHARGER_TYPE=USB_CHARGER_SDP_TYPE + USEC_INITIALIZED=227422826 + + USB charger is offline: + KERNEL[229.533933] change /devices/soc0/usbphynop1 (platform) + ACTION=change + DEVPATH=/devices/soc0/usbphynop1 + DRIVER=usb_phy_generic + MODALIAS=of:Nusbphynop1T(null)Cusb-nop-xceiv + OF_COMPATIBLE_0=usb-nop-xceiv + OF_COMPATIBLE_N=1 + OF_FULLNAME=/usbphynop1 + OF_NAME=usbphynop1 + SEQNUM=2494 + SUBSYSTEM=platform + USB_CHARGER_STATE=USB_CHARGER_ABSENT + USB_CHARGER_TYPE=USB_CHARGER_UNKNOWN_TYPE