将TypeC的adb切换到USB3.0口

前言

目前大多数Android的嵌入式系统都会使用TypeC做为adb的调试口,这样很方便。但是在有的时候,迫不得已必须将adb功能切换到USB3.0口,但由于当前的内核USB框架只能支持一个USB口作为Peripheral
功能,所以RK3399 SDK默认配置Type-C0作为OTG mode 支持USB Peripheral功能,而Type-C1只支持Hostmode。

注意: 下面介绍的方法只在rk3399的Industry的7.1版本上实现成功并测试过,其他版本仅做参考,但也大同小异。

Kernel: v4.4.126

Device: rk3399

Platform Version: Android 7.1

硬件

首先,参考原理图如下:

1

Device Tree

RK提供的dts中默认的rk3399.dtsi,默认

 usbdrd3_0: usb@fe800000 {
      ...
    usbdrd_dwc3_0: dwc3@fe800000 {
        compatible = "snps,dwc3";
        dr_mode = "otg";
    };
...
};
usbdrd3_1: usb@fe900000 {
    ...
    usbdrd_dwc3_1: dwc3@fe900000 {
        compatible = "snps,dwc3";
        dr_mode = "host";
    };
...
};

我们需要在自己的dts中修改usbdrd_dwc3_1的dr_mode模式为otg,将usbdrd_dwc3_0的模式修改为host

&usbdrd_dwc3_0 {
    status = "okay";
    dr_mode = "host";
};

&usbdrd_dwc3_1 {
    status = "okay";
    dr_mode = "otg";
};

Drivers

  1. kernel/include/linux/phy/phy.h

    diff --git a/kernel/include/linux/phy/phy.h b/kernel/include/linux/phy/phy.h
    index a3965c3..c0daa66 100644
    --- a/kernel/include/linux/phy/phy.h
    +++ b/kernel/include/linux/phy/phy.h
    @@ -36,6 +36,7 @@ enum phy_mode {
        * @power_on: powering on the phy
        * @power_off: powering off the phy
        * @set_mode: set the mode of the phy
    + * @set_vbusdet: usb disconnect of the phy
        * @reset: resetting the phy
        * @cp_test: prepare for the phy compliance test
        * @owner: the module owner containing the ops
    @@ -46,6 +47,7 @@ struct phy_ops {
            int     (*power_on)(struct phy *phy);
            int     (*power_off)(struct phy *phy);
            int     (*set_mode)(struct phy *phy, enum phy_mode mode);
    +    int (*set_vbusdet)(struct phy *phy, bool level);
            int     (*reset)(struct phy *phy);
            int     (*cp_test)(struct phy *phy);
            struct module *owner;
    @@ -133,6 +135,7 @@ int phy_exit(struct phy *phy);
      int phy_power_on(struct phy *phy);
      int phy_power_off(struct phy *phy);
      int phy_set_mode(struct phy *phy, enum phy_mode mode);
    +int phy_set_vbusdet(struct phy *phy, bool level);
      int phy_reset(struct phy *phy);
      int phy_cp_test(struct phy *phy);
      static inline int phy_get_bus_width(struct phy *phy)
    @@ -247,6 +250,13 @@ static inline int phy_set_mode(struct phy *phy, enum phy_mode mode)
            return -ENOSYS;
      }
    
    +static inline int phy_set_vbusdet(struct phy *phy, bool level)
    +{
    +    if (!phy)
    +        return 0;
    +    return -ENOSYS;
    +}
    +
      static inline int phy_reset(struct phy *phy)
      {
            if (!phy)
  2. kernel/drivers/phy/rockchip/phy-rockchip-inno-usb2.c

    diff --git a/kernel/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/kernel/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
    index 66fb407..1f11ae1 100644
    --- a/kernel/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
    +++ b/kernel/drivers/phy/rockchip/phy-rockchip-inno-usb2.c
    @@ -37,6 +37,8 @@
     #include <linux/usb/of.h>
     #include <linux/usb/otg.h>
     #include <linux/wakelock.h>
    +#include <linux/gpio.h>
    +#include <linux/of_gpio.h>
    
     #define BIT_WRITEABLE_SHIFT    16
     #define SCHEDULE_DELAY        (60 * HZ)
    @@ -250,6 +252,7 @@ struct rockchip_usb2phy_port {
         struct        delayed_work chg_work;
         struct        delayed_work otg_sm_work;
         struct        delayed_work sm_work;
    +    struct      delayed_work peripheral_work;
         struct        regulator *vbus;
         const struct    rockchip_usb2phy_port_cfg *port_cfg;
         struct notifier_block    event_nb;
    @@ -816,12 +819,37 @@ static int rockchip_usb2phy_set_mode(struct phy *phy, enum phy_mode mode)
         return ret;
     }
    
    +static int rockchip_usb2phy_set_vbusdet(struct phy *phy, bool level)
    +{
    +    struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy);
    +    struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent);
    +    int ret = 0;
    +
    +    if (rport->port_id != USB2PHY_PORT_OTG)
    +        return ret;
    +
    +    if (rphy->phy_cfg->reg == 0xe460) {
    +        if (level)
    +        {
    +                ret = regmap_write(rphy->grf, 0x4518, GENMASK(20, 20) | 0x10);
    +        }
    +        else
    +        {
    +                ret = regmap_write(rphy->grf, 0x4518, GENMASK(20, 20) | 0x00);
    +        }
    +    }
    +
    +    return ret;
    +}
    +
    +
     static const struct phy_ops rockchip_usb2phy_ops = {
         .init        = rockchip_usb2phy_init,
         .exit        = rockchip_usb2phy_exit,
         .power_on    = rockchip_usb2phy_power_on,
         .power_off    = rockchip_usb2phy_power_off,
         .set_mode    = rockchip_usb2phy_set_mode,
    +    .set_vbusdet = rockchip_usb2phy_set_vbusdet,
         .owner        = THIS_MODULE,
     };
    
    @@ -1530,13 +1558,24 @@ static int rockchip_otg_event(struct notifier_block *nb,
         return NOTIFY_DONE;
     }
    
    +static void rockchip_usb2phy_peripheral_work(struct work_struct *work)
    +{
    +       struct rockchip_usb2phy_port *rport =
    +               container_of(work, struct rockchip_usb2phy_port, peripheral_work.work);
    +       struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent);
    +       extcon_set_state(rphy->edev, EXTCON_USB, true);
    +       extcon_sync(rphy->edev, EXTCON_USB);
    +       schedule_delayed_work(&rport->peripheral_work, 3 * HZ);
    +
    +}
    +
     static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy,
                           struct rockchip_usb2phy_port *rport,
                           struct device_node *child_np)
     {
         int ret;
         int iddig;
    -
    +    int gpio_vbus_5v;
         rport->port_id = USB2PHY_PORT_OTG;
         rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_OTG];
         rport->state = OTG_STATE_UNDEFINED;
    @@ -1584,6 +1623,32 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy,
             rport->vbus = NULL;
         }
    
    +
    +
    +    rport->vbus_always_on =
    +        of_property_read_bool(child_np, "rockchip,vbus-always-on");
    +    if (rport->vbus_always_on)
    +    {
    +        ret = of_get_named_gpio_flags(child_np, "vbus-5v-gpios", 0, NULL);
    +        if (ret < 0) {
    +            printk("%s() Can not read property vbus-5v-gpio\n", __FUNCTION__);
    +        } else {
    +            gpio_vbus_5v = ret;
    +            ret = devm_gpio_request(rphy->dev, gpio_vbus_5v, "vbus-gpio");
    +            if(ret < 0)
    +                printk("%s() devm_gpio_request vbus-gpio request ERROR\n", __FUNCTION__);
    +            ret = gpio_direction_output(gpio_vbus_5v,1);
    +            if(ret < 0)
    +            printk("%s() gpio_direction_output vbus-gpio set ERROR\n", __FUNCTION__);
    +        }
    +
    +        INIT_DELAYED_WORK(&rport->peripheral_work, rockchip_usb2phy_peripheral_work);
    +        schedule_delayed_work(&rport->peripheral_work, 3 * HZ);
    +
    +        goto out;
    +    }
    +
    +
         rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1);
         if (rport->mode == USB_DR_MODE_HOST ||
             rport->mode == USB_DR_MODE_UNKNOWN) {
    @@ -1600,9 +1665,6 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy,
             goto out;
         }
    
    -    if (rport->vbus_always_on)
    -        goto out;
    -
         wake_lock_init(&rport->wakelock, WAKE_LOCK_SUSPEND, "rockchip_otg");
         INIT_DELAYED_WORK(&rport->bypass_uart_work,
                   rockchip_usb_bypass_uart_work);
  3. kernel/drivers/phy/phy-core.c

    diff --git a/kernel/drivers/phy/phy-core.c b/kernel/drivers/phy/phy-core.c
    index 0587933..8dd548b 100644
    --- a/kernel/drivers/phy/phy-core.c
    +++ b/kernel/drivers/phy/phy-core.c
    @@ -387,6 +387,21 @@ int phy_cp_test(struct phy *phy)
     }
     EXPORT_SYMBOL_GPL(phy_cp_test);
    
    +int phy_set_vbusdet(struct phy *phy, bool level)
    +{
    +    int ret;
    +
    +    if (!phy || !phy->ops->set_vbusdet)
    +        return 0;
    +
    +    mutex_lock(&phy->mutex);
    +    ret = phy->ops->set_vbusdet(phy, level);
    +    mutex_unlock(&phy->mutex);
    +
    +    return ret;
    +}
    +EXPORT_SYMBOL_GPL(phy_set_vbusdet);
    +
     /**
      * _of_phy_get() - lookup and obtain a reference to a phy by phandle
      * @np: device_node for which to get the phy
  4. kernel/drivers/usb/dwc3/dwc3-rockchip.c

    diff --git a/kernel/drivers/usb/dwc3/dwc3-rockchip.c b/kernel/drivers/usb/dwc3/dwc3-rockchip.c
    index 539b89a..7cf9675 100644
    --- a/kernel/drivers/usb/dwc3/dwc3-rockchip.c
    +++ b/kernel/drivers/usb/dwc3/dwc3-rockchip.c
    @@ -24,6 +24,7 @@
     #include <linux/dma-mapping.h>
     #include <linux/clk.h>
     #include <linux/clk-provider.h>
    +#include <linux/debugfs.h>
     #include <linux/of.h>
     #include <linux/of_platform.h>
     #include <linux/pm_runtime.h>
    @@ -31,6 +32,7 @@
     #include <linux/freezer.h>
     #include <linux/iopoll.h>
     #include <linux/reset.h>
    +#include <linux/uaccess.h>
     #include <linux/usb.h>
     #include <linux/usb/hcd.h>
     #include <linux/usb/ch9.h>
    @@ -47,6 +49,7 @@
     struct dwc3_rockchip {
         int            num_clocks;
         bool            connected;
    +    bool            disconnect;
         bool            skip_suspend;
         bool            suspended;
         bool            force_mode;
    @@ -56,6 +59,7 @@ struct dwc3_rockchip {
         struct device        *dev;
         struct clk        **clks;
         struct dwc3        *dwc;
    +    struct dentry        *root;
         struct reset_control    *otg_rst;
         struct extcon_dev    *edev;
         struct usb_hcd        *hcd;
    @@ -96,6 +100,7 @@ static ssize_t dwc3_mode_store(struct device *device,
         struct dwc3_rockchip    *rockchip = dev_get_drvdata(device);
         struct dwc3        *dwc = rockchip->dwc;
         enum usb_dr_mode    new_dr_mode;
    +    //char            buf[32];
    
         if (!rockchip->original_dr_mode)
             rockchip->original_dr_mode = dwc->dr_mode;
    @@ -107,15 +112,21 @@ static ssize_t dwc3_mode_store(struct device *device,
    
         if (!strncmp(buf, "0", 1) || !strncmp(buf, "otg", 3)) {
             new_dr_mode = USB_DR_MODE_OTG;
    +        phy_set_vbusdet(dwc->usb2_generic_phy, 0);
         } else if (!strncmp(buf, "1", 1) || !strncmp(buf, "host", 4)) {
             new_dr_mode = USB_DR_MODE_HOST;
    +        phy_set_vbusdet(dwc->usb2_generic_phy, 0);
         } else if (!strncmp(buf, "2", 1) || !strncmp(buf, "peripheral", 10)) {
             new_dr_mode = USB_DR_MODE_PERIPHERAL;
    +        phy_set_vbusdet(dwc->usb2_generic_phy, 1);
         } else {
             dev_info(rockchip->dev, "illegal dr_mode\n");
    +        phy_set_vbusdet(dwc->usb2_generic_phy, 0);
             return count;
         }
    
    +    msleep(200);
    +
         if (dwc->dr_mode == new_dr_mode) {
             dev_info(rockchip->dev, "Same with current dr_mode\n");
             return count;
    @@ -378,6 +389,17 @@ static void dwc3_rockchip_otg_extcon_evt_work(struct work_struct *work)
    
         mutex_lock(&rockchip->lock);
    
    +    if (extcon_get_cable_state_(edev, EXTCON_USB)) {
    +        if ((dwc->link_state == DWC3_LINK_STATE_U3) && !rockchip->disconnect) {
    +            phy_set_vbusdet(dwc->usb2_generic_phy, 0);
    +            msleep(3000);
    +            phy_set_vbusdet(dwc->usb2_generic_phy, 1);
    +            rockchip->disconnect = true;
    +        } else if(dwc->link_state == DWC3_LINK_STATE_U0) {
    +            rockchip->disconnect = false;
    +        }
    +    }
    +
         if (rockchip->force_mode ? dwc->dr_mode == USB_DR_MODE_PERIPHERAL :
             extcon_get_cable_state_(edev, EXTCON_USB)) {
             if (rockchip->connected)
    @@ -624,6 +646,7 @@ out:
    
     static int dwc3_rockchip_get_extcon_dev(struct dwc3_rockchip *rockchip)
     {
    +    //int            ret;
         struct device        *dev = rockchip->dev;
         struct extcon_dev    *edev;
    
    @@ -743,6 +766,7 @@ static int dwc3_rockchip_probe(struct platform_device *pdev)
         struct device        *dev = &pdev->dev;
         struct device_node    *np = dev->of_node, *child;
         struct platform_device    *child_pdev;
    +    //struct usb_hcd        *hcd = NULL;
    
         unsigned int        count;
         int            ret;

Device

device/rockchip/rk3399/init.rk30board.usb.rc

setprop sys.usb.controller "fe900000.dwc3"

应用或者framework中执行

echo peripheral > /sys/devices/platform/usb@fe900000/dwc3_mode

在Anroid启动后,在adb中执行上面的指令,即可将adb切换到usb3.0口,验证的时候需要使用双公头的usb3.0线。

注意: 虽然adb被切换到了usb3.0,但是进入loader刷机模式,还是需要用TypeC来进行刷机。

转载请注明出处:http://www.wolfnx.com/2020/03/24/Switch-Adb-To-Usb3.0

作者 : wolfnx
邮箱 : wolfnx@outlook.com
邮箱2 : lostnx@gmail.com

Click Me