linux: aspeed: Add Aspeed sdhci driver fixes

This commit adds a set of sdhci driver fixes for the eMMC. The primary
patch we're interested in is this one:

  0004-mmc-aspeed-fix-sdhci-software-reset-can-t-be-cleared.patch

That change reworks the sdhci reset functionality, so that we can
recover from any transient errors that might occur during eMMC
driver initialization.

The remaining patches are additional fixes from Aspeed's linux repo that
the reset fix depends on. For reference, Aspeed's linux repo is here:
https://github.com/AspeedTech-BMC/linux

Tested:
Rebooted the BMC many times on several Astoria machines that are known
to be vulnerable to b/267685954. The eMMC initialization succeeded
every time.
pzcap7: 40 BMC reboots
pzcbk6: 36 BMC reboots
obbnc3: 11 BMC reboots

Google-Bug-Id: 267685954
Change-Id: I64d61f263fc521e5a5044b8f7bdd2fa92786a171
Signed-off-by: John Wedig <johnwedig@google.com>
(cherry picked from commit 9280acc297387c9ac8bef3d0549b8304269eefbc)
diff --git a/recipes-kernel/linux/files/0001-sdhci-aspeed-Add-SDR50-support.patch b/recipes-kernel/linux/files/0001-sdhci-aspeed-Add-SDR50-support.patch
new file mode 100644
index 0000000..a7073b0
--- /dev/null
+++ b/recipes-kernel/linux/files/0001-sdhci-aspeed-Add-SDR50-support.patch
@@ -0,0 +1,55 @@
+From c2d1ae608b7ddeac3e9ef719f2103ac83cf35b8b Mon Sep 17 00:00:00 2001
+From: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+Date: Sun, 16 Jan 2022 14:43:57 +0800
+Subject: [PATCH 1/4] sdhci: aspeed: Add SDR50 support
+
+From the analog waveform analysis result, SD/SDIO controller
+of AST2600 cannot always work well with 200MHz. The upper bound
+stable frequency for SD/SDIO controller is 100MHz. Thus, SDR50
+supported bit, instead of SDR104, in capability 2 register
+should be set in advance.
+
+Patch Tracking Bug: b/278581540
+Upstream info: Aspeed will submit this upstream. Currently, the commit
+can be found here: https://github.com/AspeedTech-BMC/linux/commit/d1643c70c82e8729f41bd6b41c13511dd9ec0b2c
+Upstream-Status: Pending
+Justification: Aspeed will submit this change upstream.
+Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+Change-Id: I7c9df48c9a751d230cfcb603188a7d92bcf83cb8
+---
+ drivers/mmc/host/sdhci-of-aspeed.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c
+index 6e4e132903a6..ed3ce43f175b 100644
+--- a/drivers/mmc/host/sdhci-of-aspeed.c
++++ b/drivers/mmc/host/sdhci-of-aspeed.c
+@@ -35,6 +35,7 @@
+ #define ASPEED_SDC_CAP1_1_8V           (0 * 32 + 26)
+ /* SDIO{14,24} */
+ #define ASPEED_SDC_CAP2_SDR104         (1 * 32 + 1)
++#define ASPEED_SDC_CAP2_SDR50          (1 * 32 + 0)
+ 
+ struct aspeed_sdc {
+ 	struct clk *clk;
+@@ -410,11 +411,17 @@ static int aspeed_sdhci_probe(struct platform_device *pdev)
+ 	sdhci_get_of_property(pdev);
+ 
+ 	if (of_property_read_bool(np, "mmc-hs200-1_8v") ||
++	    of_property_read_bool(np, "sd-uhs-sdr50") ||
+ 	    of_property_read_bool(np, "sd-uhs-sdr104")) {
+ 		aspeed_sdc_set_slot_capability(host, dev->parent, ASPEED_SDC_CAP1_1_8V,
+ 					       true, slot);
+ 	}
+ 
++	if (of_property_read_bool(np, "sd-uhs-sdr50")) {
++		aspeed_sdc_set_slot_capability(host, dev->parent, ASPEED_SDC_CAP2_SDR50,
++			true, slot);
++	}
++
+ 	if (of_property_read_bool(np, "sd-uhs-sdr104")) {
+ 		aspeed_sdc_set_slot_capability(host, dev->parent, ASPEED_SDC_CAP2_SDR104,
+ 					       true, slot);
+-- 
+2.40.0.634.g4ca3ef3211-goog
+
diff --git a/recipes-kernel/linux/files/0002-arm-dts-aspeed-Change-eMMC-device-compatible.patch b/recipes-kernel/linux/files/0002-arm-dts-aspeed-Change-eMMC-device-compatible.patch
new file mode 100644
index 0000000..6db05a1
--- /dev/null
+++ b/recipes-kernel/linux/files/0002-arm-dts-aspeed-Change-eMMC-device-compatible.patch
@@ -0,0 +1,111 @@
+From 0a76107f690d7172b9cfe6567bb4f8efc47ffc59 Mon Sep 17 00:00:00 2001
+From: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+Date: Sun, 16 Jan 2022 15:29:53 +0800
+Subject: [PATCH 2/4] arm: dts: aspeed: Change eMMC device compatible
+
+Since the eMMC device's delay parameters are different from
+the SD's, a new compatible should be used to distinguish
+between eMMC and SD device.
+
+Patch Tracking Bug: b/278581875
+Upstream info: Aspeed will submit this upstream. Currently, the commit
+can be found here: https://github.com/AspeedTech-BMC/linux/commit/c4c3998abaef9ab5e73acf28876161ff2e94004f
+Upstream-Status: Pending
+Justification: Aspeed will submit this change upstream
+Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+Change-Id: Ie1291a04bfa28c7a6b7bba2ef4b6cdd6a56bb1cb
+---
+ arch/arm/boot/dts/aspeed-g6.dtsi   |  2 +-
+ drivers/mmc/host/sdhci-of-aspeed.c | 38 ++++++++++++++++++++++++++++++
+ 2 files changed, 39 insertions(+), 1 deletion(-)
+
+diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi
+index 8c0de3f27883..e18954361761 100644
+--- a/arch/arm/boot/dts/aspeed-g6.dtsi
++++ b/arch/arm/boot/dts/aspeed-g6.dtsi
+@@ -664,7 +664,7 @@ sdhci1: sdhci@1e740200 {
+ 			};
+ 
+ 			emmc_controller: sdc@1e750000 {
+-				compatible = "aspeed,ast2600-sd-controller";
++				compatible = "aspeed,ast2600-emmc";
+ 				reg = <0x1e750000 0x100>;
+ 				#address-cells = <1>;
+ 				#size-cells = <1>;
+diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c
+index ed3ce43f175b..9f3bd8f407ac 100644
+--- a/drivers/mmc/host/sdhci-of-aspeed.c
++++ b/drivers/mmc/host/sdhci-of-aspeed.c
+@@ -62,6 +62,8 @@ struct aspeed_sdhci_tap_desc {
+ struct aspeed_sdhci_phase_desc {
+ 	struct aspeed_sdhci_tap_desc in;
+ 	struct aspeed_sdhci_tap_desc out;
++	bool non_uniform_delay;
++	u32 nr_taps;
+ };
+ 
+ struct aspeed_sdhci_pdata {
+@@ -492,6 +494,8 @@ static const struct aspeed_sdhci_phase_desc ast2600_sdhci_phase[] = {
+ 			.enable_mask = ASPEED_SDC_S0_PHASE_OUT_EN,
+ 			.enable_value = 3,
+ 		},
++		.non_uniform_delay = false,
++		.nr_taps = 15,
+ 	},
+ 	/* SDHCI/Slot 1 */
+ 	[1] = {
+@@ -505,6 +509,33 @@ static const struct aspeed_sdhci_phase_desc ast2600_sdhci_phase[] = {
+ 			.enable_mask = ASPEED_SDC_S1_PHASE_OUT_EN,
+ 			.enable_value = 3,
+ 		},
++		.non_uniform_delay = false,
++		.nr_taps = 15,
++	},
++};
++
++static const struct aspeed_sdhci_phase_desc ast2600_emmc_phase[] = {
++	/* eMMC slot 0 */
++	[0] = {
++		.in = {
++			.tap_mask = ASPEED_SDC_S0_PHASE_IN,
++			.enable_mask = ASPEED_SDC_S0_PHASE_IN_EN,
++			.enable_value = 1,
++		},
++		.out = {
++			.tap_mask = ASPEED_SDC_S0_PHASE_OUT,
++			.enable_mask = ASPEED_SDC_S0_PHASE_OUT_EN,
++			.enable_value = 3,
++		},
++
++		/*
++		 * There are 15 taps recorded in AST2600 datasheet.
++		 * But, actually, the time period of the first tap
++		 * is two times of others. Thus, 16 tap is used to
++		 * emulate this situation.
++		 */
++		.non_uniform_delay = true,
++		.nr_taps = 16,
+ 	},
+ };
+ 
+@@ -514,10 +545,17 @@ static const struct aspeed_sdhci_pdata ast2600_sdhci_pdata = {
+ 	.nr_phase_descs = ARRAY_SIZE(ast2600_sdhci_phase),
+ };
+ 
++static const struct aspeed_sdhci_pdata ast2600_emmc_pdata = {
++	.clk_div_start = 1,
++	.phase_desc = ast2600_emmc_phase,
++	.nr_phase_descs = ARRAY_SIZE(ast2600_emmc_phase),
++};
++
+ static const struct of_device_id aspeed_sdhci_of_match[] = {
+ 	{ .compatible = "aspeed,ast2400-sdhci", .data = &ast2400_sdhci_pdata, },
+ 	{ .compatible = "aspeed,ast2500-sdhci", .data = &ast2400_sdhci_pdata, },
+ 	{ .compatible = "aspeed,ast2600-sdhci", .data = &ast2600_sdhci_pdata, },
++	{ .compatible = "aspeed,ast2600-emmc", .data = &ast2600_emmc_pdata, },
+ 	{ }
+ };
+ 
+-- 
+2.40.0.634.g4ca3ef3211-goog
+
diff --git a/recipes-kernel/linux/files/0003-mmc-aspeed-Adjust-delay-taps-calculation-method.patch b/recipes-kernel/linux/files/0003-mmc-aspeed-Adjust-delay-taps-calculation-method.patch
new file mode 100644
index 0000000..d92825b
--- /dev/null
+++ b/recipes-kernel/linux/files/0003-mmc-aspeed-Adjust-delay-taps-calculation-method.patch
@@ -0,0 +1,379 @@
+From c04e96263c90d1214990b450c72db28978450a74 Mon Sep 17 00:00:00 2001
+From: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+Date: Sun, 16 Jan 2022 17:23:24 +0800
+Subject: [PATCH 3/4] mmc: aspeed: Adjust delay taps calculation method
+
+- The maximum tap delay may be slightly different on
+  different platforms. It may also be different due to
+  different SoC processes or different manufacturers.
+  Thus, the maximum tap delay should be gotten from the
+  device tree through max-tap-delay property.
+- The delay time for each tap is an absolute value which
+  is independent of clock frequency. But, in order to combine
+  this principle with "phase" concept, clock frequency is took
+  into consideration during calculating delay taps.
+- The delay cell of eMMC device is non-uniform.
+  The time period of the first tap is two times of others.
+- The clock phase degree range is from -360 to 360.
+  But, if the clock phase signedness is negative, clock signal
+  is output from the falling edge first by default and thus, clock
+  signal is leading to data signal by 90 degrees at least.
+
+Patch Tracking Bug: b/278582663
+Upstream info: Aspeed will submit this change upstream. Currently, the
+commit is located here: https://github.com/AspeedTech-BMC/linux/commit/60f1a78d6b74c758b03826a9bea27e50047d0e77
+Upstream-Status:
+Justification: Aspeed will submit this change upstream.
+Signed-off-by: Chin-Ting Kuo <chin-ting_kuo@aspeedtech.com>
+Change-Id: I9cd2bf0624bfd0373849a97a5ab7e67291fc04f0
+---
+ arch/arm/boot/dts/aspeed-g6.dtsi   |   6 +-
+ drivers/mmc/core/host.c            |  10 +-
+ drivers/mmc/host/sdhci-of-aspeed.c | 154 ++++++++++++++++++++++-------
+ include/linux/mmc/host.h           |   2 +
+ 4 files changed, 129 insertions(+), 43 deletions(-)
+
+diff --git a/arch/arm/boot/dts/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed-g6.dtsi
+index e18954361761..e9985fae80fc 100644
+--- a/arch/arm/boot/dts/aspeed-g6.dtsi
++++ b/arch/arm/boot/dts/aspeed-g6.dtsi
+@@ -664,22 +664,24 @@ sdhci1: sdhci@1e740200 {
+ 			};
+ 
+ 			emmc_controller: sdc@1e750000 {
+-				compatible = "aspeed,ast2600-emmc";
++				compatible = "aspeed,ast2600-sd-controller";
+ 				reg = <0x1e750000 0x100>;
+ 				#address-cells = <1>;
+ 				#size-cells = <1>;
+ 				ranges = <0 0x1e750000 0x10000>;
+ 				clocks = <&syscon ASPEED_CLK_GATE_EMMCCLK>;
++				resets = <&syscon ASPEED_RESET_EMMC>;
+ 				status = "disabled";
+ 
+ 				emmc: sdhci@1e750100 {
+-					compatible = "aspeed,ast2600-sdhci";
++					compatible = "aspeed,ast2600-emmc";
+ 					reg = <0x100 0x100>;
+ 					sdhci,auto-cmd12;
+ 					interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
+ 					clocks = <&syscon ASPEED_CLK_EMMC>;
+ 					pinctrl-names = "default";
+ 					pinctrl-0 = <&pinctrl_emmc_default>;
++					status = "disabled";
+ 				};
+ 			};
+ 
+diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
+index d739e2b631fe..64cd2e49a73e 100644
+--- a/drivers/mmc/core/host.c
++++ b/drivers/mmc/core/host.c
+@@ -223,14 +223,16 @@ static void mmc_retune_timer(struct timer_list *t)
+ static void mmc_of_parse_timing_phase(struct device *dev, const char *prop,
+ 				      struct mmc_clk_phase *phase)
+ {
+-	int degrees[2] = {0};
++	int degree_info[4] = {0};
+ 	int rc;
+ 
+-	rc = device_property_read_u32_array(dev, prop, degrees, 2);
++	rc = device_property_read_u32_array(dev, prop, degree_info, 4);
+ 	phase->valid = !rc;
+ 	if (phase->valid) {
+-		phase->in_deg = degrees[0];
+-		phase->out_deg = degrees[1];
++		phase->inv_in_deg = degree_info[0] ? true : false;
++		phase->in_deg = degree_info[1];
++		phase->inv_out_deg = degree_info[2] ? true : false;
++		phase->out_deg = degree_info[3];
+ 	}
+ }
+ 
+diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c
+index 9f3bd8f407ac..fd9e3c3f9bad 100644
+--- a/drivers/mmc/host/sdhci-of-aspeed.c
++++ b/drivers/mmc/host/sdhci-of-aspeed.c
+@@ -13,6 +13,7 @@
+ #include <linux/of.h>
+ #include <linux/of_platform.h>
+ #include <linux/platform_device.h>
++#include <linux/reset.h>
+ #include <linux/spinlock.h>
+ 
+ #include "sdhci-pltfm.h"
+@@ -37,12 +38,20 @@
+ #define ASPEED_SDC_CAP2_SDR104         (1 * 32 + 1)
+ #define ASPEED_SDC_CAP2_SDR50          (1 * 32 + 0)
+ 
++#define PROBE_AFTER_ASSET_DEASSERT 0x1
++
++struct aspeed_sdc_info {
++	uint32_t flag;
++};
++
+ struct aspeed_sdc {
+ 	struct clk *clk;
+ 	struct resource *res;
++	struct reset_control *rst;
+ 
+ 	spinlock_t lock;
+ 	void __iomem *regs;
++	u32 max_tap_delay_ps;
+ };
+ 
+ struct aspeed_sdhci_tap_param {
+@@ -80,6 +89,10 @@ struct aspeed_sdhci {
+ 	const struct aspeed_sdhci_phase_desc *phase_desc;
+ };
+ 
++static struct aspeed_sdc_info ast2600_sdc_info = {
++	.flag = PROBE_AFTER_ASSET_DEASSERT
++};
++
+ /*
+  * The function sets the mirror register for updating
+  * capbilities of the current slot.
+@@ -160,56 +173,89 @@ aspeed_sdc_set_phase_taps(struct aspeed_sdc *sdc,
+ 
+ #define PICOSECONDS_PER_SECOND		1000000000000ULL
+ #define ASPEED_SDHCI_NR_TAPS		15
+-/* Measured value with *handwave* environmentals and static loading */
+-#define ASPEED_SDHCI_MAX_TAP_DELAY_PS	1253
++
+ static int aspeed_sdhci_phase_to_tap(struct device *dev, unsigned long rate_hz,
+-				     int phase_deg)
++			bool invert, int phase_deg, bool non_uniform_delay, u32 nr_taps)
+ {
+ 	u64 phase_period_ps;
+ 	u64 prop_delay_ps;
+ 	u64 clk_period_ps;
+-	unsigned int tap;
+-	u8 inverted;
++	u32 tap = 0;
++	struct aspeed_sdc *sdc = dev_get_drvdata(dev->parent);
+ 
+-	phase_deg %= 360;
++	if (sdc->max_tap_delay_ps == 0)
++		return 0;
+ 
+-	if (phase_deg >= 180) {
+-		inverted = ASPEED_SDHCI_TAP_PARAM_INVERT_CLK;
+-		phase_deg -= 180;
+-		dev_dbg(dev,
+-			"Inverting clock to reduce phase correction from %d to %d degrees\n",
+-			phase_deg + 180, phase_deg);
+-	} else {
+-		inverted = 0;
++	prop_delay_ps = sdc->max_tap_delay_ps / nr_taps;
++	clk_period_ps = div_u64(PICOSECONDS_PER_SECOND, (u64)rate_hz);
++
++	/*
++	 * For ast2600, if clock phase degree is negative, clock signal is
++	 * output from falling edge first by default. Namely, clock signal
++	 * is leading to data signal by 180 degrees at least.
++	 */
++	if (invert) {
++		if (phase_deg >= 180)
++			phase_deg -= 180;
++		else
++			return -EINVAL;
+ 	}
+ 
+-	prop_delay_ps = ASPEED_SDHCI_MAX_TAP_DELAY_PS / ASPEED_SDHCI_NR_TAPS;
+-	clk_period_ps = div_u64(PICOSECONDS_PER_SECOND, (u64)rate_hz);
+ 	phase_period_ps = div_u64((u64)phase_deg * clk_period_ps, 360ULL);
+ 
+-	tap = div_u64(phase_period_ps, prop_delay_ps);
++	/*
++	 * The delay cell is non-uniform for eMMC controller.
++	 * The time period of the first tap is two times of others.
++	 */
++	if (non_uniform_delay && phase_period_ps > prop_delay_ps * 2) {
++		phase_period_ps -= prop_delay_ps * 2;
++		tap++;
++	}
++
++	tap += div_u64(phase_period_ps, prop_delay_ps);
+ 	if (tap > ASPEED_SDHCI_NR_TAPS) {
+ 		dev_dbg(dev,
+-			 "Requested out of range phase tap %d for %d degrees of phase compensation at %luHz, clamping to tap %d\n",
+-			 tap, phase_deg, rate_hz, ASPEED_SDHCI_NR_TAPS);
++			"Requested out of range phase tap %d for %d degrees of phase compensation at %luHz, clamping to tap %d\n",
++			tap, phase_deg, rate_hz, ASPEED_SDHCI_NR_TAPS);
+ 		tap = ASPEED_SDHCI_NR_TAPS;
+ 	}
+ 
+-	return inverted | tap;
++	if (invert)
++		tap |= ASPEED_SDHCI_TAP_PARAM_INVERT_CLK;
++
++	return tap;
+ }
+ 
+-static void
+-aspeed_sdhci_phases_to_taps(struct device *dev, unsigned long rate,
+-			    const struct mmc_clk_phase *phases,
+-			    struct aspeed_sdhci_tap_param *taps)
++static void aspeed_sdhci_phases_to_taps(
++				struct device *dev, unsigned long rate,
++				const struct mmc_clk_phase *phases,
++				struct aspeed_sdhci_tap_param *taps)
+ {
++	int tmp_ret;
++	struct sdhci_host *host = dev->driver_data;
++	struct aspeed_sdhci *sdhci;
++
++	sdhci = sdhci_pltfm_priv(sdhci_priv(host));
+ 	taps->valid = phases->valid;
+ 
+ 	if (!phases->valid)
+ 		return;
+ 
+-	taps->in = aspeed_sdhci_phase_to_tap(dev, rate, phases->in_deg);
+-	taps->out = aspeed_sdhci_phase_to_tap(dev, rate, phases->out_deg);
++	tmp_ret = aspeed_sdhci_phase_to_tap(dev, rate, phases->inv_in_deg,
++				phases->in_deg, sdhci->phase_desc->non_uniform_delay,
++				sdhci->phase_desc->nr_taps);
++	if (tmp_ret < 0)
++		return;
++
++	taps->in = tmp_ret;
++
++	tmp_ret = aspeed_sdhci_phase_to_tap(dev, rate, phases->inv_out_deg,
++				phases->out_deg, sdhci->phase_desc->non_uniform_delay,
++				sdhci->phase_desc->nr_taps);
++	if (tmp_ret < 0)
++		return;
++
++	taps->out = tmp_ret;
+ }
+ 
+ static void
+@@ -238,6 +284,10 @@ aspeed_sdhci_configure_phase(struct sdhci_host *host, unsigned long rate)
+ 
+ static void aspeed_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
+ {
++#ifdef CONFIG_MACH_ASPEED_G6
++	sdhci_set_clock(host, clock);
++	aspeed_sdhci_configure_phase(host, host->max_clk);
++#else
+ 	struct sdhci_pltfm_host *pltfm_host;
+ 	unsigned long parent, bus;
+ 	struct aspeed_sdhci *sdhci;
+@@ -286,6 +336,7 @@ static void aspeed_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
+ 	aspeed_sdhci_configure_phase(host, bus);
+ 
+ 	sdhci_enable_clk(host, clk);
++#endif
+ }
+ 
+ static unsigned int aspeed_sdhci_get_max_clock(struct sdhci_host *host)
+@@ -341,9 +392,14 @@ static const struct sdhci_ops aspeed_sdhci_ops = {
+ 	.set_uhs_signaling = sdhci_set_uhs_signaling,
+ };
+ 
+-static const struct sdhci_pltfm_data aspeed_sdhci_pdata = {
+-	.ops = &aspeed_sdhci_ops,
++static struct sdhci_pltfm_data aspeed_sdhci_pdata = {
++#ifndef CONFIG_MACH_ASPEED_G6
+ 	.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
++	.quirks2 = SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN | SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
++#else
++	.ops = &aspeed_sdhci_ops,
++	.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
++#endif
+ };
+ 
+ static inline int aspeed_sdhci_calculate_slot(struct aspeed_sdhci *dev,
+@@ -569,11 +625,22 @@ static struct platform_driver aspeed_sdhci_driver = {
+ 	.remove		= aspeed_sdhci_remove,
+ };
+ 
++static const struct of_device_id aspeed_sdc_of_match[] = {
++	{ .compatible = "aspeed,ast2400-sd-controller", },
++	{ .compatible = "aspeed,ast2500-sd-controller", },
++	{ .compatible = "aspeed,ast2600-sd-controller", .data = &ast2600_sdc_info},
++	{ }
++};
++
++MODULE_DEVICE_TABLE(of, aspeed_sdc_of_match);
++
+ static int aspeed_sdc_probe(struct platform_device *pdev)
+ 
+ {
+ 	struct device_node *parent, *child;
+ 	struct aspeed_sdc *sdc;
++	const struct of_device_id *match = NULL;
++	const struct aspeed_sdc_info *info = NULL;
+ 	int ret;
+ 
+ 	sdc = devm_kzalloc(&pdev->dev, sizeof(*sdc), GFP_KERNEL);
+@@ -582,6 +649,23 @@ static int aspeed_sdc_probe(struct platform_device *pdev)
+ 
+ 	spin_lock_init(&sdc->lock);
+ 
++	match = of_match_device(aspeed_sdc_of_match, &pdev->dev);
++	if (!match)
++		return -ENODEV;
++
++	if (match->data)
++		info = match->data;
++
++	if (info) {
++		if (info->flag & PROBE_AFTER_ASSET_DEASSERT) {
++			sdc->rst = devm_reset_control_get(&pdev->dev, NULL);
++			if (!IS_ERR(sdc->rst)) {
++				reset_control_assert(sdc->rst);
++				reset_control_deassert(sdc->rst);
++			}
++		}
++	}
++
+ 	sdc->clk = devm_clk_get(&pdev->dev, NULL);
+ 	if (IS_ERR(sdc->clk))
+ 		return PTR_ERR(sdc->clk);
+@@ -599,6 +683,11 @@ static int aspeed_sdc_probe(struct platform_device *pdev)
+ 		goto err_clk;
+ 	}
+ 
++	ret = of_property_read_u32(pdev->dev.of_node, "aspeed-max-tap-delay",
++		&sdc->max_tap_delay_ps);
++	if (ret)
++		sdc->max_tap_delay_ps = 0;
++
+ 	dev_set_drvdata(&pdev->dev, sdc);
+ 
+ 	parent = pdev->dev.of_node;
+@@ -629,15 +718,6 @@ static int aspeed_sdc_remove(struct platform_device *pdev)
+ 	return 0;
+ }
+ 
+-static const struct of_device_id aspeed_sdc_of_match[] = {
+-	{ .compatible = "aspeed,ast2400-sd-controller", },
+-	{ .compatible = "aspeed,ast2500-sd-controller", },
+-	{ .compatible = "aspeed,ast2600-sd-controller", },
+-	{ }
+-};
+-
+-MODULE_DEVICE_TABLE(of, aspeed_sdc_of_match);
+-
+ static struct platform_driver aspeed_sdc_driver = {
+ 	.driver		= {
+ 		.name	= "sd-controller-aspeed",
+diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
+index 0c0c9a0fdf57..3c13010683e0 100644
+--- a/include/linux/mmc/host.h
++++ b/include/linux/mmc/host.h
+@@ -82,7 +82,9 @@ struct mmc_ios {
+ 
+ struct mmc_clk_phase {
+ 	bool valid;
++	bool inv_in_deg;
+ 	u16 in_deg;
++	bool inv_out_deg;
+ 	u16 out_deg;
+ };
+ 
+-- 
+2.40.0.634.g4ca3ef3211-goog
+
diff --git a/recipes-kernel/linux/files/0004-mmc-aspeed-fix-sdhci-software-reset-can-t-be-cleared.patch b/recipes-kernel/linux/files/0004-mmc-aspeed-fix-sdhci-software-reset-can-t-be-cleared.patch
new file mode 100644
index 0000000..e5adef3
--- /dev/null
+++ b/recipes-kernel/linux/files/0004-mmc-aspeed-fix-sdhci-software-reset-can-t-be-cleared.patch
@@ -0,0 +1,78 @@
+From 5f21aa8b8e9c5d454e0e134c2acfb5627fa08635 Mon Sep 17 00:00:00 2001
+From: Cool Lee <cool_lee@aspeedtech.com>
+Date: Mon, 5 Dec 2022 11:34:52 +0800
+Subject: [PATCH 4/4] mmc: aspeed: fix sdhci software reset can't be cleared
+ issue.
+
+Replace sdhci software reset by top reset.
+
+Patch Tracking Bug: b/278582907
+Upstream info: Aspeed will submit this change upstream. Currently, the
+commit is located here: https://github.com/AspeedTech-BMC/linux/commit/5a41e235d75c60e6dbea158905b4c4b74ce64a70
+Upstream-Status: Pending
+Justification: Aspeed will submit this change upstream.
+Signed-off-by: Cool Lee <cool_lee@aspeedtech.com>
+Change-Id: Ic931e243e9b2b62ef7a30b1d2da78c1c204620a5
+---
+ drivers/mmc/host/sdhci-of-aspeed.c | 39 +++++++++++++++++++++++++++++-
+ 1 file changed, 38 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/mmc/host/sdhci-of-aspeed.c b/drivers/mmc/host/sdhci-of-aspeed.c
+index fd9e3c3f9bad..47ec145c1025 100644
+--- a/drivers/mmc/host/sdhci-of-aspeed.c
++++ b/drivers/mmc/host/sdhci-of-aspeed.c
+@@ -382,13 +382,50 @@ static u32 aspeed_sdhci_readl(struct sdhci_host *host, int reg)
+ 	return val;
+ }
+ 
++static void aspeed_sdhci_reset(struct sdhci_host *host, u8 mask)
++{
++	struct sdhci_pltfm_host *pltfm_priv;
++	struct aspeed_sdhci *aspeed_sdhci;
++	struct aspeed_sdc *aspeed_sdc;
++	uint32_t save_array[7];
++	uint32_t reg_array[] = {SDHCI_DMA_ADDRESS,
++			SDHCI_BLOCK_SIZE,
++			SDHCI_ARGUMENT,
++			SDHCI_HOST_CONTROL,
++			SDHCI_CLOCK_CONTROL,
++			SDHCI_INT_ENABLE,
++			SDHCI_SIGNAL_ENABLE};
++	int i;
++
++	pltfm_priv = sdhci_priv(host);
++	aspeed_sdhci = sdhci_pltfm_priv(pltfm_priv);
++	aspeed_sdc = aspeed_sdhci->parent;
++
++	if (!IS_ERR(aspeed_sdc->rst)) {
++		for (i = 0; i < ARRAY_SIZE(reg_array); i++)
++			save_array[i] = sdhci_readl(host, reg_array[i]);
++
++		reset_control_assert(aspeed_sdc->rst);
++		mdelay(1);
++		reset_control_deassert(aspeed_sdc->rst);
++		mdelay(1);
++
++		for (i = 0; i < ARRAY_SIZE(reg_array); i++)
++			sdhci_writel(host, save_array[i], reg_array[i]);
++
++		aspeed_sdhci_set_clock(host, host->clock);
++	}
++
++	sdhci_reset(host, mask);
++}
++
+ static const struct sdhci_ops aspeed_sdhci_ops = {
+ 	.read_l = aspeed_sdhci_readl,
+ 	.set_clock = aspeed_sdhci_set_clock,
+ 	.get_max_clock = aspeed_sdhci_get_max_clock,
+ 	.set_bus_width = aspeed_sdhci_set_bus_width,
+ 	.get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
+-	.reset = sdhci_reset,
++	.reset = aspeed_sdhci_reset,
+ 	.set_uhs_signaling = sdhci_set_uhs_signaling,
+ };
+ 
+-- 
+2.40.0.634.g4ca3ef3211-goog
+
diff --git a/recipes-kernel/linux/linux-gbmc.inc b/recipes-kernel/linux/linux-gbmc.inc
index 9c94c6f..b6de687 100644
--- a/recipes-kernel/linux/linux-gbmc.inc
+++ b/recipes-kernel/linux/linux-gbmc.inc
@@ -22,3 +22,16 @@
   file://0003-net-ftgmac100-Add-scu-reset-toggling.patch \
   file://0003-ftgmac100-enlarge-the-ring-size.patch \
 "
+
+# Aspeed eMMC driver fixes.
+# The commit "mmc-aspeed-fix-sdhci-software-reset-can-t-be-cleared" allows us
+# to recover from any sdhci errors. That fix resolves b/267685954, where we
+# found that some machines hit sdhci errors during initialization, which the
+# driver couldn't recover from, leaving the eMMC unusable until the BMC reboots.
+# The other 3 patches are dependencies needed by that fix.
+SRC_URI:append:aspeed-g6 = " \
+  file://0001-sdhci-aspeed-Add-SDR50-support.patch \
+  file://0002-arm-dts-aspeed-Change-eMMC-device-compatible.patch \
+  file://0003-mmc-aspeed-Adjust-delay-taps-calculation-method.patch \
+  file://0004-mmc-aspeed-fix-sdhci-software-reset-can-t-be-cleared.patch \
+"