[PATCH] mtd: spi-nor: intel-spi: Add support for second flash chip

Mika Westerberg mika.westerberg at linux.intel.com
Tue May 25 09:03:18 PDT 2021


Intel SPI flash controller has been supporting two chip selects long
time already even if the most common configuration is to have single
flash chip for the BIOS and related data. This adds support for the
second chip select if we find out that there are two flash components
(this information is available in the mandatory flash descriptor on the
first chip). The second chip is exposed as is without any partition
information with name "chip1". The first chip continues work as is.

Signed-off-by: Mika Westerberg <mika.westerberg at linux.intel.com>
---
 drivers/mtd/spi-nor/controllers/intel-spi.c | 208 ++++++++++++++------
 1 file changed, 150 insertions(+), 58 deletions(-)

diff --git a/drivers/mtd/spi-nor/controllers/intel-spi.c b/drivers/mtd/spi-nor/controllers/intel-spi.c
index a413892ff449..f9a900c4dd40 100644
--- a/drivers/mtd/spi-nor/controllers/intel-spi.c
+++ b/drivers/mtd/spi-nor/controllers/intel-spi.c
@@ -118,6 +118,11 @@
 #define ERASE_64K_OPCODE_SHIFT		16
 #define ERASE_64K_OPCODE_MASK		(0xff << ERASE_OPCODE_SHIFT)
 
+/* Flash descriptor fields */
+#define FLVALSIG_MAGIC			0x0ff0a55a
+#define FLMAP0_NC_MASK			GENMASK(9, 8)
+#define FLMAP0_NC_SHIFT			8
+
 #define INTEL_SPI_TIMEOUT		5000 /* ms */
 #define INTEL_SPI_FIFO_SZ		64
 
@@ -125,7 +130,8 @@
  * struct intel_spi - Driver private data
  * @dev: Device pointer
  * @info: Pointer to board specific info
- * @nor: SPI NOR layer structure
+ * @nor: SPI NOR layer structures
+ * @nc: Number of flash chips
  * @base: Beginning of MMIO space
  * @pregs: Start of protection registers
  * @sregs: Start of software sequencer registers
@@ -143,7 +149,8 @@
 struct intel_spi {
 	struct device *dev;
 	const struct intel_spi_boardinfo *info;
-	struct spi_nor nor;
+	struct spi_nor nor[2];
+	size_t nc;
 	void __iomem *base;
 	void __iomem *pregs;
 	void __iomem *sregs;
@@ -554,14 +561,23 @@ static int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, size_t len,
 	return 0;
 }
 
+static void intel_spi_set_addr(const struct spi_nor *nor, loff_t addr)
+{
+	const struct intel_spi *ispi = nor->priv;
+
+	/* Pick correct flash component */
+	if (nor == &ispi->nor[1])
+		addr += ispi->nor[0].mtd.size;
+	writel(addr, ispi->base + FADDR);
+}
+
 static int intel_spi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
 			      size_t len)
 {
 	struct intel_spi *ispi = nor->priv;
 	int ret;
 
-	/* Address of the first chip */
-	writel(0, ispi->base + FADDR);
+	intel_spi_set_addr(nor, 0);
 
 	if (ispi->swseq_reg)
 		ret = intel_spi_sw_cycle(ispi, opcode, len,
@@ -620,7 +636,7 @@ static int intel_spi_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf,
 	if (opcode == SPINOR_OP_WRDI)
 		return 0;
 
-	writel(0, ispi->base + FADDR);
+	intel_spi_set_addr(nor, 0);
 
 	/* Write the value beforehand */
 	ret = intel_spi_write_block(ispi, buf, len);
@@ -633,12 +649,49 @@ static int intel_spi_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf,
 	return intel_spi_hw_cycle(ispi, opcode, len);
 }
 
+static ssize_t __intel_spi_read(struct intel_spi *ispi, struct spi_nor *nor,
+				loff_t from, size_t len, u_char *read_buf)
+{
+	u32 val, status;
+	int ret;
+
+	if (nor)
+		intel_spi_set_addr(nor, from);
+	else
+		writel(from, ispi->base + FADDR);
+
+	val = readl(ispi->base + HSFSTS_CTL);
+	val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK);
+	val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
+	val |= (len - 1) << HSFSTS_CTL_FDBC_SHIFT;
+	val |= HSFSTS_CTL_FCYCLE_READ;
+	val |= HSFSTS_CTL_FGO;
+	writel(val, ispi->base + HSFSTS_CTL);
+
+	ret = intel_spi_wait_hw_busy(ispi);
+	if (ret)
+		return ret;
+
+	status = readl(ispi->base + HSFSTS_CTL);
+	if (status & HSFSTS_CTL_FCERR)
+		ret = -EIO;
+	else if (status & HSFSTS_CTL_AEL)
+		ret = -EACCES;
+
+	if (ret < 0) {
+		dev_err(ispi->dev, "read error: %llx: %#x\n", from,
+			status);
+		return ret;
+	}
+
+	return intel_spi_read_block(ispi, read_buf, len);
+}
+
 static ssize_t intel_spi_read(struct spi_nor *nor, loff_t from, size_t len,
 			      u_char *read_buf)
 {
 	struct intel_spi *ispi = nor->priv;
 	size_t block_size, retlen = 0;
-	u32 val, status;
 	ssize_t ret;
 
 	/*
@@ -665,33 +718,7 @@ static ssize_t intel_spi_read(struct spi_nor *nor, loff_t from, size_t len,
 		block_size = min_t(loff_t, from + block_size,
 				   round_up(from + 1, SZ_4K)) - from;
 
-		writel(from, ispi->base + FADDR);
-
-		val = readl(ispi->base + HSFSTS_CTL);
-		val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK);
-		val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
-		val |= (block_size - 1) << HSFSTS_CTL_FDBC_SHIFT;
-		val |= HSFSTS_CTL_FCYCLE_READ;
-		val |= HSFSTS_CTL_FGO;
-		writel(val, ispi->base + HSFSTS_CTL);
-
-		ret = intel_spi_wait_hw_busy(ispi);
-		if (ret)
-			return ret;
-
-		status = readl(ispi->base + HSFSTS_CTL);
-		if (status & HSFSTS_CTL_FCERR)
-			ret = -EIO;
-		else if (status & HSFSTS_CTL_AEL)
-			ret = -EACCES;
-
-		if (ret < 0) {
-			dev_err(ispi->dev, "read error: %llx: %#x\n", from,
-				status);
-			return ret;
-		}
-
-		ret = intel_spi_read_block(ispi, read_buf, block_size);
+		ret = __intel_spi_read(ispi, nor, from, block_size, read_buf);
 		if (ret)
 			return ret;
 
@@ -722,7 +749,7 @@ static ssize_t intel_spi_write(struct spi_nor *nor, loff_t to, size_t len,
 		block_size = min_t(loff_t, to + block_size,
 				   round_up(to + 1, SZ_4K)) - to;
 
-		writel(to, ispi->base + FADDR);
+		intel_spi_set_addr(nor, to);
 
 		val = readl(ispi->base + HSFSTS_CTL);
 		val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK);
@@ -785,7 +812,7 @@ static int intel_spi_erase(struct spi_nor *nor, loff_t offs)
 
 	if (ispi->swseq_erase) {
 		while (len > 0) {
-			writel(offs, ispi->base + FADDR);
+			intel_spi_set_addr(nor, offs);
 
 			ret = intel_spi_sw_cycle(ispi, nor->erase_opcode,
 						 0, OPTYPE_WRITE_WITH_ADDR);
@@ -803,7 +830,7 @@ static int intel_spi_erase(struct spi_nor *nor, loff_t offs)
 	ispi->atomic_preopcode = 0;
 
 	while (len > 0) {
-		writel(offs, ispi->base + FADDR);
+		intel_spi_set_addr(nor, offs);
 
 		val = readl(ispi->base + HSFSTS_CTL);
 		val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK);
@@ -851,6 +878,37 @@ static bool intel_spi_is_protected(const struct intel_spi *ispi,
 	return false;
 }
 
+static int intel_spi_read_desc(struct intel_spi *ispi)
+{
+	u32 buf[2], nc;
+	ssize_t ret;
+
+	ret = __intel_spi_read(ispi, NULL, 0x10, sizeof(buf), (u_char *)buf);
+	if (ret < 0) {
+		dev_warn(ispi->dev, "failed to read descriptor\n");
+		return ret;
+	}
+
+	dev_dbg(ispi->dev, "FLVALSIG=0x%08x\n", buf[0]);
+	dev_dbg(ispi->dev, "FLMAP0=0x%08x\n", buf[1]);
+
+	if (buf[0] != FLVALSIG_MAGIC) {
+		dev_warn(ispi->dev, "descriptor signature not valid\n");
+		return -ENODEV;
+	}
+
+	nc = (buf[1] & FLMAP0_NC_MASK) >> FLMAP0_NC_SHIFT;
+	if (nc == 0)
+		ispi->nc = 1;
+	else if (nc == 1)
+		ispi->nc = 2;
+	else
+		return -EINVAL;
+
+	dev_dbg(ispi->dev, "%zd flash components found\n", ispi->nc);
+	return 0;
+}
+
 /*
  * There will be a single partition holding all enabled flash regions. We
  * call this "BIOS".
@@ -861,8 +919,6 @@ static void intel_spi_fill_partition(struct intel_spi *ispi,
 	u64 end;
 	int i;
 
-	memset(part, 0, sizeof(*part));
-
 	/* Start from the mandatory descriptor region */
 	part->size = 4096;
 	part->name = "BIOS";
@@ -889,7 +945,7 @@ static void intel_spi_fill_partition(struct intel_spi *ispi,
 			ispi->writeable = false;
 
 		end = (limit << 12) + 4096;
-		if (end > part->size)
+		if (end > part->size && end <= ispi->nor[0].mtd.size)
 			part->size = end;
 	}
 }
@@ -902,15 +958,47 @@ static const struct spi_nor_controller_ops intel_spi_controller_ops = {
 	.erase = intel_spi_erase,
 };
 
-struct intel_spi *intel_spi_probe(struct device *dev,
-	struct resource *mem, const struct intel_spi_boardinfo *info)
+static int intel_spi_add_chip(struct intel_spi *ispi, struct spi_nor *nor,
+			      const char *name)
 {
 	const struct spi_nor_hwcaps hwcaps = {
 		.mask = SNOR_HWCAPS_READ |
 			SNOR_HWCAPS_READ_FAST |
 			SNOR_HWCAPS_PP,
 	};
-	struct mtd_partition part;
+	struct mtd_partition parts[1];
+	int ret, nr_parts = 0;
+
+	nor->dev = ispi->dev;
+	nor->priv = ispi;
+	nor->controller_ops = &intel_spi_controller_ops;
+
+	memset(parts, 0, sizeof(parts));
+
+	ret = spi_nor_scan(nor, NULL, &hwcaps);
+	if (ret) {
+		dev_info(ispi->dev, "failed to locate the chip\n");
+		return ret;
+	}
+
+	/* Only the first chip has partition */
+	if (nor == &ispi->nor[0]) {
+		intel_spi_fill_partition(ispi, &parts[0]);
+		nr_parts = 1;
+	}
+
+	/* Prevent writes if not explicitly enabled */
+	if (!ispi->writeable || !writeable)
+		nor->mtd.flags &= ~MTD_WRITEABLE;
+	if (name)
+		nor->mtd.name = name;
+
+	return mtd_device_register(&nor->mtd, parts, nr_parts);
+}
+
+struct intel_spi *intel_spi_probe(struct device *dev,
+	struct resource *mem, const struct intel_spi_boardinfo *info)
+{
 	struct intel_spi *ispi;
 	int ret;
 
@@ -933,33 +1021,37 @@ struct intel_spi *intel_spi_probe(struct device *dev,
 	if (ret)
 		return ERR_PTR(ret);
 
-	ispi->nor.dev = ispi->dev;
-	ispi->nor.priv = ispi;
-	ispi->nor.controller_ops = &intel_spi_controller_ops;
-
-	ret = spi_nor_scan(&ispi->nor, NULL, &hwcaps);
-	if (ret) {
-		dev_info(dev, "failed to locate the chip\n");
+	ret = intel_spi_read_desc(ispi);
+	if (ret)
 		return ERR_PTR(ret);
-	}
-
-	intel_spi_fill_partition(ispi, &part);
 
-	/* Prevent writes if not explicitly enabled */
-	if (!ispi->writeable || !writeable)
-		ispi->nor.mtd.flags &= ~MTD_WRITEABLE;
-
-	ret = mtd_device_register(&ispi->nor.mtd, &part, 1);
+	ret = intel_spi_add_chip(ispi, &ispi->nor[0], NULL);
 	if (ret)
 		return ERR_PTR(ret);
 
+	if (ispi->nc > 1) {
+		ret = intel_spi_add_chip(ispi, &ispi->nor[1], "chip1");
+		if (ret) {
+			mtd_device_unregister(&ispi->nor[0].mtd);
+			return ERR_PTR(ret);
+		}
+	}
+
 	return ispi;
 }
 EXPORT_SYMBOL_GPL(intel_spi_probe);
 
 int intel_spi_remove(struct intel_spi *ispi)
 {
-	return mtd_device_unregister(&ispi->nor.mtd);
+	int i, ret;
+
+	for (i = ispi->nc - 1; i >= 0; i--) {
+		ret = mtd_device_unregister(&ispi->nor[i].mtd);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 EXPORT_SYMBOL_GPL(intel_spi_remove);
 
-- 
2.30.2




More information about the linux-mtd mailing list