mirror of
				https://github.com/Ysurac/openmptcprouter.git
				synced 2025-03-09 15:40:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			498 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			498 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
From 0a05300f83bc273af3715807f7ea6bed965d2521 Mon Sep 17 00:00:00 2001
 | 
						|
From: Ulf Hansson <ulf.hansson@linaro.org>
 | 
						|
Date: Thu, 29 Oct 2020 09:57:16 +0800
 | 
						|
Subject: [PATCH] mmc: brcmstb: add support for BCM2712
 | 
						|
 | 
						|
BCM2712 has an SD Express capable SDHCI implementation and uses
 | 
						|
the SDIO CFG register block present on other STB chips.
 | 
						|
 | 
						|
Add plumbing for SD Express handover and BCM2712-specific functions.
 | 
						|
 | 
						|
Due to the common bus infrastructure between BCM2711 and BCM2712,
 | 
						|
the driver also needs to implement 32-bit IO accessors.
 | 
						|
 | 
						|
mmc: brcmstb: override card presence if broken-cd is set
 | 
						|
 | 
						|
Not just if the card is declared as nonremovable.
 | 
						|
 | 
						|
sdhci: brcmstb: align SD express switchover with SD spec v8.00
 | 
						|
 | 
						|
Part 1 of the Physical specification, figure 3-24, details the switch
 | 
						|
sequence for cards initially probed as SD. Add a missing check for DAT2
 | 
						|
level after switching VDD2 on.
 | 
						|
 | 
						|
sdhci: brcmstb: clean up SD Express probe and error handling
 | 
						|
 | 
						|
Refactor to avoid spurious error messages in dmesg if the requisite SD
 | 
						|
Express DT nodes aren't present.
 | 
						|
 | 
						|
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
 | 
						|
 | 
						|
mmc: sdhci-brcmstb: only use the delay line PHY for tuneable speeds
 | 
						|
 | 
						|
The MMC core has a 200MHz core clock which allows the use of DDR50 and
 | 
						|
below without incremental phase tuning. SDR50/SDR104 and the EMMC HS200
 | 
						|
speeds require tuning.
 | 
						|
 | 
						|
Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>
 | 
						|
---
 | 
						|
 drivers/mmc/host/Kconfig         |   2 +
 | 
						|
 drivers/mmc/host/sdhci-brcmstb.c | 356 +++++++++++++++++++++++++++++++
 | 
						|
 2 files changed, 358 insertions(+)
 | 
						|
 | 
						|
--- a/drivers/mmc/host/Kconfig
 | 
						|
+++ b/drivers/mmc/host/Kconfig
 | 
						|
@@ -1082,7 +1082,9 @@ config MMC_SDHCI_BRCMSTB
 | 
						|
 	tristate "Broadcom SDIO/SD/MMC support"
 | 
						|
 	depends on ARCH_BRCMSTB || BMIPS_GENERIC
 | 
						|
 	depends on MMC_SDHCI_PLTFM
 | 
						|
+	select MMC_SDHCI_IO_ACCESSORS
 | 
						|
 	select MMC_CQHCI
 | 
						|
+	select OF_DYNAMIC
 | 
						|
 	default y
 | 
						|
 	help
 | 
						|
 	  This selects support for the SDIO/SD/MMC Host Controller on
 | 
						|
--- a/drivers/mmc/host/sdhci-brcmstb.c
 | 
						|
+++ b/drivers/mmc/host/sdhci-brcmstb.c
 | 
						|
@@ -11,6 +11,8 @@
 | 
						|
 #include <linux/of.h>
 | 
						|
 #include <linux/bitops.h>
 | 
						|
 #include <linux/delay.h>
 | 
						|
+#include <linux/pinctrl/consumer.h>
 | 
						|
+#include <linux/regulator/consumer.h>
 | 
						|
 
 | 
						|
 #include "sdhci-cqhci.h"
 | 
						|
 #include "sdhci-pltfm.h"
 | 
						|
@@ -26,18 +28,43 @@
 | 
						|
 
 | 
						|
 #define BRCMSTB_PRIV_FLAGS_HAS_CQE		BIT(0)
 | 
						|
 #define BRCMSTB_PRIV_FLAGS_GATE_CLOCK		BIT(1)
 | 
						|
+#define BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS	BIT(2)
 | 
						|
 
 | 
						|
 #define SDHCI_ARASAN_CQE_BASE_ADDR		0x200
 | 
						|
 
 | 
						|
+#define SDIO_CFG_CTRL				0x0
 | 
						|
+#define  SDIO_CFG_CTRL_SDCD_N_TEST_EN		BIT(31)
 | 
						|
+#define  SDIO_CFG_CTRL_SDCD_N_TEST_LEV		BIT(30)
 | 
						|
+
 | 
						|
+#define SDIO_CFG_SD_PIN_SEL			0x44
 | 
						|
+#define  SDIO_CFG_SD_PIN_SEL_MASK		0x3
 | 
						|
+#define  SDIO_CFG_SD_PIN_SEL_CARD		BIT(1)
 | 
						|
+
 | 
						|
+#define SDIO_CFG_MAX_50MHZ_MODE			0x1ac
 | 
						|
+#define  SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE	BIT(31)
 | 
						|
+#define  SDIO_CFG_MAX_50MHZ_MODE_ENABLE		BIT(0)
 | 
						|
+
 | 
						|
 struct sdhci_brcmstb_priv {
 | 
						|
 	void __iomem *cfg_regs;
 | 
						|
 	unsigned int flags;
 | 
						|
 	struct clk *base_clk;
 | 
						|
 	u32 base_freq_hz;
 | 
						|
+	u32 shadow_cmd;
 | 
						|
+	u32 shadow_blk;
 | 
						|
+	bool is_cmd_shadowed;
 | 
						|
+	bool is_blk_shadowed;
 | 
						|
+	struct regulator *sde_1v8;
 | 
						|
+	struct device_node *sde_pcie;
 | 
						|
+	void *__iomem sde_ioaddr;
 | 
						|
+	void *__iomem sde_ioaddr2;
 | 
						|
+	struct pinctrl *pinctrl;
 | 
						|
+	struct pinctrl_state *pins_default;
 | 
						|
+	struct pinctrl_state *pins_sdex;
 | 
						|
 };
 | 
						|
 
 | 
						|
 struct brcmstb_match_priv {
 | 
						|
 	void (*hs400es)(struct mmc_host *mmc, struct mmc_ios *ios);
 | 
						|
+	void (*cfginit)(struct sdhci_host *host);
 | 
						|
 	struct sdhci_ops *ops;
 | 
						|
 	const unsigned int flags;
 | 
						|
 };
 | 
						|
@@ -94,6 +121,124 @@ static void sdhci_brcmstb_set_clock(stru
 | 
						|
 	sdhci_enable_clk(host, clk);
 | 
						|
 }
 | 
						|
 
 | 
						|
+#define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18)
 | 
						|
+
 | 
						|
+static inline u32 sdhci_brcmstb_32only_readl(struct sdhci_host *host, int reg)
 | 
						|
