[PATCH v4 5/6] mmc: bcmstb: Add support for bcm2712 SD controller

Ivan T. Ivanov iivanov at suse.de
Wed Jan 10 13:29:07 CET 2024


Borrow SD quirks from vendor Linux driver.

"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."

Signed-off-by: Ivan T. Ivanov <iivanov at suse.de>
---
 drivers/mmc/bcmstb_sdhci.c | 173 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 172 insertions(+), 1 deletion(-)

diff --git a/drivers/mmc/bcmstb_sdhci.c b/drivers/mmc/bcmstb_sdhci.c
index dc96818cff..21489e66c0 100644
--- a/drivers/mmc/bcmstb_sdhci.c
+++ b/drivers/mmc/bcmstb_sdhci.c
@@ -38,6 +38,16 @@
  */
 #define BCMSTB_SDHCI_MINIMUM_CLOCK_FREQUENCY	400000
 
+#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 REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18)
+
 /*
  * This driver has only been tested with eMMC devices; SD devices may
  * not work.
@@ -47,6 +57,53 @@ struct sdhci_bcmstb_plat {
 	struct mmc mmc;
 };
 
+struct sdhci_bcmstb_host {
+	struct sdhci_host host;
+	u32 shadow_cmd;
+	u32 shadow_blk;
+	bool is_cmd_shadowed;
+	bool is_blk_shadowed;
+};
+
+struct sdhci_brcmstb_dev_priv {
+	int (*init)(struct udevice *dev);
+	struct sdhci_ops *ops;
+};
+
+static inline struct sdhci_bcmstb_host *to_bcmstb_host(struct sdhci_host *host)
+{
+	return container_of(host, struct sdhci_bcmstb_host, host);
+}
+
+static int sdhci_brcmstb_init_2712(struct udevice *dev)
+{
+	struct sdhci_host *host = dev_get_priv(dev);
+	void *cfg_regs;
+	u32 reg;
+
+	/* Map in the non-standard CFG registers */
+	cfg_regs = dev_remap_addr_name(dev, "cfg");
+	if (!cfg_regs)
+		return -ENOENT;
+
+	if ((host->mmc->host_caps & MMC_CAP_NONREMOVABLE) ||
+	    (host->mmc->host_caps & MMC_CAP_NEEDS_POLL)) {
+		/* Force presence */
+		reg = readl(cfg_regs + SDIO_CFG_CTRL);
+		reg &= ~SDIO_CFG_CTRL_SDCD_N_TEST_LEV;
+		reg |= SDIO_CFG_CTRL_SDCD_N_TEST_EN;
+		writel(reg, cfg_regs + SDIO_CFG_CTRL);
+	} else {
+		/* Enable card detection line */
+		reg = readl(cfg_regs + SDIO_CFG_SD_PIN_SEL);
+		reg &= ~SDIO_CFG_SD_PIN_SEL_MASK;
+		reg |= SDIO_CFG_SD_PIN_SEL_CARD;
+		writel(reg, cfg_regs + SDIO_CFG_SD_PIN_SEL);
+	}
+
+	return 0;
+}
+
 static int sdhci_bcmstb_bind(struct udevice *dev)
 {
 	struct sdhci_bcmstb_plat *plat = dev_get_plat(dev);
@@ -58,10 +115,14 @@ static int sdhci_bcmstb_probe(struct udevice *dev)
 {
 	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
 	struct sdhci_bcmstb_plat *plat = dev_get_plat(dev);
-	struct sdhci_host *host = dev_get_priv(dev);
+	struct sdhci_bcmstb_host *bcmstb = dev_get_priv(dev);
+	struct sdhci_host *host = &bcmstb->host;
+	struct sdhci_brcmstb_dev_priv *dev_priv;
 	fdt_addr_t base;
 	int ret;
 
+	dev_priv = (struct sdhci_brcmstb_dev_priv *)dev_get_driver_data(dev);
+
 	base = dev_read_addr(dev);
 	if (base == FDT_ADDR_T_NONE)
 		return -EINVAL;
@@ -75,6 +136,10 @@ static int sdhci_bcmstb_probe(struct udevice *dev)
 
 	host->mmc = &plat->mmc;
 	host->mmc->dev = dev;
+
+	if (dev_priv && dev_priv->ops)
+		host->ops = dev_priv->ops;
+
 	ret = sdhci_setup_cfg(&plat->cfg, host,
 			      BCMSTB_SDHCI_MAXIMUM_CLOCK_FREQUENCY,
 			      BCMSTB_SDHCI_MINIMUM_CLOCK_FREQUENCY);
@@ -84,10 +149,116 @@ static int sdhci_bcmstb_probe(struct udevice *dev)
 	upriv->mmc = &plat->mmc;
 	host->mmc->priv = host;
 
+	if (dev_priv && dev_priv->init) {
+		ret = dev_priv->init(dev);
+		if (ret)
+			return ret;
+	}
+
 	return sdhci_probe(dev);
 }
 
+static u16 sdhci_brcmstb_32bits_readw(struct sdhci_host *host, int reg)
+{
+	struct sdhci_bcmstb_host *bcmstb = to_bcmstb_host(host);
+	u16 word;
+	u32 val;
+
+	if (reg == SDHCI_TRANSFER_MODE && bcmstb->is_cmd_shadowed) {
+		/* Get the saved transfer mode */
+		val = bcmstb->shadow_cmd;
+	} else if ((reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) &&
+		   bcmstb->is_blk_shadowed) {
+		/* Get the saved block info */
+		val = bcmstb->shadow_blk;
+	} else {
+		val = readl(host->ioaddr + (reg & ~3));
+	}
+
+	word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff;
+	return word;
+}
+
+static u8 sdhci_brcmstb_32bits_readb(struct sdhci_host *host, int reg)
+{
+	u32 val = readl(host->ioaddr + (reg & ~3));
+	u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff;
+	return byte;
+}
+
+/*
+ * 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_32bits_writew(struct sdhci_host *host, u16 val, int reg)
+{
+	struct sdhci_bcmstb_host *bcmstb = to_bcmstb_host(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 (bcmstb->is_blk_shadowed) {
+			writel(bcmstb->shadow_blk, host->ioaddr + SDHCI_BLOCK_SIZE);
+			bcmstb->is_blk_shadowed = false;
+		}
+		oldval = bcmstb->shadow_cmd;
+		bcmstb->is_cmd_shadowed = false;
+	} else if ((reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) &&
+		   bcmstb->is_blk_shadowed) {
+		/* Block size and count are stored in shadow reg */
+		oldval = bcmstb->shadow_blk;
+	} else {
+		/* Read reg, all other registers are not shadowed */
+		oldval = readl(host->ioaddr + (reg & ~3));
+	}
+	newval = (oldval & ~mask) | (val << word_shift);
+
+	if (reg == SDHCI_TRANSFER_MODE) {
+		/* Save the transfer mode until the command is issued */
+		bcmstb->shadow_cmd = newval;
+		bcmstb->is_cmd_shadowed = true;
+	} else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) {
+		/* Save the block info until the command is issued */
+		bcmstb->shadow_blk = newval;
+		bcmstb->is_blk_shadowed = true;
+	} else {
+		/* Command or other regular 32-bit write */
+		writel(newval, host->ioaddr + (reg & ~3));
+	}
+}
+
+static void sdhci_brcmstb_32bits_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+	u32 oldval = readl(host->ioaddr + (reg & ~3));
+	u32 byte_shift = REG_OFFSET_IN_BITS(reg);
+	u32 mask = 0xff << byte_shift;
+	u32 newval = (oldval & ~mask) | (val << byte_shift);
+
+	writel(newval, host->ioaddr + (reg & ~3));
+}
+
+static struct sdhci_ops sdhci_brcmstb_ops_2712 = {
+	.read_b    = sdhci_brcmstb_32bits_readb,
+	.read_w    = sdhci_brcmstb_32bits_readw,
+	.write_w   = sdhci_brcmstb_32bits_writew,
+	.write_b   = sdhci_brcmstb_32bits_writeb,
+};
+
+static const struct sdhci_brcmstb_dev_priv match_priv_2712 = {
+	.init = sdhci_brcmstb_init_2712,
+	.ops = &sdhci_brcmstb_ops_2712,
+};
+
 static const struct udevice_id sdhci_bcmstb_match[] = {
+	{ .compatible = "brcm,bcm2712-sdhci", .data = (ulong)&match_priv_2712 },
 	{ .compatible = "brcm,bcm7425-sdhci" },
 	{ .compatible = "brcm,sdhci-brcmstb" },
 	{ }
-- 
2.35.3



More information about the U-Boot mailing list