linux-gbmc: Add RoT mailbox backoff to everspin

When writing to the 64KB RAM-backed RoT mailbox region on Google
Hoth/Dauntless SPI NOR parts (at 0x700000 on 32MB hothb2-512 parts, or
the top 64KB on 64MB/128MB parts), the RoT intercepts the write and
holds the flash busy (SR_WIP) for several milliseconds while processing
the mailbox command. Tight status register polling in
spi_nor_wait_till_ready causes excessive SPI bus traffic and CPU usage.

Override the MTD _write callback in everspin.c so that status polling on
pages within the 64KB mailbox region uses an exponential backoff sleep
(250us doubling up to 20ms), while non-mailbox flash writes continue
using standard spi_nor_wait_till_ready() without backoff delay.

Tested: Verified on hardware that hothd mailbox transactions complete
with minimal SPI transfers instead of thousands of tight SR polls, while
non-mailbox flash operations remain unaffected.
Fusion-Link: fusion2 N/A

Google-Bug-Id: 565629335
Change-Id: I17eb681995d38bbcdf4d6015159cb2d9f5eeb13d
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c b/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c
index ae815af..58afbbd 100644
--- a/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c
+++ b/recipes-kernel/linux/files/linux-gbmc/drivers/mtd/spi-nor/everspin.c
@@ -1,6 +1,10 @@
 // We replace the everspin spi nor file as it has a part that we will
 // never use with gBMC systems.
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/mtd/mtd.h>
 #include <linux/mtd/spi-nor.h>
+#include <linux/sizes.h>
 #include <linux/version.h>
 #include "core.h"
 
@@ -26,8 +30,105 @@
 	GINFO("dauntlessd2-2048", SNOR_ID(0x26, 0x22, 0x1b), 2048),
 };
 
+#define HOTH_MBOX_SIZE		0x10000
+#define HOTH_MBOX_OFFSET_32M	0x700000
+#define MBOX_WAIT_JIFFIES	(40UL * HZ)
+
+static inline bool google_is_mbox_addr(struct mtd_info *mtd, loff_t addr)
+{
+	loff_t mbox_offset;
+
+	if (mtd->size == SZ_32M)
+		mbox_offset = HOTH_MBOX_OFFSET_32M;
+	else if (mtd->size > HOTH_MBOX_SIZE)
+		mbox_offset = mtd->size - HOTH_MBOX_SIZE;
+	else
+		return false;
+
+	return addr >= mbox_offset && addr < mbox_offset + HOTH_MBOX_SIZE;
+}
+
+static int google_nor_wait_till_ready_backoff(struct spi_nor *nor)
+{
+	unsigned long deadline = jiffies + MBOX_WAIT_JIFFIES;
+	unsigned int backoff_us = 250;
+	int timeout = 0, ret;
+
+	while (!timeout) {
+		if (time_after_eq(jiffies, deadline))
+			timeout = 1;
+
+		ret = spi_nor_read_sr(nor, nor->bouncebuf);
+		if (ret)
+			return ret;
+		if (!(nor->bouncebuf[0] & SR_WIP))
+			return 0;
+		if (timeout)
+			break;
+
+		if (backoff_us < 20000) {
+			usleep_range(backoff_us, backoff_us * 3 / 2);
+			backoff_us = min(backoff_us * 2, 20000U);
+		} else {
+			msleep(20);
+		}
+	}
+
+	dev_dbg(nor->dev, "flash mailbox operation timed out\n");
+	return -ETIMEDOUT;
+}
+
+static int google_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
+			    size_t *retlen, const u_char *buf)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+	u32 page_size = nor->params->page_size;
+	size_t i;
+	ssize_t ret;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0)
+	ret = spi_nor_lock_and_prep(nor);
+#else
+	ret = spi_nor_prep_and_lock(nor);
+#endif
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; ) {
+		ssize_t written;
+		loff_t addr = to + i;
+		size_t page_offset = addr & (page_size - 1);
+		size_t page_remain = min_t(size_t, page_size - page_offset,
+					   len - i);
+
+		ret = spi_nor_write_enable(nor);
+		if (ret)
+			goto write_err;
+
+		ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
+		if (ret < 0)
+			goto write_err;
+		written = ret;
+
+		if (google_is_mbox_addr(mtd, addr))
+			ret = google_nor_wait_till_ready_backoff(nor);
+		else
+			ret = spi_nor_wait_till_ready(nor);
+		if (ret)
+			goto write_err;
+
+		*retlen += written;
+		i += written;
+	}
+
+write_err:
+	spi_nor_unlock_and_unprep(nor);
+	return ret;
+}
+
 static void google_nor_default_init(struct spi_nor *nor)
 {
+	nor->mtd._write = google_nor_write;
 	nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
 #if LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0)
 	nor->params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode;