phy: rockchip-usb: Fixed the port cannot be resumed after suspended.
[firefly-linux-kernel-4.4.55.git] / drivers / phy / phy-rockchip-usb.c
index 33a80eba1cb44836ebc74e6ddeefb5269b76dd30..28119edc0599f1dc33cfdfbb28cc52583a807243 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/clk.h>
 #include <linux/clk-provider.h>
 #include <linux/io.h>
+#include <linux/gpio/consumer.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #define SIDDQ_ON               BIT(13)
 #define SIDDQ_OFF              (0 << 13)
 
+#define USB2_PHY_WRITE_ENA     (0xffff << 16)
+#define USB2_PHY_SUSPEND       (0x5 << 0 | 0xd << 4 | 0x1 << 8)
+#define USB2_PHY_RESUME        (0)
+
+#define UTMI_SEL_GRF_WR_ENA    (0x3 << 16)
+#define UTMI_SEL_GRF_SUSPEND   (0x1 << 0)
+#define UTMI_SEL_GRF_RESUME    (0x2 << 0)
+
 struct rockchip_usb_phys {
        int reg;
        const char *pll_name;
@@ -45,11 +54,15 @@ struct rockchip_usb_phys {
 
 struct rockchip_usb_phy_pdata {
        struct rockchip_usb_phys *phys;
+       unsigned int phy_pw_on;
+       unsigned int phy_pw_off;
+       bool siddq_ctl;
 };
 
 struct rockchip_usb_phy_base {
        struct device *dev;
        struct regmap *reg_base;
+       struct gpio_desc *vbus_drv_gpio;
        const struct rockchip_usb_phy_pdata *pdata;
 };
 
@@ -64,10 +77,12 @@ struct rockchip_usb_phy {
 };
 
 static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy,
-                                          bool siddq)
+                                          bool off)
 {
-       return regmap_write(phy->base->reg_base, phy->reg_offset,
-                           SIDDQ_WRITE_ENA | (siddq ? SIDDQ_ON : SIDDQ_OFF));
+       unsigned int val;
+
+       val = !off ? phy->base->pdata->phy_pw_on : phy->base->pdata->phy_pw_off;
+       return regmap_write(phy->base->reg_base, phy->reg_offset, val);
 }
 
 static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw,
@@ -83,17 +98,22 @@ static void rockchip_usb_phy480m_disable(struct clk_hw *hw)
                                                    clk480m_hw);
 
        /* Power down usb phy analog blocks by set siddq 1 */
-       rockchip_usb_phy_power(phy, 1);
+       if (phy->base->pdata->siddq_ctl)
+               rockchip_usb_phy_power(phy, 1);
 }
 
 static int rockchip_usb_phy480m_enable(struct clk_hw *hw)
 {
+       int ret = 0;
        struct rockchip_usb_phy *phy = container_of(hw,
                                                    struct rockchip_usb_phy,
                                                    clk480m_hw);
 
        /* Power up usb phy analog blocks by set siddq 0 */
-       return rockchip_usb_phy_power(phy, 0);
+       if (phy->base->pdata->siddq_ctl)
+               ret = rockchip_usb_phy_power(phy, 0);
+
+       return ret;
 }
 
 static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw)
@@ -101,14 +121,18 @@ static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw)
        struct rockchip_usb_phy *phy = container_of(hw,
                                                    struct rockchip_usb_phy,
                                                    clk480m_hw);
-       int ret;
+       int ret = 1;
        u32 val;
 
-       ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val);
-       if (ret < 0)
-               return ret;
+       if (phy->base->pdata->siddq_ctl) {
+               ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val);
+               if (ret < 0)
+                       return ret;
+
+               ret = (val & SIDDQ_ON) ? 0 : 1;
+       }
 
-       return (val & SIDDQ_ON) ? 0 : 1;
+       return ret;
 }
 
 static const struct clk_ops rockchip_usb_phy480m_ops = {
@@ -120,18 +144,32 @@ static const struct clk_ops rockchip_usb_phy480m_ops = {
 
 static int rockchip_usb_phy_power_off(struct phy *_phy)
 {
+       int ret = 0;
        struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
 
-       clk_disable_unprepare(phy->clk480m);
+       if (!phy->base->pdata->siddq_ctl) {
+               ret = rockchip_usb_phy_power(phy, 1);
+               if (ret)
+                       return ret;
+       }
 
+       clk_disable_unprepare(phy->clk480m);
        return 0;
 }
 
 static int rockchip_usb_phy_power_on(struct phy *_phy)
 {
+       int ret = 0;
        struct rockchip_usb_phy *phy = phy_get_drvdata(_phy);
 
-       return clk_prepare_enable(phy->clk480m);
+       ret = clk_prepare_enable(phy->clk480m);
+       if (ret)
+               return ret;
+
+       if (!phy->base->pdata->siddq_ctl)
+               ret = rockchip_usb_phy_power(phy, 0);
+
+       return ret;
 }
 
 static const struct phy_ops ops = {
@@ -249,6 +287,9 @@ static const struct rockchip_usb_phy_pdata rk3066a_pdata = {
                { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" },
                { /* sentinel */ }
        },
+       .phy_pw_on  = SIDDQ_WRITE_ENA | SIDDQ_OFF,
+       .phy_pw_off = SIDDQ_WRITE_ENA | SIDDQ_ON,
+       .siddq_ctl  = true,
 };
 
 static const struct rockchip_usb_phy_pdata rk3188_pdata = {
@@ -257,6 +298,9 @@ static const struct rockchip_usb_phy_pdata rk3188_pdata = {
                { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" },
                { /* sentinel */ }
        },
+       .phy_pw_on  = SIDDQ_WRITE_ENA | SIDDQ_OFF,
+       .phy_pw_off = SIDDQ_WRITE_ENA | SIDDQ_ON,
+       .siddq_ctl  = true,
 };
 
 static const struct rockchip_usb_phy_pdata rk3288_pdata = {
@@ -266,6 +310,31 @@ static const struct rockchip_usb_phy_pdata rk3288_pdata = {
                { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" },
                { /* sentinel */ }
        },
+       .phy_pw_on  = SIDDQ_WRITE_ENA | SIDDQ_OFF,
+       .phy_pw_off = SIDDQ_WRITE_ENA | SIDDQ_ON,
+       .siddq_ctl  = true,
+};
+
+static const struct rockchip_usb_phy_pdata rk336x_pdata = {
+       .phys = (struct rockchip_usb_phys[]){
+               { .reg = 0x700, .pll_name = "sclk_otgphy0_480m" },
+               { .reg = 0x728, .pll_name = "sclk_otgphy1_480m" },
+               { /* sentinel */ }
+       },
+       .phy_pw_on  = USB2_PHY_WRITE_ENA | USB2_PHY_RESUME,
+       .phy_pw_off = USB2_PHY_WRITE_ENA | USB2_PHY_SUSPEND,
+       .siddq_ctl  = false,
+};
+
+static const struct rockchip_usb_phy_pdata rk3399_pdata = {
+       .phys = (struct rockchip_usb_phys[]){
+               { .reg = 0xe458, .pll_name = "sclk_otgphy0_480m" },
+               { .reg = 0xe468, .pll_name = "sclk_otgphy1_480m" },
+               { /* sentinel */ }
+       },
+       .phy_pw_on  = UTMI_SEL_GRF_WR_ENA | UTMI_SEL_GRF_RESUME,
+       .phy_pw_off = UTMI_SEL_GRF_WR_ENA | UTMI_SEL_GRF_SUSPEND,
+       .siddq_ctl  = false,
 };
 
 static int rockchip_usb_phy_probe(struct platform_device *pdev)
@@ -297,6 +366,14 @@ static int rockchip_usb_phy_probe(struct platform_device *pdev)
                return PTR_ERR(phy_base->reg_base);
        }
 
+       /* Request the vbus_drv GPIO asserted */
+       phy_base->vbus_drv_gpio =
+               devm_gpiod_get_optional(dev, "vbus_drv", GPIOD_OUT_HIGH);
+       if (!phy_base->vbus_drv_gpio)
+               dev_info(&pdev->dev, "vbus_drv is not assigned!\n");
+       else if (IS_ERR(phy_base->vbus_drv_gpio))
+               return PTR_ERR(phy_base->vbus_drv_gpio);
+
        for_each_available_child_of_node(dev->of_node, child) {
                err = rockchip_usb_phy_init(phy_base, child);
                if (err) {
@@ -313,6 +390,8 @@ static const struct of_device_id rockchip_usb_phy_dt_ids[] = {
        { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata },
        { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata },
        { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata },
+       { .compatible = "rockchip,rk336x-usb-phy", .data = &rk336x_pdata },
+       { .compatible = "rockchip,rk3399-usb-phy", .data = &rk3399_pdata },
        {}
 };