From: David Bauer <mail@david-bauer.net>
Date: Tue, 2 Jan 2025 19:22:40 +0100
Subject: [PATCH] reset: ath79: reset ETH switch for AR9344

According to datasheet, on AR9344 the switch and switch analog need to
be reset first before initiating a full reset.

Resetting these systems fixes spurious reset hangs on Atheros AR9344
SoCs.

Link: https://github.com/freifunk-gluon/gluon/issues/2904

Signed-off-by: David Bauer <mail@david-bauer.net>

--- a/drivers/reset/reset-ath79.c
+++ b/drivers/reset/reset-ath79.c
@@ -12,8 +12,11 @@
 #include <linux/platform_device.h>
 #include <linux/reset-controller.h>
 #include <linux/reboot.h>
+#include <linux/delay.h>
+#include <linux/of.h>
 
 struct ath79_reset {
+	struct platform_device *pdev;
 	struct reset_controller_dev rcdev;
 	struct notifier_block restart_nb;
 	void __iomem *base;
@@ -21,16 +24,13 @@ struct ath79_reset {
 };
 
 #define FULL_CHIP_RESET 24
+#define ETH_SWITCH_RESET 8
+#define ETH_SWITCH_ARESET 12
 
-static int ath79_reset_update(struct reset_controller_dev *rcdev,
+static void __ath79_reset_update_unlocked(struct ath79_reset *ath79_reset,
 			unsigned long id, bool assert)
 {
-	struct ath79_reset *ath79_reset =
-		container_of(rcdev, struct ath79_reset, rcdev);
-	unsigned long flags;
 	u32 val;
-
-	spin_lock_irqsave(&ath79_reset->lock, flags);
 	val = readl(ath79_reset->base);
 	if (assert)
 		val |= BIT(id);
@@ -39,6 +39,17 @@ static int ath79_reset_update(struct res
 	writel(val, ath79_reset->base);
 	/* Flush cache */
 	readl(ath79_reset->base);
+}
+
+static int ath79_reset_update(struct reset_controller_dev *rcdev,
+			unsigned long id, bool assert)
+{
+	struct ath79_reset *ath79_reset =
+		container_of(rcdev, struct ath79_reset, rcdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&ath79_reset->lock, flags);
+	__ath79_reset_update_unlocked(ath79_reset, id, assert);
 	spin_unlock_irqrestore(&ath79_reset->lock, flags);
 
 	return 0;
@@ -79,8 +90,28 @@ static int ath79_reset_restart_handler(s
 {
 	struct ath79_reset *ath79_reset =
 		container_of(nb, struct ath79_reset, restart_nb);
+	unsigned long flags;
 
-	ath79_reset_assert(&ath79_reset->rcdev, FULL_CHIP_RESET);
+	spin_lock_irqsave(&ath79_reset->lock, flags);
+
+	if (of_device_is_compatible(ath79_reset->pdev->dev.of_node, "qca,ar9340-reset")) {
+		/**
+		 * AR9344 has been observed to hang on reboot in rare cases.
+		 *
+		 * Datasheet states to reset the ETH switch systems before asserting
+		 * full chip reset. See page 111 of the AR9344 datasheet.
+		 */
+		__ath79_reset_update_unlocked(ath79_reset, ETH_SWITCH_RESET, true);
+		mdelay(1);
+		__ath79_reset_update_unlocked(ath79_reset, ETH_SWITCH_ARESET, true);
+		mdelay(1);
+		__ath79_reset_update_unlocked(ath79_reset, FULL_CHIP_RESET, true);
+		mdelay(10);
+	} else {
+		__ath79_reset_update_unlocked(ath79_reset, FULL_CHIP_RESET, true);
+	}
+
+	spin_unlock_irqrestore(&ath79_reset->lock, flags);
 
 	return NOTIFY_DONE;
 }
@@ -95,6 +126,8 @@ static int ath79_reset_probe(struct plat
 	if (!ath79_reset)
 		return -ENOMEM;
 
+	ath79_reset->pdev = pdev;
+
 	ath79_reset->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(ath79_reset->base))
 		return PTR_ERR(ath79_reset->base);
