FROMLIST: phy: rockchip-inno-usb2: add a new driver for Rockchip usb2phy
authorFrank Wang <frank.wang@rock-chips.com>
Fri, 24 Jun 2016 03:10:18 +0000 (11:10 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Wed, 29 Jun 2016 03:20:50 +0000 (11:20 +0800)
The newer SoCs (rk3366, rk3399) take a different usb-phy IP block
than rk3288 and before, and most of phy-related registers are also
different from the past, so a new phy driver is required necessarily.

Signed-off-by: Frank Wang <frank.wang@rock-chips.com>
Suggested-by: Heiko Stuebner <heiko@sntech.de>
Suggested-by: Guenter Roeck <linux@roeck-us.net>
Suggested-by: Doug Anderson <dianders@chromium.org>
Reviewed-by: Heiko Stuebner <heiko@sntech.de>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
(merge from https://patchwork.kernel.org/patch/9190287/)

Change-Id: I3e7739dee6057928172904565c10cebf9785fec6
Signed-off-by: Frank Wang <frank.wang@rock-chips.com>
drivers/phy/phy-rockchip-inno-usb2.c

index 85b86110ece5771dec74b9f66f361787e9f92a39..3f67c012126a6299e242b4a7ea6f78dfa9534c0d 100644 (file)
@@ -5,7 +5,8 @@
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License.
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -78,15 +79,17 @@ struct rockchip_usb2phy_port_cfg {
 
 /**
  * struct rockchip_usb2phy_cfg: usb-phy configuration.
+ * @reg: the address offset of grf for usb-phy config.
  * @num_ports: specify how many ports that the phy has.
  * @phy_tuning: phy default parameters tunning.
  * @clkout_ctl: keep on/turn off output clk of phy.
  */
 struct rockchip_usb2phy_cfg {
+       unsigned int    reg;
        unsigned int    num_ports;
        int (*phy_tuning)(struct rockchip_usb2phy *);
        struct usb2phy_reg      clkout_ctl;
-       const struct rockchip_usb2phy_port_cfg  *port_cfgs;
+       const struct rockchip_usb2phy_port_cfg  port_cfgs[USB2PHY_NUM_PORTS];
 };
 
 /**
@@ -111,24 +114,24 @@ struct rockchip_usb2phy_port {
 /**
  * struct rockchip_usb2phy: usb2.0 phy driver data.
  * @grf: General Register Files regmap.
+ * @clk: clock struct of phy input clk.
  * @clk480m: clock struct of phy output clk.
  * @clk_hw: clock struct of phy output clk management.
- * @vbus_host_gpio: host VBUS direction output.
  * @phy_cfg: phy register configuration, assigned by driver data.
  * @ports: phy port instance.
  */
 struct rockchip_usb2phy {
        struct device   *dev;
        struct regmap   *grf;
+       struct clk      *clk;
        struct clk      *clk480m;
        struct clk_hw   clk480m_hw;
-       struct gpio_desc        *vbus_host_gpio;
        const struct rockchip_usb2phy_cfg       *phy_cfg;
        struct rockchip_usb2phy_port    ports[USB2PHY_NUM_PORTS];
 };
 
 static inline int property_enable(struct rockchip_usb2phy *rphy,
-                          const struct usb2phy_reg *reg, bool en)
+                                 const struct usb2phy_reg *reg, bool en)
 {
        unsigned int val, mask, tmp;
 
@@ -158,7 +161,7 @@ static int rockchip_usb2phy_clk480m_enable(struct clk_hw *hw)
 {
        struct rockchip_usb2phy *rphy =
                container_of(hw, struct rockchip_usb2phy, clk480m_hw);
-       int ret = 0;
+       int ret;
 
        /* turn on 480m clk output if it is off */
        if (!property_enabled(rphy, &rphy->phy_cfg->clkout_ctl)) {
@@ -170,19 +173,13 @@ static int rockchip_usb2phy_clk480m_enable(struct clk_hw *hw)
                mdelay(1);
        }
 
-       return ret;
+       return 0;
 }
 
 static void rockchip_usb2phy_clk480m_disable(struct clk_hw *hw)
 {
        struct rockchip_usb2phy *rphy =
                container_of(hw, struct rockchip_usb2phy, clk480m_hw);
-       int index;
-
-       /* make sure all ports in suspended mode */
-       for (index = 0; index != rphy->phy_cfg->num_ports; index++)
-               if (!rphy->ports[index].suspended)
-                       return;
 
        /* turn off 480m clk output */
        property_enable(rphy, &rphy->phy_cfg->clkout_ctl, false);
@@ -210,36 +207,78 @@ static const struct clk_ops rockchip_usb2phy_clkout_ops = {
        .recalc_rate = rockchip_usb2phy_clk480m_recalc_rate,
 };
 
-static struct clk *
+static void rockchip_usb2phy_clk480m_unregister(void *data)
+{
+       struct rockchip_usb2phy *rphy = data;
+
+       of_clk_del_provider(rphy->dev->of_node);
+       clk_unregister(rphy->clk480m);
+
+       if (rphy->clk)
+               clk_put(rphy->clk);
+}
+
+static int
 rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy)
 {
        struct device_node *node = rphy->dev->of_node;
-       struct clk *clk;
        struct clk_init_data init;
+       const char *clk_name;
+       int ret;
 
+       init.flags = 0;
        init.name = "clk_usbphy_480m";
        init.ops = &rockchip_usb2phy_clkout_ops;
-       init.flags = CLK_IS_ROOT;
-       init.parent_names = NULL;
-       init.num_parents = 0;
-       rphy->clk480m_hw.init = &init;
 
        /* optional override of the clockname */
        of_property_read_string(node, "clock-output-names", &init.name);
 
+       rphy->clk = of_clk_get_by_name(node, "phyclk");
+       if (IS_ERR(rphy->clk)) {
+               rphy->clk = NULL;
+               init.parent_names = NULL;
+               init.num_parents = 0;
+       } else {
+               clk_name = __clk_get_name(rphy->clk);
+               init.parent_names = &clk_name;
+               init.num_parents = 1;
+       }
+
+       rphy->clk480m_hw.init = &init;
+
        /* register the clock */
-       clk = clk_register(rphy->dev, &rphy->clk480m_hw);
-       if (!IS_ERR(clk))
-               of_clk_add_provider(node, of_clk_src_simple_get, clk);
+       rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw);
+       if (IS_ERR(rphy->clk480m)) {
+               ret = PTR_ERR(rphy->clk480m);
+               goto err_register;
+       }
+
+       ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m);
+       if (ret < 0)
+               goto err_clk_provider;
+
+       ret = devm_add_action(rphy->dev, rockchip_usb2phy_clk480m_unregister,
+                             rphy);
+       if (ret < 0)
+               goto err_unreg_action;
 
-       return clk;
+       return 0;
+
+err_unreg_action:
+       of_clk_del_provider(node);
+err_clk_provider:
+       clk_unregister(rphy->clk480m);
+err_register:
+       if (rphy->clk)
+               clk_put(rphy->clk);
+       return ret;
 }
 
 static int rockchip_usb2phy_init(struct phy *phy)
 {
        struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
        struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent);
-       int ret = 0;
+       int ret;
 
        if (rport->port_id == USB2PHY_PORT_HOST) {
                /* clear linestate and enable linestate detect irq */
@@ -261,16 +300,19 @@ static int rockchip_usb2phy_init(struct phy *phy)
                schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY);
        }
 
