Skip to content

Instantly share code, notes, and snippets.

@599316527
Last active February 8, 2021 07:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 599316527/d4380b1f3f8c8dc413bb90cac729a406 to your computer and use it in GitHub Desktop.
Save 599316527/d4380b1f3f8c8dc413bb90cac729a406 to your computer and use it in GitHub Desktop.
[KERNEL] Z3TC (SGP611) USB OTG (host mode) + simultaneous charging!

[KERNEL] Z3TC (SGP611) USB OTG (host mode) + simultaneous charging!

参考 USB OTG (host mode) + simultaneous charging!Power over OTG host mod 修改 android_kernel_sony_23.5.A.1.291 的 dwc3_otg.c 驱动实现 Xperia Z3 Tablet Compact 的 OTG 的同时充电。

According to the thread USB OTG (host mode) + simultaneous charging!Power over OTG host mod, I customized usb driver dwc3_otg.c based on android_kernel_sony_23.5.A.1.291 to attain OTG and charging simultaneously for Xperia Z3 Tablet Compact.

注:boot.img cmdline 需要增加 dwc3.aca_enable=Y 来启用。

Note: dwc3.aca_enable=Y should be appended to cmdline of boot.img to enable the feature.

For more info

--- 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) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment