修复展锐SL8541E 偶现开机无法启动OTG

哎,又是紫光展锐,真的拉啊!!!

问题是这样的,当USB口插上OTG线再开机,会偶现无法启动OTG。这个问题排查了好久,现在终于改好了,下面记录一下分析过程。

异常log【上】和正常log【下】

前面部分就不贴了,都是一样的。可以看到开机时通知器会上报两个通知,先来的是vbus notify,后来的id notify。出现异常时vbus的通知器函数会去设置状态机,但是状态机的work queue还没跑完呢,id的通知器函数就开始执行了。经过分析,正确设置状态机就需要让各个通知器先等状态机的work queue跑完了再进行。

最终修改方案,使用一个原子量来给id、vbus通知器回调函数与状态机的work_queue做同步。这里为什么要用原子量,因为展锐的这个驱动写的太烂了,work_queue有可能会出现长时间卡在里面,因此不得不使用原子量,死等肯定是不行的。

diff --git a/drivers/usb/musb/musb_sprd.c b/drivers/usb/musb/musb_sprd.c
index d32027a..c8dc027 100644
--- a/drivers/usb/musb/musb_sprd.c
+++ b/drivers/usb/musb/musb_sprd.c
@@ -34,6 +34,7 @@
 #include <linux/wait.h>
 #include <linux/mfd/syscon.h>
 #include <linux/regmap.h>
+#include <linux/mutex.h>
 
 #include "musb_core.h"
 #include "sprd_musbhsdma.h"
@@ -44,6 +45,7 @@
 
 MODULE_DESCRIPTION(DRIVER_INFO);
 MODULE_LICENSE("GPL v2");
+static DEFINE_MUTEX(musb_sprd_extcon_mutex);
 
 #define MUSB_RECOVER_TIMEOUT 100
 struct sprd_glue {
@@ -53,6 +55,8 @@ struct sprd_glue {
 	struct phy		*phy;
 	struct usb_phy		*xceiv;
 	struct regulator	*vbus;
+	struct regulator	*eth;
+	struct regulator	*usbnet;
 	struct wakeup_source	pd_wake_lock;
 	struct regmap		*pmu;
 
@@ -71,6 +75,9 @@ struct sprd_glue {
 	struct notifier_block		vbus_nb;
 	struct notifier_block		id_nb;
 
+	atomic_t	workqueue_running;	/* for extcon sync */
+	atomic_t	extcon_running;	/* for extcon sync */
+
 	bool		bus_active;
 	bool		vbus_active;
 	bool		charging_mode;
@@ -85,6 +92,13 @@ struct sprd_glue {
 };
 
 static int boot_charging;
+static char* musb_dr_mode_show[] = {
+	"unknown",
+	"host",
+	"device",
+	"otg",
+};
+
 static void sprd_musb_enable(struct musb *musb)
 {
 	struct sprd_glue *glue = dev_get_drvdata(musb->controller->parent);
@@ -452,41 +466,44 @@ static int musb_sprd_vbus_notifier(struct notifier_block *nb,
 				unsigned long event, void *data)
 {
 	struct sprd_glue *glue = container_of(nb, struct sprd_glue, vbus_nb);
-	unsigned long flags;
+	int try = 500;
+
+	mutex_lock(&musb_sprd_extcon_mutex);
+	while (atomic_read(&glue->workqueue_running) && try--)
+		msleep(1);
 
 	if (event) {
-		spin_lock_irqsave(&glue->lock, flags);
 		if (glue->vbus_active == 1 || glue->dr_mode == USB_DR_MODE_HOST) {
-			spin_unlock_irqrestore(&glue->lock, flags);
 			dev_info(glue->dev,
 				"ignore device connection detected from VBUS GPIO.\n");
+			mutex_unlock(&musb_sprd_extcon_mutex);
 			return 0;
 		}
 		if (glue->dpdm_switch)
 			 gpiod_set_value_cansleep(glue->dpdm_switch, 0);
 		glue->vbus_active = 1;
 		glue->wq_mode = USB_DR_MODE_PERIPHERAL;
+		atomic_set(&glue->workqueue_running, 1);
 		queue_work(system_unbound_wq, &glue->work);
-		spin_unlock_irqrestore(&glue->lock, flags);
 		dev_info(glue->dev,
 			"device connection detected from VBUS GPIO.\n");
 	} else {
-		spin_lock_irqsave(&glue->lock, flags);
 		if (glue->vbus_active == 0 || glue->dr_mode == USB_DR_MODE_HOST) {
-			spin_unlock_irqrestore(&glue->lock, flags);
 			dev_info(glue->dev,
 				"ignore device disconnect detected from VBUS GPIO.\n");
+			mutex_unlock(&musb_sprd_extcon_mutex);
 			return 0;
 		}
 
 		glue->vbus_active = 0;
 		glue->wq_mode = USB_DR_MODE_PERIPHERAL;
+		atomic_set(&glue->workqueue_running, 1);
 		queue_work(system_unbound_wq, &glue->work);
-		spin_unlock_irqrestore(&glue->lock, flags);
 		dev_info(glue->dev,
 			"device disconnect detected from VBUS GPIO.\n");
 	}
 
+	mutex_unlock(&musb_sprd_extcon_mutex);
 	return 0;
 }
 
@@ -494,41 +511,44 @@ static int musb_sprd_id_notifier(struct notifier_block *nb,
 				unsigned long event, void *data)
 {
 	struct sprd_glue *glue = container_of(nb, struct sprd_glue, id_nb);
-	unsigned long flags;
+	int try = 500;
+
+	mutex_lock(&musb_sprd_extcon_mutex);
+	while (atomic_read(&glue->workqueue_running) && try--)
+		msleep(1);
 
 	if (event) {
-		spin_lock_irqsave(&glue->lock, flags);
 		if (glue->vbus_active == 1 || glue->dr_mode == USB_DR_MODE_PERIPHERAL) {
-			spin_unlock_irqrestore(&glue->lock, flags);
 			dev_info(glue->dev,
 				"ignore host connection detected from ID GPIO.\n");
+			mutex_unlock(&musb_sprd_extcon_mutex);
 			return 0;
 		}
 		if (glue->dpdm_switch)
 			 gpiod_set_value_cansleep(glue->dpdm_switch, 1);
 		glue->vbus_active = 1;
 		glue->wq_mode = USB_DR_MODE_HOST;
+		atomic_set(&glue->workqueue_running, 1);
 		queue_work(system_unbound_wq, &glue->work);
-		spin_unlock_irqrestore(&glue->lock, flags);
 		dev_info(glue->dev,
 			"host connection detected from ID GPIO.\n");
 	} else {
-		spin_lock_irqsave(&glue->lock, flags);
 		if (glue->vbus_active == 0 || glue->dr_mode == USB_DR_MODE_PERIPHERAL) {
-			spin_unlock_irqrestore(&glue->lock, flags);
 			dev_info(glue->dev,
 				"ignore host disconnect detected from ID GPIO.\n");
+			mutex_unlock(&musb_sprd_extcon_mutex);
 			return 0;
 		}
 
 		glue->vbus_active = 0;
 		glue->wq_mode = USB_DR_MODE_HOST;
+		atomic_set(&glue->workqueue_running, 1);
 		queue_work(system_unbound_wq, &glue->work);
-		spin_unlock_irqrestore(&glue->lock, flags);
 		dev_info(glue->dev,
 			"host disconnect detected from ID GPIO.\n");
 	}
 
+	mutex_unlock(&musb_sprd_extcon_mutex);
 	return 0;
 }
 
@@ -651,16 +671,16 @@ static bool musb_sprd_is_connect_host(struct sprd_glue *glue)
 	return false;
 }
 
-static __init int musb_sprd_charger_mode(char *str)
-{
-	if (strcmp(str, "charger"))
-		boot_charging = 0;
-	else
-		boot_charging = 1;
+// static __init int musb_sprd_charger_mode(char *str)
+// {
+// 	if (strcmp(str, "charger"))
+// 		boot_charging = 0;
+// 	else
+// 		boot_charging = 1;
 
-	return 0;
-}
-__setup("androidboot.mode=", musb_sprd_charger_mode);
+// 	return 0;
+// }
+// __setup("androidboot.mode=", musb_sprd_charger_mode);
 
 static void sprd_musb_recover_work(struct work_struct *work)
 {
@@ -704,13 +724,17 @@ static void sprd_musb_work(struct work_struct *work)
 	int ret;
 	int cnt = 100;
 
+	dev_info(glue->dev, "enter sprd_musb_work()\n");
+	atomic_set(&glue->workqueue_running, 1);
 	spin_lock_irqsave(&glue->lock, flags);
 	current_mode = glue->wq_mode;
 	current_state = glue->vbus_active;
 	glue->wq_mode = USB_DR_MODE_UNKNOWN;
 	spin_unlock_irqrestore(&glue->lock, flags);
-	if (current_mode == USB_DR_MODE_UNKNOWN)
+	if (current_mode == USB_DR_MODE_UNKNOWN) {
+		atomic_set(&glue->workqueue_running, 0);
 		return;
+	}
 
 	/*
 	 * There is a hidden danger, when system is going to suspend.
@@ -780,7 +804,10 @@ static void sprd_musb_work(struct work_struct *work)
 		if (glue->dr_mode == USB_DR_MODE_HOST) {
 			if (!glue->vbus) {
 				glue->vbus = devm_regulator_get(glue->dev, "vbus");
-				if (IS_ERR(glue->vbus)) {
+				glue->eth = devm_regulator_get(glue->dev, "eth");
+				glue->usbnet = devm_regulator_get(glue->dev, "usbnet");
+				if (IS_ERR(glue->vbus) || IS_ERR(glue->eth) \
+					|| IS_ERR(glue->usbnet)) {
 					dev_err(glue->dev,
 						"unable to get vbus supply\n");
 					glue->vbus = NULL;
@@ -788,10 +815,12 @@ static void sprd_musb_work(struct work_struct *work)
 				}
 			}
 			ret = regulator_enable(glue->vbus);
+			ret = regulator_enable(glue->eth);
+			ret = regulator_enable(glue->usbnet);
 			if (ret) {
 				dev_err(glue->dev,
 					"Failed to enable vbus: %d\n", ret);
-				goto end;
+				// goto end;
 			}
 		}
 
@@ -869,10 +898,12 @@ static void sprd_musb_work(struct work_struct *work)
 
 		if (glue->dr_mode == USB_DR_MODE_HOST && glue->vbus) {
 			ret = regulator_disable(glue->vbus);
+			ret = regulator_disable(glue->eth);
+			ret = regulator_disable(glue->usbnet);
 			if (ret) {
 				dev_err(glue->dev,
 					"Failed to disable vbus: %d\n", ret);
-				goto end;
+				// goto end;
 			}
 		}
 
@@ -909,6 +940,7 @@ static void sprd_musb_work(struct work_struct *work)
 	glue->schedule_work_done_flag = 1;
 	__pm_relax(&glue->pd_wake_lock);
 	enable_irq(glue->vbus_irq);
+	atomic_set(&glue->workqueue_running, 0);
 }
 
 /**
@@ -1031,7 +1063,38 @@ static ssize_t mode_switch_store(struct device *dev,
 	return count;
 }
 
-DEVICE_ATTR_WO(mode_switch);
+static ssize_t mode_switch_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct sprd_glue *glue = dev_get_drvdata(dev);
+	const char *str = NULL;
+
+	switch (glue->dr_mode) {
+		case USB_DR_MODE_UNKNOWN :
+			str = musb_dr_mode_show[USB_DR_MODE_UNKNOWN];
+			break;
+
+		case USB_DR_MODE_HOST :
+			str = musb_dr_mode_show[USB_DR_MODE_HOST];
+			break;
+
+		case USB_DR_MODE_PERIPHERAL :
+			str = musb_dr_mode_show[USB_DR_MODE_PERIPHERAL];
+			break;
+
+		case USB_DR_MODE_OTG :
+			str = musb_dr_mode_show[USB_DR_MODE_OTG];
+			break;
+
+		default:
+			str = musb_dr_mode_show[USB_DR_MODE_UNKNOWN];
+			break;
+	}
+
+	return sprintf(buf, "%s\n", str);
+}
+
+DEVICE_ATTR_RW(mode_switch);
 
 static ssize_t maximum_speed_show(struct device *dev,
 				  struct device_attribute *attr, char *buf)
@@ -1127,6 +1190,9 @@ static int musb_sprd_probe(struct platform_device *pdev)
 	else
 		dev_err(&pdev->dev, "Invalid or missing 'dr_mode' property\n");
 
+	atomic_set(&glue->workqueue_running, 0);
+	atomic_set(&glue->extcon_running, 0);
+
 	glue->clk = devm_clk_get(dev, "core_clk");
 	if (IS_ERR(glue->clk)) {
 		dev_err(dev, "no core clk specified\n");
@@ -1148,6 +1214,8 @@ static int musb_sprd_probe(struct platform_device *pdev)
 	if (pdata.mode == MUSB_PORT_MODE_HOST ||
 		pdata.mode == MUSB_PORT_MODE_DUAL_ROLE) {
 		glue->vbus = devm_regulator_get(dev, "vbus");
+		glue->eth = devm_regulator_get(dev, "eth");
+		glue->usbnet = devm_regulator_get(dev, "usbnet");
 		if (IS_ERR(glue->vbus)) {
 			ret = PTR_ERR(glue->vbus);
 			dev_warn(dev, "unable to get vbus supply %d\n", ret);
@@ -1229,7 +1297,7 @@ static int musb_sprd_probe(struct platform_device *pdev)
 			goto err_glue_musb;
 		}
 		glue->id_nb.notifier_call = musb_sprd_id_notifier;
-		glue->id_edev = extcon_get_edev_by_phandle(glue->dev, 1);
+		glue->id_edev = extcon_get_edev_by_phandle(glue->dev, 0);
 		if (IS_ERR(glue->id_edev)) {
 			glue->id_edev = NULL;
 			dev_info(dev, "No separate ID extcon device.\n");
@@ -1390,6 +1458,8 @@ static int musb_sprd_suspend(struct device *dev)
 	if (musb->is_offload && !musb->offload_used) {
 		if (glue->vbus) {
 			ret = regulator_disable(glue->vbus);
+			ret = regulator_disable(glue->eth);
+			ret = regulator_disable(glue->usbnet);
 			if (ret < 0)
 				dev_err(glue->dev,
 					"Failed to disable vbus: %d\n", ret);
@@ -1416,6 +1486,8 @@ static int musb_sprd_resume(struct device *dev)
 	if (musb->is_offload && !musb->offload_used) {
 		if (glue->vbus) {
 			ret = regulator_enable(glue->vbus);
+			ret = regulator_enable(glue->eth);
+			ret = regulator_enable(glue->usbnet);
 			if (ret < 0)
 				dev_err(glue->dev,
 					"Failed to enable vbus: %d\n", ret);

如果本文对你有帮助,请不要吝啬你的赞!转载请务必注明来源!

PYPYN.COM 版权所有