本文原是 «特斯拉也要CarPlay!» 中的一节,后又增加了 Z3TC 的部分,篇幅过长,遂修改后独立成文。

鼠标、U盘等设备本身并没有电池,它们被插到电脑上后,是电脑通过USB口给它们提供电力。同样的,手机插到电脑上,也是电脑给手机充电。那么如果把鼠标、U盘(通过转接线)插到手机上呢?这时手机就需要转“受”(外接设备,peripheral)为“攻”(主机,host),对外提供电力。实现这一转换的协议就是 USB On-The-Go (简称:OTG),转接线也必须得是支持OTG的,具体实现细节«USB charging, part 1: requirements»这篇文章介绍得很清楚,建议作为延伸阅读。

你可能注意到了问题,因为手机通常只有一个USB口,临时用一下OTG没啥问题,但如果要长时间使用OTG,那么USB被占用了没法充电,就不是个可持续方案。在处理自制车载屏幕时我就遇到了这个问题:如果连接了 CarKit 就没法充电,要充电就没法连接 CarKit,两者无法同时发生。对于手机而言,如果是“攻”模式就对外供电,如果是“受”模式就接受供电,而我们的需求是让它又“攻”又“受”————作为主机控制外接设备 + 同时接受外部充电。这时就需要 USB Accessory charging adaptors (ACA) 出场了,它解决的就是怎么同时给主机和外接设备供电的问题。ACA的实现细节在上述«USB charging»一文中也介绍清楚了。因为还要额外的供电,所以需要支持 ACA的 Y型 OTG线。我买的是下图这款,介绍页面上还列出了已知支持的设备名单,并写明并不是所有设备都支持。

Xperia Z5

搞定线材只是第一步,接下来需要让 Xperia Z5 支持 ACA 。

搜到了 XDA 上已经有大神 @nlra 搞定了这个事情:《USB OTG (host mode) + simultaneous charging!》。大致内容是:

因为骁龙换了USB 3芯片,驱动也变成了完全不同 DWC3。新的驱动没有实现 USB ACAs (就是我们需要的功能:OTG同时充电)。有人已经给 一加One 内核里的DWC3加上了 ACA,作者基于此(的重写版本)移植到了 Z5 内核并做了一些其它的改动:

  1. 开关做成了选项,可以在系统启动时或内核命令行里指定,也可以在启动后通过 sysfs 修改
  2. 支持ACA功能,可以OTG同时充电
  3. 绕过索尼对驱动的“特别”修改:把供电OTG线当成非OTG线。(不知道索尼为啥干这个,可能是为了降低进水风险?)
  4. 其它修改包括:禁用检测不到设备时的主机模式超时,非OTG供电Y型线上的USB设备也可以触发OTG,正常OTG情况下不需要手动触发“检查USB设备”

好消息是我的 5.1.1 固件版本 就是文章中提及的 32.0.A.6.20x,应该可以直接使用;而坏消息是大神只给出了修改后的 patch 代码,需要自己编译。

编译内核

对于如何编译 Android 内核我也是一窍不通,只能硬着头皮慢慢摸索。先看了下Xperia官方开发者网站上的编译指南,只了解了个大概流程,没有实质性帮助,因为:1. 写得比较简单,省略了很多步骤,应该是给有经验的开发者看的;2. 不确定是信息过旧还是过新,反正就是跟实际代码和工具链对不上,毕竟 2021年了再去搞 Android 5 的编译确实不合时宜。另外找到一篇博客《编译 Android 内核与修改 ramdisk》 介绍得就比较详细。

要编译内核就得先搞到源码,好在Xperia把代码都开源在 GitHub 上,找到对应代码库后翻一下分支列表就找到了对应 32.0.A.6.xxx 的代码,拉取下来。代码库中 README_Xperia 文档也介绍了比较详细的编译流程,对照之前的博客看,大致有了方向。下面介绍具体步骤,完整命令见文末附录。

准备交叉编译工具,文档中推荐使用 aarch64-linux-android-4.9 来避免问题。博客中也给出了这个版本的 git clone 地址,照着拉取下来并配置PATHCROSS_COMPILE环境变量。但我在后面编译过程中 make报错“multiple target patterns”,也没有打印出更详细错误信息,搜来搜去没有个结论,一时毫无头绪。StackOverflow 上的一个回答给出了debug方向,通过在报错行之前加上 $(info VAR="$(vmlinux-deps)") 打印出具体错误,发现是没有 /usr/bin/aarch64-linux-gnu-gcc。可我的 aarch64-linux-android-4.9 就是从 android.googlesource.com 上拉取的,怎么可能会没有呢?慢着,现在都 Android 11 了,当年 Android 5 的编译工具链可能早就变了,如果拉取的是 master 应该是对不上的。找到编译工具对应 Android 5.1.1 的tag android-5.1.1_r37,拉取下来检查下 bin 里面的文件,果然就有了 aarch64-linux-gnu-gcc。这个结论也记录在 StackOverflow 上,希望能给人有所启发。

准备 mkbootimg 用于生成 boot.img。我这里使用的是 mkbootimg_tools,集成了解包和打包工具,输入和输出都通过文件(夹)管理,使用起来比 mkbootimg 更方便。

准备 ramdisk.img 文件。不需要自己编译,下载个现成的 boot.img(版本需要一致),使用 mkbootimg_tools 工具解包得到一个包含 ramdisk.img、dt.img、config 等文件的文件夹,先放着,后面组装时可以直接使用。

下载前文 XDA 帖子中给出的 patch 文件。进入内核源码目录。应用 patch,得到修改后的DWC3驱动。配置 ARCHKBUILD_DIFFCONFIG 环境变量,跑 make kitakami_defconfig。然后跑 make。编译好的内核在 arch/arm64/boot/Image 目录下。至此,编译步骤跟一般的软件编译基本一致。

组装 boot.img。dt.img 已经在前面解包时得到了,不需要编译。把前一步编译得到的 kernel 复制到 mkbootimg_tools 工具之前解包得到文件夹里,替换掉解包出来的 kernel。编辑 config 文件把 dwc3.aca_enable=Y 等配置加到 cmdline 上。最后跑 mkbootimg_tools 打包得到 boot.img。

以上过程我是在 Google Cloud 上开了个实例来搞的,全新的 Ubuntu 环境,超快的下载速度,CPU搞多点编译嗖嗖的,硬盘20G就够了,搞完就把实例一关,用不了几个钱,非常推荐。

最后使用 fastboot 刷入千辛万苦得到的 boot.img。开机进入设置 - 关于本机 检查下,果然是刚刚编译出的这个。连上后试用,完全OK👌👍。

OTG和充电一起

Xperia Z3 Tablet Compact

本来写到了这里就该结束了,然而我还是按耐不住折腾的心继续搞事。因为 Z5 的屏幕小看着不爽,又二手收了个 Xperia Z3TC,8寸 16:9屏 大小正好。同样是骁龙 800 系列 CPU,上面的这些我又得再来一遍。Z3TC官方支持的最后版本是 Android 6.0.1,固件版本 23.5.A.1.291,先用线刷更新上去。然后准备源码,奇怪的是 sonyxperiadev/kernel-copyleft 上并没有,从索尼开发者网站可以下载到存档

这回可没有大神给我移植好的 dwc3_otg 驱动直接用,得自己动手了。因为 Z5 已经是后面两代产品了,驱动也已经修改了很多,很难再进行比对,所以我选择对比 一加One 的 dwc3_otg( @PhoenixWright 的修改版本: https://forum.xda-developers.com/t/kernel-power-over-otg-host-mod.3201100/#post-64461279 ),凭着我粗浅的C语言知识把修改一点点应用过来。编译内核跟 Z5 有点不同,交叉编译环境需要换成 32 位,其它流程不变(完整命令见文末附录)。

dwc3_otg.c.patch
--- a/drivers/usb/dwc3/dwc3_otg.c 2021-02-08 15:44:20.000000000 +0800
+++ b/drivers/usb/dwc3/dwc3_otg.c 2021-02-08 15:45:03.000000000 +0800
@@ -33,6 +33,18 @@
static int max_chgr_retry_count = MAX_INVALID_CHRGR_RETRY;
module_param(max_chgr_retry_count, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(max_chgr_retry_count, "Max invalid charger retry count");
+
+//Module param for aca enabling
+static bool aca_enable = 0;
+module_param(aca_enable, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(aca_enable, "Enable ACA host mode to allow charging and host");
+
+//Define absent ID_A flag (from msm_otg module)
+#define ID_A 2
+
+//Flag for choosing either ID(host w/ vbus) or ID_A (host w/ charge) based on aca_enable toggle
+int ID_MODE;
+
static void dwc3_otg_reset(struct dwc3_otg *dotg);
static void dwc3_otg_notify_host_mode(struct usb_otg *otg, int host_mode);
@@ -198,38 +210,42 @@ static int dwc3_otg_start_host(struct us
if (!dwc->xhci)
return -EINVAL;
- if (!dotg->vbus_otg) {
- dotg->vbus_otg = devm_regulator_get(dwc->dev->parent,
- "vbus_dwc3");
- if (IS_ERR(dotg->vbus_otg)) {
- dev_err(dwc->dev, "Failed to get vbus regulator\n");
- ret = PTR_ERR(dotg->vbus_otg);
- dotg->vbus_otg = 0;
- return ret;
+ if (ID_MODE == ID) {
+ if (!dotg->vbus_otg) {
+ dotg->vbus_otg = devm_regulator_get(dwc->dev->parent,
+ "vbus_dwc3");
+ if (IS_ERR(dotg->vbus_otg)) {
+ dev_err(dwc->dev, "Failed to get vbus regulator\n");
+ ret = PTR_ERR(dotg->vbus_otg);
+ dotg->vbus_otg = 0;
+ return ret;
+ }
}
- }
+ }
if (on) {
dev_dbg(otg->phy->dev, "%s: turn on host\n", __func__);
- dwc3_otg_notify_host_mode(otg, on);
+ if (ID_MODE == ID) {
+ dwc3_otg_notify_host_mode(otg, on);
- /* register ocp notification */
- if (ext_xceiv && ext_xceiv->otg_capability &&
- ext_xceiv->ext_ocp_notification.notify) {
- ret = regulator_register_ocp_notification(
- dotg->vbus_otg,
- &ext_xceiv->ext_ocp_notification);
- if (ret)
- dev_err(otg->phy->dev,
- "unable to register ocp\n");
- }
+ /* register ocp notification */
+ if (ext_xceiv && ext_xceiv->otg_capability &&
+ ext_xceiv->ext_ocp_notification.notify) {
+ ret = regulator_register_ocp_notification(
+ dotg->vbus_otg,
+ &ext_xceiv->ext_ocp_notification);
+ if (ret)
+ dev_err(otg->phy->dev,
+ "unable to register ocp\n");
+ }
- ret = regulator_enable(dotg->vbus_otg);
- if (ret) {
- dev_err(otg->phy->dev, "unable to enable vbus_otg\n");
- dwc3_otg_notify_host_mode(otg, 0);
- return ret;
+ ret = regulator_enable(dotg->vbus_otg);
+ if (ret) {
+ dev_err(otg->phy->dev, "unable to enable vbus_otg\n");
+ dwc3_otg_notify_host_mode(otg, 0);
+ return ret;
+ }
}
/* The delay between enabling regulator and adding the
@@ -262,8 +278,10 @@ static int dwc3_otg_start_host(struct us
dev_err(otg->phy->dev,
"%s: failed to add XHCI pdev ret=%d\n",
__func__, ret);
- regulator_disable(dotg->vbus_otg);
- dwc3_otg_notify_host_mode(otg, 0);
+ if (ID_MODE == ID) {
+ regulator_disable(dotg->vbus_otg);
+ dwc3_otg_notify_host_mode(otg, 0);
+ }
return ret;
}
@@ -273,24 +291,24 @@ static int dwc3_otg_start_host(struct us
} else {
dev_dbg(otg->phy->dev, "%s: turn off host\n", __func__);
- ret = regulator_disable(dotg->vbus_otg);
- if (ret) {
- dev_err(otg->phy->dev, "unable to disable vbus_otg\n");
- return ret;
- }
+ if (ID_MODE == ID) {
+ ret = regulator_disable(dotg->vbus_otg);
+ if (ret) {
+ dev_err(otg->phy->dev, "unable to disable vbus_otg\n");
+ return ret;
+ }
- /* unregister ocp notification */
- if (ext_xceiv && ext_xceiv->otg_capability &&
- ext_xceiv->ext_ocp_notification.notify) {
- ret = regulator_register_ocp_notification(
- dotg->vbus_otg, NULL);
- if (ret)
- dev_err(otg->phy->dev,
- "unable to unregister ocp\n");
+ /* unregister ocp notification */
+ if (ext_xceiv && ext_xceiv->otg_capability &&
+ ext_xceiv->ext_ocp_notification.notify) {
+ ret = regulator_register_ocp_notification(
+ dotg->vbus_otg, NULL);
+ if (ret)
+ dev_err(otg->phy->dev,
+ "unable to unregister ocp\n");
+ }
+ dwc3_otg_notify_host_mode(otg, on);
}
-
- dwc3_otg_notify_host_mode(otg, on);
-
platform_device_del(dwc->xhci);
/*
* Perform USB hardware RESET (both core reset and DBM reset)
@@ -334,8 +352,10 @@ static int dwc3_otg_set_host(struct usb_
* required for XHCI controller before setting OTG Port Power
* TODO: Tune this delay
*/
- msleep(300);
- dwc3_otg_set_host_power(dotg);
+ if (ID_MODE == ID) {
+ msleep(300);
+ dwc3_otg_set_host_power(dotg);
+ }
} else {
otg->host = NULL;
}
@@ -499,11 +519,14 @@ static void dwc3_ext_event_notify(struct
dev_warn(phy->dev, "pm_runtime_get failed!!\n");
}
if (ext_xceiv->id == DWC3_ID_FLOAT) {
- dev_info(phy->dev, "XCVR: ID set\n");
+ dev_info(phy->dev, "XCVR: ID/ID_A set\n");
set_bit(ID, &dotg->inputs);
- } else {
- dev_info(phy->dev, "XCVR: ID clear\n");
- clear_bit(ID, &dotg->inputs);
+ set_bit(ID_A, &dotg->inputs);
+ } else if (phy->state != OTG_STATE_A_HOST) {
+ dev_dbg(phy->dev, "XCVR: ID_MODE clear\n");
+ ID_MODE = (!aca_enable) ? ID : ID_A;
+ set_bit((ID_MODE == ID) ? ID_A : ID, &dotg->inputs);
+ clear_bit(ID_MODE, &dotg->inputs);
}
if (ext_xceiv->bsv) {
@@ -681,17 +704,20 @@ static irqreturn_t dwc3_otg_interrupt(in
if ((oevt_reg & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) ||
(oevt_reg & DWC3_OEVTEN_OTGBDEVVBUSCHNGEVNT)) {
/*
- * ID sts has changed, set inputs later, in the workqueue
+ * ID_MODE sts has changed, set inputs later, in the workqueue
* function, switch from A to B or from B to A.
*/
if (oevt_reg & DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT) {
if (osts & DWC3_OTG_OSTS_CONIDSTS) {
- dev_info(phy->dev, "ID set\n");
+ dev_info(phy->dev, "ID/ID_A set\n");
set_bit(ID, &dotg->inputs);
- } else {
- dev_info(phy->dev, "ID clear\n");
- clear_bit(ID, &dotg->inputs);
+ set_bit(ID_A, &dotg->inputs);
+ } else if (phy->state != OTG_STATE_A_HOST) {
+ dev_dbg(phy->dev, "ID_MODE clear\n");
+ ID_MODE = (!aca_enable) ? ID : ID_A;
+ set_bit((ID_MODE == ID) ? ID_A : ID, &dotg->inputs);
+ clear_bit(ID_MODE, &dotg->inputs);
}
handled_irqs |= DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT;
}
@@ -741,15 +767,20 @@ void dwc3_otg_init_sm(struct dwc3_otg *d
dev_err(phy->dev, "%s: completion timeout\n", __func__);
/* We can safely assume no cable connected */
set_bit(ID, &dotg->inputs);
+ set_bit(ID_A, &dotg->inputs);
}
ext_xceiv = dotg->ext_xceiv;
dwc3_otg_reset(dotg);
+ ID_MODE = (!aca_enable) ? ID : ID_A;
if (ext_xceiv && !ext_xceiv->otg_capability) {
- if (osts & DWC3_OTG_OSTS_CONIDSTS)
+ if (osts & DWC3_OTG_OSTS_CONIDSTS) {
set_bit(ID, &dotg->inputs);
- else
- clear_bit(ID, &dotg->inputs);
+ set_bit(ID_A, &dotg->inputs);
+ } else {
+ set_bit((ID_MODE == ID) ? ID_A : ID, &dotg->inputs);
+ clear_bit(ID_MODE, &dotg->inputs);
+ }
if (osts & DWC3_OTG_OSTS_BSESVALID)
set_bit(B_SESS_VLD, &dotg->inputs);
@@ -775,6 +806,8 @@ static void dwc3_otg_sm_work(struct work
bool work = 0;
int ret = 0;
unsigned long delay = 0;
+ /* Flag to determine the current ACA host mode status */
+ static bool acaenabled;
pm_runtime_resume(phy->dev);
dev_info(phy->dev, "%s state\n", otg_state_string(phy->state));
@@ -791,8 +824,8 @@ static void dwc3_otg_sm_work(struct work
"couldn't get usb power supply\n");
}
- /* Switch to A or B-Device according to ID / BSV */
- if (!test_bit(ID, &dotg->inputs)) {
+ /* Switch to A or B-Device according to ID_MODE / BSV */
+ if (!test_bit(ID_MODE, &dotg->inputs)) {
dev_dbg(phy->dev, "!id\n");
phy->state = OTG_STATE_A_IDLE;
work = 1;
@@ -808,7 +841,7 @@ static void dwc3_otg_sm_work(struct work
break;
case OTG_STATE_B_IDLE:
- if (!test_bit(ID, &dotg->inputs)) {
+ if (!test_bit(ID_MODE, &dotg->inputs)) {
dev_dbg(phy->dev, "!id\n");
phy->state = OTG_STATE_A_IDLE;
work = 1;
@@ -921,7 +954,7 @@ static void dwc3_otg_sm_work(struct work
case OTG_STATE_B_PERIPHERAL:
if (!test_bit(B_SESS_VLD, &dotg->inputs) ||
- !test_bit(ID, &dotg->inputs)) {
+ !test_bit(ID_MODE, &dotg->inputs)) {
dev_dbg(phy->dev, "!id || !bsv\n");
dwc3_otg_start_peripheral(&dotg->otg, 0);
phy->state = OTG_STATE_B_IDLE;
@@ -933,7 +966,7 @@ static void dwc3_otg_sm_work(struct work
case OTG_STATE_A_IDLE:
/* Switch to A-Device*/
- if (test_bit(ID, &dotg->inputs)) {
+ if (test_bit(ID_MODE, &dotg->inputs)) {
dev_dbg(phy->dev, "id\n");
phy->state = OTG_STATE_B_IDLE;
dotg->vbus_retry_count = 0;
@@ -947,36 +980,46 @@ static void dwc3_otg_sm_work(struct work
/* staying on here until exit from A-Device */
} else {
phy->state = OTG_STATE_A_HOST;
- ret = dwc3_otg_start_host(&dotg->otg, 1);
- if ((ret == -EPROBE_DEFER) &&
- dotg->vbus_retry_count < 3) {
- /*
- * Get regulator failed as regulator driver is
- * not up yet. Will try to start host after 1sec
- */
- phy->state = OTG_STATE_A_IDLE;
- dev_dbg(phy->dev, "Unable to get vbus regulator. Retrying...\n");
- delay = VBUS_REG_CHECK_DELAY;
- work = 1;
- dotg->vbus_retry_count++;
- } else if (ret) {
- /*
- * Probably set_host was not called yet.
- * We will re-try as soon as it will be called
- */
- dev_dbg(phy->dev, "enter lpm as\n"
- "unable to start A-device\n");
- phy->state = OTG_STATE_A_IDLE;
- pm_runtime_put_sync(phy->dev);
- return;
+ /* Wait, as host must be enabled after power */
+ if (ID_MODE == ID_A) {
+ acaenabled = 0;
+ /* Ensure there's no charger before suspending */
+ msleep(200);
+ if (!dotg->ext_xceiv->bsv)
+ pm_runtime_put_sync(phy->dev);
+ } else {
+ ret = dwc3_otg_start_host(&dotg->otg, 1);
+ if ((ret == -EPROBE_DEFER) &&
+ dotg->vbus_retry_count < 3) {
+ /*
+ * Get regulator failed as regulator driver is
+ * not up yet. Will try to start host after 1sec
+ */
+ phy->state = OTG_STATE_A_IDLE;
+ dev_dbg(phy->dev, "Unable to get vbus regulator. Retrying...\n");
+ delay = VBUS_REG_CHECK_DELAY;
+ work = 1;
+ dotg->vbus_retry_count++;
+ } else if (ret) {
+ /*
+ * Probably set_host was not called yet.
+ * We will re-try as soon as it will be called
+ */
+ dev_dbg(phy->dev, "enter lpm as\n"
+ "unable to start A-device\n");
+ phy->state = OTG_STATE_A_IDLE;
+ pm_runtime_put_sync(phy->dev);
+ return;
+ }
}
}
break;
case OTG_STATE_A_HOST:
- if (test_bit(ID, &dotg->inputs)) {
+ if (test_bit(ID_MODE, &dotg->inputs)) {
dev_dbg(phy->dev, "id\n");
- dwc3_otg_start_host(&dotg->otg, 0);
+ if (ID_MODE == ID || acaenabled)
+ dwc3_otg_start_host(&dotg->otg, 0);
phy->state = OTG_STATE_B_IDLE;
dotg->vbus_retry_count = 0;
work = 1;
@@ -992,6 +1035,82 @@ static void dwc3_otg_sm_work(struct work
#ifdef CONFIG_USB_HOST_EXTRA_NOTIFICATION
host_send_uevent(USB_HOST_EXT_EVENT_VBUS_DROP);
#endif
+ } else if (test_bit(B_SESS_VLD, &dotg->inputs)) {
+ if (ID_MODE == ID_A && !acaenabled) {
+ dev_dbg(phy->dev, "b_sess_vld\n");
+ /* Has charger been detected? If no detect it */
+ switch (charger->chg_type) {
+ case DWC3_DCP_CHARGER:
+ case DWC3_CDP_CHARGER:
+ case DWC3_PROPRIETARY_CHARGER:
+ dwc3_otg_set_power(phy,
+ DWC3_IDEV_CHG_MAX);
+ break;
+ case DWC3_SDP_CHARGER:
+ break;
+ case DWC3_FLOATED_CHARGER:
+ if (dotg->charger_retry_count <
+ max_chgr_retry_count)
+ dotg->charger_retry_count++;
+ /*
+ * In case of floating charger, if
+ * retry count equal to max retry count
+ * notify PMIC about floating charger
+ * and put Hw in low power mode. Else
+ * perform charger detection again by
+ * calling start_detection() with false
+ * and then with true argument.
+ */
+ if (dotg->charger_retry_count ==
+ max_chgr_retry_count) {
+ charger->pulldown_dp(charger,
+ false);
+ dwc3_otg_set_power(phy, 0);
+ qpnp_chg_notify_invalid_usb();
+ pm_runtime_put_sync(phy->dev);
+ break;
+ }
+ charger->start_detection(dotg->charger,
+ false);
+
+ charger->pulldown_dp(charger, true);
+ delay = msecs_to_jiffies(100 *
+ dotg->charger_retry_count);
+ work = 1;
+ break;
+ default:
+ dev_dbg(phy->dev, "chg_det started\n");
+ charger->start_detection(charger, true);
+ return;
+ }
+
+ ret = dwc3_otg_start_host(&dotg->otg, 1);
+ if (!ret)
+ acaenabled = 1;
+ else {
+ /*
+ * Probably set_host was not called yet.
+ * We will re-try as soon as it will be called
+ */
+
+ dev_dbg(phy->dev, "enter lpm as\n"
+ "unable to start A-device\n");
+ pm_runtime_put_sync(phy->dev);
+ return;
+ }
+ }
+ } else if (ID_MODE == ID_A) {
+ /* Charger has been removed */
+ dev_dbg(phy->dev, "Charger removed, trying to suspend\n");
+ if (acaenabled) {
+ dwc3_otg_start_host(&dotg->otg, 0);
+ acaenabled = 0;
+ }
+ charger->start_detection(dotg->charger, false);
+
+ dotg->charger_retry_count = 0;
+ dwc3_otg_set_power(phy, 0);
+ pm_runtime_put_sync(phy->dev);
}
break;
@@ -1041,7 +1160,7 @@ static void dwc3_otg_reset(struct dwc3_o
/* Clear all otg events (interrupts) indications */
dwc3_writel(dotg->regs, DWC3_OEVT, 0xFFFF);
- /* Enable ID/BSV StsChngEn event*/
+ /* Enable ID_MODE/BSV StsChngEn event*/
if (ext_xceiv && !ext_xceiv->otg_capability)
dwc3_writel(dotg->regs, DWC3_OEVTEN,
DWC3_OEVTEN_OTGCONIDSTSCHNGEVNT |
@@ -1084,7 +1203,7 @@ int dwc3_otg_init(struct dwc3 *dwc)
return -ENOMEM;
}
- /* DWC3 has separate IRQ line for OTG events (ID/BSV etc.) */
+ /* DWC3 has separate IRQ line for OTG events (ID_MODE/BSV etc.) */
dotg->irq = platform_get_irq_byname(to_platform_device(dwc->dev),
"otg_irq");
if (dotg->irq < 0) {

接下来是组装 boot.img,按照 Z5 的经验,下载个 stock kernel 来用就行了,可 Z3TC 的 stock kernel 愣是没找到(或者下载地址挂了)。那就自己从 FTF 提取吧,跟着某图片都挂了的教程操作:把 FTF 解压得到 kernel.sin,再用 flashTool sinEditor 提取出 kernel.elf。按照教程的说法,此时 kernel.elf 就是 boot.img (原话:“rename your 'kernel.elf' to 'boot.img'”),可当我刷入 kernel.elf 时 fastboot 报错说这不是个 boot image (remote: image is not a boot image)。一开始怀疑是提取的问题,换了 unSIN 工具尝试,甚至检查了 sin 文件结构是否正确,但都没有问题。我就纳闷了,检查 kernel.elf 文件的二进制内容,确实不对。boot image 开头应该是 ANDROID magic,而 kernel.elf 的开头是 ELF 。既然是真的 ELF,那就再用 flashTool extract elf 提取一次吧,得到 kernel.elf.Image, kernel.elf.qcdt, kernel.elf.ramdisk.gz。这跟某古早mkelf教程的描述也不完全一致,看文件大小和格式猜测这仨应该是 kernel, dt.img 和 ramdisk.img,姑且一试手动用 mkbootimg 再组成 boot.img 刷入,成功开机。至此费了九牛二虎之力成功得到 stock boot.img。

elf magic

最后替换 kernel 重新打包得到 boot.img。记得把 dwc3.aca_enable=Y 加到 cmdline 里面。

sgp611-patched-kernel-version

相关文件

Z5

Z3TC

完整命令

环境:Ubuntu 16.04

Z5 boot.img 制作

cd ~ # /home/work: 32.0.A.6.200-boot.img, otg-aca-xperia_z5.diff

# 安装编译依赖和工具
sudo apt-get install -y build-essential kernel-package libncurses5-dev bzip2 cpio git

# 安装交叉编译工具
git clone --depth 1 https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9
git fetch android-5.1.1_r37
git checkout -b android-5.1.1_r37 FETCH_HEAD
export PATH=/home/work/aarch64-linux-android-4.9/bin:$PATH
export CROSS_COMPILE=aarch64-linux-android-

# 安装 mkbootimg_tools
git clone --depth 1 https://github.com/xiaolu/mkbootimg_tools.git # commitid: 2d8905e
export PATH=/home/work/mkbootimg_tools:$PATH

# 准备 ramdisk.img
mkboot 32.0.A.6.200-boot.img boot_unpacked # 解包

# 准备源码
wget https://github.com/sonyxperiadev/kernel-copyleft/archive/32.0.A.6.xxx.zip
unzip 32.0.A.6.xxx.zip
cd kernel-copyleft-32.0.A.6.xxx
git init
git apply ../otg-aca-xperia_z5.diff

# 开始编译
export ARCH=arm64
export KBUILD_DIFFCONFIG=sumire_dsds_diffconfig # 我的是 E6683 双卡,具体看 README_Xperia
make kitakami_defconfig
make -j4 # 4核CPU

# 组装
cd ..
cp kernel-copyleft-32.0.A.6.xxx/arch/arm64/boot/Image/kernel.img boot_unpacked/kernel # 替换 kernel
vim boot_unpacked/img_info # cmdline 增加 dwc3.aca_enable=Y 等配置
mkboot boot_unpacked my-aca-boot.img # 打包出 boot.img

# 刷入
fastboot flash boot my-aca-boot.img

Z3TC boot.img 制作

省略部分重复步骤

cd ~ # /home/work
ls
# > sgp611-aca-dwc3_otg.c                                     # 修改后的 dwc3_otg.c
# > android_kernel_sony_23.5.A.1.291                          # 源码文件夹
# > kernel.elf.Image, kernel.elf.qcdt, kernel.elf.ramdisk.gz  # 从 kernel.elf <- kernel.sin 解包得到

# 得到 stock kernel
mkbootimg \
    --kernel kernel.elf.Image \
    --ramdisk kernel.elf.ramdisk.gz \
    --cmdline "console=ttyHSL0,115200,n8 androidboot.hardware=qcom user_debug=23 msm_rtb.filter=0x3b7 ehci-hcd.park=3 androidboot.bootdevice=msm_sdcc.1 vmalloc=300M dwc3.maximum_speed=high dwc3_msm.prop_chg_detect=Y" \
    --base 0x00000000 \
    --pagesize 2048 \
    --dt kernel.elf.qcdt \
    --ramdisk_offset 0x02000000 --tags_offset 0x01E00000 \
    --output boot.img

# 准备 ramdisk.img
mkboot boot.img boot_unpacked # 解包

# 安装交叉编译工具
git clone --depth 1 https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9
git fetch android-6.0.1_r81
git checkout -b android-6.0.1_r81 FETCH_HEAD
export PATH=/home/work/arm-linux-androideabi-4.9/bin:$PATH
export CROSS_COMPILE=arm-linux-androideabi-


# 准备源码
cd android_kernel_sony_23.5.A.1.291
cp ../sgp611-aca-dwc3_otg.c drivers/usb/dwc3/dwc3_otg.c

# 开始编译
export ARCH=arm
export KBUILD_DIFFCONFIG=shinano_scorpion_windy_defconfig # 我的是 SGP611,具体看 README_Xperia
make shinano_sirius_defconfig
make -j4 # 4核CPU

# 组装
cd ..
cp android_kernel_sony_23.5.A.1.291/arch/arm/boot/zImage-dtb boot_unpacked/kernel # 替换 kernel
vim boot_unpacked/img_info # cmdline 增加 dwc3.aca_enable=Y 配置
mkboot boot_unpacked my-aca-boot.img # 打包出 boot.img