-       return ret;
+       return 0;
 }
 
-static int rockchip_usb2phy_resume(struct phy *phy)
+static int rockchip_usb2phy_power_on(struct phy *phy)
 {
        struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
        struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent);
        int ret;
 
-       dev_dbg(&rport->phy->dev, "port resume\n");
+       dev_dbg(&rport->phy->dev, "port power on\n");
+
+       if (!rport->suspended)
+               return 0;
 
        ret = clk_prepare_enable(rphy->clk480m);
        if (ret)
@@ -284,13 +326,16 @@ static int rockchip_usb2phy_resume(struct phy *phy)
        return 0;
 }
 
-static int rockchip_usb2phy_suspend(struct phy *phy)
+static int rockchip_usb2phy_power_off(struct phy *phy)
 {
        struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
        struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent);
        int ret;
 
-       dev_dbg(&rport->phy->dev, "port suspend\n");
+       dev_dbg(&rport->phy->dev, "port power off\n");
+
+       if (rport->suspended)
+               return 0;
 
        ret = property_enable(rphy, &rport->port_cfg->phy_sus, true);
        if (ret)
@@ -298,6 +343,7 @@ static int rockchip_usb2phy_suspend(struct phy *phy)
 
        rport->suspended = true;
        clk_disable_unprepare(rphy->clk480m);