+{
 | 
						|
+	u32 val = readl(host->ioaddr + reg);
 | 
						|
+
 | 
						|
+	pr_debug("%s: readl [0x%02x] 0x%08x\n",
 | 
						|
+		 mmc_hostname(host->mmc), reg, val);
 | 
						|
+	return val;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static u16 sdhci_brcmstb_32only_readw(struct sdhci_host *host, int reg)
 | 
						|
+{
 | 
						|
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 | 
						|
+	struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
 | 
						|
+	u32 val;
 | 
						|
+	u16 word;
 | 
						|
+
 | 
						|
+	if ((reg == SDHCI_TRANSFER_MODE) && brcmstb_priv->is_cmd_shadowed) {
 | 
						|
+		/* Get the saved transfer mode */
 | 
						|
+		val = brcmstb_priv->shadow_cmd;
 | 
						|
+	} else if ((reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) &&
 | 
						|
+		   brcmstb_priv->is_blk_shadowed) {
 | 
						|
+		/* Get the saved block info */
 | 
						|
+		val = brcmstb_priv->shadow_blk;
 | 
						|
+	} else {
 | 
						|
+		val = sdhci_brcmstb_32only_readl(host, (reg & ~3));
 | 
						|
+	}
 | 
						|
+	word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff;
 | 
						|
+	return word;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static u8 sdhci_brcmstb_32only_readb(struct sdhci_host *host, int reg)
 | 
						|
+{
 | 
						|
+	u32 val = sdhci_brcmstb_32only_readl(host, (reg & ~3));
 | 
						|
+	u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff;
 | 
						|
+	return byte;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static inline void sdhci_brcmstb_32only_writel(struct sdhci_host *host, u32 val, int reg)
 | 
						|
+{
 | 
						|
+	pr_debug("%s: writel [0x%02x] 0x%08x\n",
 | 
						|
+		 mmc_hostname(host->mmc), reg, val);
 | 
						|
+
 | 
						|
+	writel(val, host->ioaddr + reg);
 | 
						|
+}
 | 
						|
+
 | 
						|
+/*
 | 
						|
+ * BCM2712 unfortunately carries with it a perennial bug with the SD controller
 | 
						|
+ * register interface present on previous chips (2711/2709/2708). Accesses must
 | 
						|
+ * be dword-sized and a read-modify-write cycle to the 32-bit registers
 | 
						|
+ * containing the COMMAND, TRANSFER_MODE, BLOCK_SIZE and BLOCK_COUNT registers
 | 
						|
+ * tramples the upper/lower 16 bits of data written. BCM2712 does not seem to
 | 
						|
+ * need the extreme delay between each write as on previous chips, just the
 | 
						|
+ * serialisation of writes to these registers in a single 32-bit operation.
 | 
						|
+ */
 | 
						|
+static void sdhci_brcmstb_32only_writew(struct sdhci_host *host, u16 val, int reg)
 | 
						|
+{
 | 
						|
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 | 
						|
+	struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
 | 
						|
+	u32 word_shift = REG_OFFSET_IN_BITS(reg);
 | 
						|
+	u32 mask = 0xffff << word_shift;
 | 
						|
+	u32 oldval, newval;
 | 
						|
+
 | 
						|
+	if (reg == SDHCI_COMMAND) {
 | 
						|
+		/* Write the block now as we are issuing a command */
 | 
						|
+		if (brcmstb_priv->is_blk_shadowed) {
 | 
						|
+			sdhci_brcmstb_32only_writel(host, brcmstb_priv->shadow_blk,
 | 
						|
+				SDHCI_BLOCK_SIZE);
 | 
						|
+			brcmstb_priv->is_blk_shadowed = false;
 | 
						|
+		}
 | 
						|
+		oldval = brcmstb_priv->shadow_cmd;
 | 
						|
+		brcmstb_priv->is_cmd_shadowed = false;
 | 
						|
+	} else if ((reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) &&
 | 
						|
+		   brcmstb_priv->is_blk_shadowed) {
 | 
						|
+		/* Block size and count are stored in shadow reg */
 | 
						|
+		oldval = brcmstb_priv->shadow_blk;
 | 
						|
+	} else {
 | 
						|
+		/* Read reg, all other registers are not shadowed */
 | 
						|
+		oldval = sdhci_brcmstb_32only_readl(host, (reg & ~3));
 | 
						|
+	}
 | 
						|
+	newval = (oldval & ~mask) | (val << word_shift);
 | 
						|
+
 | 
						|
+	if (reg == SDHCI_TRANSFER_MODE) {
 | 
						|
+		/* Save the transfer mode until the command is issued */
 | 
						|
+		brcmstb_priv->shadow_cmd = newval;
 | 
						|
+		brcmstb_priv->is_cmd_shadowed = true;
 | 
						|
+	} else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
 | 
						|
+		/* Save the block info until the command is issued */
 | 
						|
+		brcmstb_priv->shadow_blk = newval;
 | 
						|
+		brcmstb_priv->is_blk_shadowed = true;
 | 
						|
+	} else {
 | 
						|
+		/* Command or other regular 32-bit write */
 | 
						|
+		sdhci_brcmstb_32only_writel(host, newval, reg & ~3);
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void sdhci_brcmstb_32only_writeb(struct sdhci_host *host, u8 val, int reg)
 | 
						|
+{
 | 
						|
+	u32 oldval = sdhci_brcmstb_32only_readl(host, (reg & ~3));
 | 
						|
+	u32 byte_shift = REG_OFFSET_IN_BITS(reg);
 | 
						|
+	u32 mask = 0xff << byte_shift;
 | 
						|
+	u32 newval = (oldval & ~mask) | (val << byte_shift);
 | 
						|
+
 | 
						|
+	sdhci_brcmstb_32only_writel(host, newval, reg & ~3);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void sdhci_brcmstb_set_power(struct sdhci_host *host, unsigned char mode,
 | 
						|
+				  unsigned short vdd)
 | 
						|
+{
 | 
						|
+	if (!IS_ERR(host->mmc->supply.vmmc)) {
 | 
						|
+		struct mmc_host *mmc = host->mmc;
 | 
						|
+
 | 
						|
+		mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
 | 
						|
+	}
 | 
						|
+	sdhci_set_power_noreg(host, mode, vdd);
 | 
						|
+}
 | 
						|
+
 | 
						|
 static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host,
 | 
						|
 					    unsigned int timing)
 | 
						|
 {
 | 
						|
@@ -123,6 +268,146 @@ static void sdhci_brcmstb_set_uhs_signal
 | 
						|
 	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
 | 
						|
 }
 | 
						|
 
 | 
						|
+static void sdhci_brcmstb_cfginit_2712(struct sdhci_host *host)
 | 
						|
+{
 | 
						|
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 | 
						|
+	struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
 | 
						|
+	bool want_dll = false;
 | 
						|
+	u32 uhs_mask = (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104);
 | 
						|
+	u32 hsemmc_mask = (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS200_1_2V_SDR |
 | 
						|
+			   MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V);
 | 
						|
+	u32 reg;
 | 
						|
+
 | 
						|
+	if (!(host->quirks2 & SDHCI_QUIRK2_NO_1_8_V)) {
 | 
						|
+	    if((host->mmc->caps & uhs_mask) || (host->mmc->caps2 & hsemmc_mask))
 | 
						|
+		want_dll = true;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/*
 | 
						|
+	 * If we want a speed that requires tuning,
 | 
						|
+	 * then select the delay line PHY as the clock source.
 | 
						|
+	 */
 | 
						|
+	if (want_dll) {
 | 
						|
+		reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE);
 | 
						|
+		reg &= ~SDIO_CFG_MAX_50MHZ_MODE_ENABLE;
 | 
						|
+		reg |= SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE;
 | 
						|
+		writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
 | 
						|
+	    (host->mmc->caps & MMC_CAP_NEEDS_POLL)) {
 | 
						|
+		/* Force presence */
 | 
						|
+		reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_CTRL);
 | 
						|
+		reg &= ~SDIO_CFG_CTRL_SDCD_N_TEST_LEV;
 | 
						|
+		reg |= SDIO_CFG_CTRL_SDCD_N_TEST_EN;
 | 
						|
+		writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CTRL);
 | 
						|
+	} else {
 | 
						|
+		/* Enable card detection line */
 | 
						|
+		reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL);
 | 
						|
+		reg &= ~SDIO_CFG_SD_PIN_SEL_MASK;
 | 
						|
+		reg |= SDIO_CFG_SD_PIN_SEL_CARD;
 | 
						|
+		writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL);
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int bcm2712_init_sd_express(struct sdhci_host *host, struct mmc_ios *ios)
 | 
						|
+{
 | 
						|
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 | 
						|
+	struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
 | 
						|
+	struct device *dev = host->mmc->parent;
 | 
						|
+	u32 ctrl_val;
 | 
						|
+	u32 present_state;
 | 
						|
+	int ret;
 | 
						|
+
 | 
						|
+	if (!brcmstb_priv->sde_ioaddr || !brcmstb_priv->sde_ioaddr2)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	if (!brcmstb_priv->pinctrl)
 | 
						|
+		return -EINVAL;
 | 
						|
+
 | 
						|
+	/* Turn off the SD clock first */
 | 
						|
+	sdhci_set_clock(host, 0);
 | 
						|
+
 | 
						|
+	/* Disable SD DAT0-3 pulls */
 | 
						|
+	pinctrl_select_state(brcmstb_priv->pinctrl, brcmstb_priv->pins_sdex);
 | 
						|
+
 | 
						|
+	ctrl_val = readl(brcmstb_priv->sde_ioaddr);
 | 
						|
+	dev_dbg(dev, "ctrl_val 1 %08x\n", ctrl_val);
 | 
						|
+
 | 
						|
+	/* Tri-state the SD pins */
 | 
						|
+	ctrl_val |= 0x1ff8;
 | 
						|
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr);
 | 
						|
+	dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr));
 | 
						|
+	/* Let voltages settle */
 | 
						|
+	udelay(100);
 | 
						|
+
 | 
						|
+	/* Enable the PCIe sideband pins */
 | 
						|
+	ctrl_val &= ~0x6000;
 | 
						|
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr);
 | 
						|
+	dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr));
 | 
						|
+	/* Let voltages settle */
 | 
						|
+	udelay(100);
 | 
						|
+
 | 
						|
+	/* Turn on the 1v8 VDD2 regulator */
 | 
						|
+	ret = regulator_enable(brcmstb_priv->sde_1v8);
 | 
						|
+	if (ret)
 | 
						|
+		return ret;
 | 
						|
+
 | 
						|
+	/* Wait for Tpvcrl */
 | 
						|
+	msleep(1);
 | 
						|
+
 | 
						|
+	/* Sample DAT2 (CLKREQ#) - if low, card is in PCIe mode */
 | 
						|
+	present_state = sdhci_readl(host, SDHCI_PRESENT_STATE);
 | 
						|
+	present_state = (present_state & SDHCI_DATA_LVL_MASK) >> SDHCI_DATA_LVL_SHIFT;
 | 
						|
+	dev_dbg(dev, "state = 0x%08x\n", present_state);
 | 
						|
+
 | 
						|
+	if (present_state & BIT(2)) {
 | 
						|
+		dev_err(dev, "DAT2 still high, abandoning SDex switch\n");
 | 
						|
+		return -ENODEV;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Turn on the LCPLL PTEST mux */
 | 
						|
+	ctrl_val = readl(brcmstb_priv->sde_ioaddr2 + 20); // misc5
 | 
						|
+	ctrl_val &= ~(0x7 << 7);
 | 
						|
+	ctrl_val |= 3 << 7;
 | 
						|
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr2 + 20);
 | 
						|
+	dev_dbg(dev, "misc 5->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2 + 20));
 | 
						|
+
 | 
						|
+	/* PTEST diff driver enable */
 | 
						|
+	ctrl_val = readl(brcmstb_priv->sde_ioaddr2);
 | 
						|
+	ctrl_val |= BIT(21);
 | 
						|
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr2);
 | 
						|
+
 | 
						|
+	dev_dbg(dev, "misc 0->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2));
 | 
						|
+
 | 
						|
+	/* Wait for more than the minimum Tpvpgl time */
 | 
						|
+	msleep(100);
 | 
						|
+
 | 
						|
+	if (brcmstb_priv->sde_pcie) {
 | 
						|
+		struct of_changeset changeset;
 | 
						|
+		static struct property okay_property = {
 | 
						|
+			.name = "status",
 | 
						|
+			.value = "okay",
 | 
						|
+			.length = 5,
 | 
						|
+		};
 | 
						|
+
 | 
						|
+		/* Enable the pcie controller */
 | 
						|
+		of_changeset_init(&changeset);
 | 
						|
+		ret = of_changeset_update_property(&changeset,
 | 
						|
+						   brcmstb_priv->sde_pcie,
 | 
						|
+						   &okay_property);
 | 
						|
+		if (ret) {
 | 
						|
+			dev_err(dev, "%s: failed to update property - %d\n", __func__,
 | 
						|
+			       ret);
 | 
						|
+			return -ENODEV;
 | 
						|
+		}
 | 
						|
+		ret = of_changeset_apply(&changeset);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	dev_dbg(dev, "%s -> %d\n", __func__, ret);
 | 
						|
+	return ret;
 | 
						|
+}
 | 
						|
+
 | 
						|
 static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc)
 | 
						|
 {
 | 
						|
 	sdhci_dumpregs(mmc_priv(mmc));
 | 
						|
@@ -155,6 +440,21 @@ static struct sdhci_ops sdhci_brcmstb_op
 | 
						|
 	.set_uhs_signaling = sdhci_set_uhs_signaling,
 | 
						|
 };
 | 
						|
 
 | 
						|
+static struct sdhci_ops sdhci_brcmstb_ops_2712 = {
 | 
						|
+	.read_l = sdhci_brcmstb_32only_readl,
 | 
						|
+	.read_w = sdhci_brcmstb_32only_readw,
 | 
						|
+	.read_b = sdhci_brcmstb_32only_readb,
 | 
						|
+	.write_l = sdhci_brcmstb_32only_writel,
 | 
						|
+	.write_w = sdhci_brcmstb_32only_writew,
 | 
						|
+	.write_b = sdhci_brcmstb_32only_writeb,
 | 
						|
+	.set_clock = sdhci_set_clock,
 | 
						|
+	.set_power = sdhci_brcmstb_set_power,
 | 
						|
+	.set_bus_width = sdhci_set_bus_width,
 | 
						|
+	.reset = sdhci_reset,
 | 
						|
+	.set_uhs_signaling = sdhci_set_uhs_signaling,
 | 
						|
+	.init_sd_express = bcm2712_init_sd_express,
 | 
						|
+};
 | 
						|
+
 | 
						|
 static struct sdhci_ops sdhci_brcmstb_ops_7216 = {
 | 
						|
 	.set_clock = sdhci_brcmstb_set_clock,
 | 
						|
 	.set_bus_width = sdhci_set_bus_width,
 | 
						|
@@ -179,10 +479,16 @@ static const struct brcmstb_match_priv m
 | 
						|
 	.ops = &sdhci_brcmstb_ops_7216,
 | 
						|
 };
 | 
						|
 
 | 
						|
+static const struct brcmstb_match_priv match_priv_2712 = {
 | 
						|
+	.cfginit = sdhci_brcmstb_cfginit_2712,
 | 
						|
+	.ops = &sdhci_brcmstb_ops_2712,
 | 
						|
+};
 | 
						|
+
 | 
						|
 static const struct of_device_id sdhci_brcm_of_match[] = {
 | 
						|
 	{ .compatible = "brcm,bcm7425-sdhci", .data = &match_priv_7425 },
 | 
						|
 	{ .compatible = "brcm,bcm7445-sdhci", .data = &match_priv_7445 },
 | 
						|
 	{ .compatible = "brcm,bcm7216-sdhci", .data = &match_priv_7216 },
 | 
						|
+	{ .compatible = "brcm,bcm2712-sdhci", .data = &match_priv_2712 },
 | 
						|
 	{},
 | 
						|
 };
 | 
						|
 
 | 
						|
@@ -256,6 +562,7 @@ static int sdhci_brcmstb_probe(struct pl
 | 
						|
 	u32 actual_clock_mhz;
 | 
						|
 	struct sdhci_host *host;
 | 
						|
 	struct resource *iomem;
 | 
						|
+	bool no_pinctrl = false;
 | 
						|
 	struct clk *clk;
 | 
						|
 	struct clk *base_clk = NULL;
 | 
						|
 	int res;
 | 
						|
@@ -290,6 +597,11 @@ static int sdhci_brcmstb_probe(struct pl
 | 
						|
 		match_priv->ops->irq = sdhci_brcmstb_cqhci_irq;
 | 
						|
 	}
 | 
						|
 
 | 
						|
+	priv->sde_pcie = of_parse_phandle(pdev->dev.of_node,
 | 
						|
+					  "sde-pcie", 0);
 | 
						|
+	if (priv->sde_pcie)
 | 
						|
+		priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
 | 
						|
+
 | 
						|
 	/* Map in the non-standard CFG registers */
 | 
						|
 	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 | 
						|
 	priv->cfg_regs = devm_ioremap_resource(&pdev->dev, iomem);
 | 
						|
@@ -303,6 +615,43 @@ static int sdhci_brcmstb_probe(struct pl
 | 
						|
 	if (res)
 | 
						|
 		goto err;
 | 
						|
 
 | 
						|
+	priv->sde_1v8 = devm_regulator_get_optional(&pdev->dev, "sde-1v8");
 | 
						|
+	if (IS_ERR(priv->sde_1v8))
 | 
						|
+		priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
 | 
						|
+
 | 
						|
+	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 2);
 | 
						|
+	if (iomem) {
 | 
						|
+		priv->sde_ioaddr = devm_ioremap_resource(&pdev->dev, iomem);
 | 
						|
+		if (IS_ERR(priv->sde_ioaddr))
 | 
						|
+			priv->sde_ioaddr = NULL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 3);
 | 
						|
+	if (iomem) {
 | 
						|
+		priv->sde_ioaddr2 = devm_ioremap_resource(&pdev->dev, iomem);
 | 
						|
+		if (IS_ERR(priv->sde_ioaddr2))
 | 
						|
+			priv->sde_ioaddr = NULL;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	priv->pinctrl = devm_pinctrl_get(&pdev->dev);
 | 
						|
+	if (IS_ERR(priv->pinctrl)) {
 | 
						|
+			no_pinctrl = true;
 | 
						|
+	}
 | 
						|
+	priv->pins_default = pinctrl_lookup_state(priv->pinctrl, "default");
 | 
						|
+	if (IS_ERR(priv->pins_default)) {
 | 
						|
+			dev_dbg(&pdev->dev, "No pinctrl default state\n");
 | 
						|
+			no_pinctrl = true;
 | 
						|
+	}
 | 
						|
+	priv->pins_sdex = pinctrl_lookup_state(priv->pinctrl, "sd-express");
 | 
						|
+	if (IS_ERR(priv->pins_sdex)) {
 | 
						|
+			dev_dbg(&pdev->dev, "No pinctrl sd-express state\n");
 | 
						|
+			no_pinctrl = true;
 | 
						|
+	}
 | 
						|
+	if (no_pinctrl || !priv->sde_ioaddr || !priv->sde_ioaddr2) {
 | 
						|
+		priv->pinctrl = NULL;
 | 
						|
+		priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
 | 
						|
+	}
 | 
						|
+
 | 
						|
 	/*
 | 
						|
 	 * Automatic clock gating does not work for SD cards that may
 | 
						|
 	 * voltage switch so only enable it for non-removable devices.
 | 
						|
@@ -319,6 +668,13 @@ static int sdhci_brcmstb_probe(struct pl
 | 
						|
 	    (host->mmc->caps2 & MMC_CAP2_HS400_ES))
 | 
						|
 		host->mmc_host_ops.hs400_enhanced_strobe = match_priv->hs400es;
 | 
						|
 
 | 
						|
+	if (host->ops->init_sd_express &&
 | 
						|
+	    (priv->flags & BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS))
 | 
						|
+		host->mmc->caps2 |= MMC_CAP2_SD_EXP;
 | 
						|
+
 | 
						|
+	if(match_priv->cfginit)
 | 
						|
+		match_priv->cfginit(host);
 | 
						|
+
 | 
						|
 	/*
 | 
						|
 	 * Supply the existing CAPS, but clear the UHS modes. This
 | 
						|
 	 * will allow these modes to be specified by device tree
 |