+
        return 0;
 }
 
@@ -314,8 +360,8 @@ static int rockchip_usb2phy_exit(struct phy *phy)
 static const struct phy_ops rockchip_usb2phy_ops = {
        .init           = rockchip_usb2phy_init,
        .exit           = rockchip_usb2phy_exit,
-       .power_on       = rockchip_usb2phy_resume,
-       .power_off      = rockchip_usb2phy_suspend,
+       .power_on       = rockchip_usb2phy_power_on,
+       .power_off      = rockchip_usb2phy_power_off,
        .owner          = THIS_MODULE,
 };
 
@@ -329,8 +375,8 @@ static const struct phy_ops rockchip_usb2phy_ops = {
  * device is disconnected at last through rearm the delayed work,
  * to suspend the phy port in _PHY_STATE_DISCONNECT_ case.
  *
- * NOTE: It will invoke some clk related APIs, so do not invoke it from
- * interrupt context.
+ * NOTE: It may invoke *phy_powr_off or *phy_power_on which will invoke
+ * some clk related APIs, so do not invoke it from interrupt context directly.
  */
 static void rockchip_usb2phy_sm_work(struct work_struct *work)
 {
@@ -381,14 +427,14 @@ static void rockchip_usb2phy_sm_work(struct work_struct *work)
        case PHY_STATE_HS_CONNECT:
                if (rport->suspended) {
                        dev_dbg(&rport->phy->dev, "HS/FS connected\n");
-                       rockchip_usb2phy_resume(rport->phy);
+                       rockchip_usb2phy_power_on(rport->phy);
                        rport->suspended = false;
                }
                break;
        case PHY_STATE_DISCONNECT:
                if (!rport->suspended) {
                        dev_dbg(&rport->phy->dev, "HS/FS disconnected\n");
-                       rockchip_usb2phy_suspend(rport->phy);
+                       rockchip_usb2phy_power_off(rport->phy);
                        rport->suspended = true;
                }
 
@@ -446,9 +492,12 @@ static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy,
                                           struct rockchip_usb2phy_port *rport,
                                           struct device_node *child_np)
 {
-       int ret = 0;
+       int ret;
 
        rport->port_id = USB2PHY_PORT_HOST;
+       rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST];
+       rport->suspended = true;
+
        mutex_init(&rport->mutex);
        INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work);
 
@@ -467,7 +516,7 @@ static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy,
                return ret;
        }
 
-       return ret;
+       return 0;
 }
 
 static int rockchip_usb2phy_probe(struct platform_device *pdev)
@@ -477,7 +526,9 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev)
        struct device_node *child_np;
        struct phy_provider *provider;
        struct rockchip_usb2phy *rphy;
+       const struct rockchip_usb2phy_cfg *phy_cfgs;
        const struct of_device_id *match;
+       unsigned int reg;
        int index, ret;
 
        rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL);
@@ -491,19 +542,44 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev)
        }
 
        if (!dev->parent || !dev->parent->of_node)
-               return -ENOMEM;
+               return -EINVAL;
 
        rphy->grf = syscon_node_to_regmap(dev->parent->of_node);
        if (IS_ERR(rphy->grf))
                return PTR_ERR(rphy->grf);
 
+       if (of_property_read_u32(np, "reg", &reg)) {
+               dev_err(dev, "the reg property is not assigned in %s node\n",
+                       np->name);
+               return -EINVAL;
+       }
+
        rphy->dev = dev;
-       rphy->phy_cfg = match->data;
+       phy_cfgs = match->data;
        platform_set_drvdata(pdev, rphy);
 
-       rphy->clk480m = rockchip_usb2phy_clk480m_register(rphy);
-       if (IS_ERR(rphy->clk480m))
-               return PTR_ERR(rphy->clk480m);
+       /* find out a proper config which can be matched with dt. */
+       index = 0;
+       while (phy_cfgs[index].reg) {
+               if (phy_cfgs[index].reg == reg) {
+                       rphy->phy_cfg = &phy_cfgs[index];
+                       break;
+               }
+
+               ++index;
+       }
+
+       if (!rphy->phy_cfg) {
+               dev_err(dev, "no phy-config can be matched with %s node\n",
+                       np->name);
+               return -EINVAL;
+       }
+
+       ret = rockchip_usb2phy_clk480m_register(rphy);
+       if (ret) {
+               dev_err(dev, "failed to register 480m output clock\n");
+               return ret;
+       }
 
        if (rphy->phy_cfg->phy_tuning) {
                ret = rphy->phy_cfg->phy_tuning(rphy);
@@ -511,18 +587,20 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev)
                        return ret;
        }
 
-       rphy->vbus_host_gpio =
-               devm_gpiod_get_optional(dev, "vbus_host", GPIOD_OUT_HIGH);
-       if (!rphy->vbus_host_gpio)
-               dev_info(dev, "host_vbus is not assigned!\n");
-       else if (IS_ERR(rphy->vbus_host_gpio))
-               return PTR_ERR(rphy->vbus_host_gpio);
-
        index = 0;
-       for_each_child_of_node(np, child_np) {
+       for_each_available_child_of_node(np, child_np) {
                struct rockchip_usb2phy_port *rport = &rphy->ports[index];
                struct phy *phy;
 
+               /*
+                * This driver aim to support both otg-port and host-port,
+                * but unfortunately, the otg part is not ready in current,
+                * so this comments and below codes are interim, which should
+                * be changed after otg-port is supplied soon.
+                */
+               if (of_node_cmp(child_np->name, "host-port"))
+                       goto next_child;
+
                phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops);
                if (IS_ERR(phy)) {
                        dev_err(dev, "failed to create phy\n");
@@ -531,18 +609,16 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev)
                }
 
                rport->phy = phy;
-               rport->port_cfg = &rphy->phy_cfg->port_cfgs[index];
-
-               /* initialize otg/host port separately */
-               if (!of_node_cmp(child_np->name, "host-port")) {
-                       ret = rockchip_usb2phy_host_port_init(rphy, rport,
-                                                             child_np);
-                       if (ret)
-                               goto put_child;
-               }
-
                phy_set_drvdata(rport->phy, rport);
-               index++;
+
+               ret = rockchip_usb2phy_host_port_init(rphy, rport, child_np);
+               if (ret)
+                       goto put_child;
+
+next_child:
+               /* to prevent out of boundary */
+               if (++index >= rphy->phy_cfg->num_ports)
+                       break;
        }
 
        provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
@@ -550,8 +626,6 @@ static int rockchip_usb2phy_probe(struct platform_device *pdev)
 
 put_child:
        of_node_put(child_np);
-       of_clk_del_provider(np);
-       clk_unregister(rphy->clk480m);
        return ret;
 }
 
@@ -574,21 +648,24 @@ static int rk3366_usb2phy_tuning(struct rockchip_usb2phy *rphy)
        return ret;
 }
 
-static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs = {
-       .num_ports      = 1,
-       .phy_tuning     = rk3366_usb2phy_tuning,
-       .clkout_ctl     = { 0x0724, 15, 15, 1, 0 },
-       .port_cfgs      = (struct rockchip_usb2phy_port_cfg[]) {
-               {
-                       .phy_sus        = { 0x0728, 15, 0, 0, 0x1d1 },
-                       .ls_det_en      = { 0x0680, 4, 4, 0, 1 },
-                       .ls_det_st      = { 0x0690, 4, 4, 0, 1 },
-                       .ls_det_clr     = { 0x06a0, 4, 4, 0, 1 },
-                       .utmi_ls        = { 0x049c, 14, 13, 0, 1 },
-                       .utmi_hstdet    = { 0x049c, 12, 12, 0, 1 }
+static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = {
+       {
+               .reg = 0x700,
+               .num_ports      = 2,
+               .phy_tuning     = rk3366_usb2phy_tuning,
+               .clkout_ctl     = { 0x0724, 15, 15, 1, 0 },
+               .port_cfgs      = {
+                       [USB2PHY_PORT_HOST] = {
+                               .phy_sus        = { 0x0728, 15, 0, 0, 0x1d1 },
+                               .ls_det_en      = { 0x0680, 4, 4, 0, 1 },
+                               .ls_det_st      = { 0x0690, 4, 4, 0, 1 },
+                               .ls_det_clr     = { 0x06a0, 4, 4, 0, 1 },
+                               .utmi_ls        = { 0x049c, 14, 13, 0, 1 },
+                               .utmi_hstdet    = { 0x049c, 12, 12, 0, 1 }
+                       }
                },
-               { /* sentinel */ }
        },
+       { /* sentinel */ }
 };
 
 static const struct of_device_id rockchip_usb2phy_dt_match[] = {