[PATCH v2 07/10] spi-nor: intel-spi: Don't assume OPMENU0/1 to be programmed by BIOS
From: Bin Meng
Date: Mon Sep 11 2017 - 05:39:06 EST
At present the driver relies on valid OPMENU0/OPMENU1 register values
that are programmed by BIOS to function correctly. However in a real
world it's absolutely legitimate for a bootloader to leave these two
registers untouched. Intel FSP for Baytrail exactly does like this.
When we are booting from any Intel FSP based bootloaders like U-Boot,
the driver refuses to work.
We can of course program various flash opcodes in the OPMENU0/OPMENU1
registers, and such workaround can be added in either the bootloader
codes, or the kernel driver itself.
But a graceful solution would be to update the kernel driver to remove
such limitation of OPMENU0/1 register dependency. The SPI controller
settings are not locked under such configuration. So we can first check
the controller locking status, and if it is not locked that means the
driver job can be fulfilled by using a chosen OPMENU index to set up
the flash opcode every time.
While we are here, the missing 'Atomic Cycle Sequence' handling in the
SW sequencer codes is also added.
Signed-off-by: Bin Meng <bmeng.cn@xxxxxxxxx>
Acked-by: Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx>
---
Changes in v2: None
drivers/mtd/spi-nor/intel-spi.c | 91 +++++++++++++++++++++++++++++------------
1 file changed, 65 insertions(+), 26 deletions(-)
diff --git a/drivers/mtd/spi-nor/intel-spi.c b/drivers/mtd/spi-nor/intel-spi.c
index 757b9f1..07146ab 100644
--- a/drivers/mtd/spi-nor/intel-spi.c
+++ b/drivers/mtd/spi-nor/intel-spi.c
@@ -88,6 +88,11 @@
#define OPMENU0 0x08
#define OPMENU1 0x0c
+#define OPTYPE_READ_NO_ADDR 0
+#define OPTYPE_WRITE_NO_ADDR 1
+#define OPTYPE_READ_WITH_ADDR 2
+#define OPTYPE_WRITE_WITH_ADDR 3
+
/* CPU specifics */
#define BYT_PR 0x74
#define BYT_SSFSTS_CTL 0x90
@@ -120,6 +125,7 @@
* @nregions: Maximum number of regions
* @pr_num: Maximum number of protected range registers
* @writeable: Is the chip writeable
+ * @locked: Is SPI setting locked
* @swseq: Use SW sequencer in register reads/writes
* @erase_64k: 64k erase supported
* @opcodes: Opcodes which are supported. This are programmed by BIOS
@@ -136,6 +142,7 @@ struct intel_spi {
size_t nregions;
size_t pr_num;
bool writeable;
+ bool locked;
bool swseq;
bool erase_64k;
u8 opcodes[8];
@@ -343,23 +350,29 @@ static int intel_spi_init(struct intel_spi *ispi)
writel(val, ispi->sregs + SSFSTS_CTL);
}
- /*
- * BIOS programs allowed opcodes and then locks down the register.
- * So read back what opcodes it decided to support. That's the set
- * we are going to support as well.
- */
- opmenu0 = readl(ispi->sregs + OPMENU0);
- opmenu1 = readl(ispi->sregs + OPMENU1);
+ /* Check controller's lock status */
+ val = readl(ispi->base + HSFSTS_CTL);
+ ispi->locked = !!(val & HSFSTS_CTL_FLOCKDN);
- if (opmenu0 && opmenu1) {
- for (i = 0; i < ARRAY_SIZE(ispi->opcodes) / 2; i++) {
- ispi->opcodes[i] = opmenu0 >> i * 8;
- ispi->opcodes[i + 4] = opmenu1 >> i * 8;
- }
+ if (ispi->locked) {
+ /*
+ * BIOS programs allowed opcodes and then locks down the
+ * register. So read back what opcodes it decided to support.
+ * That's the set we are going to support as well.
+ */
+ opmenu0 = readl(ispi->sregs + OPMENU0);
+ opmenu1 = readl(ispi->sregs + OPMENU1);
- val = readl(ispi->sregs + PREOP_OPTYPE);
- ispi->preopcodes[0] = val;
- ispi->preopcodes[1] = val >> 8;
+ if (opmenu0 && opmenu1) {
+ for (i = 0; i < ARRAY_SIZE(ispi->opcodes) / 2; i++) {
+ ispi->opcodes[i] = opmenu0 >> i * 8;
+ ispi->opcodes[i + 4] = opmenu1 >> i * 8;
+ }
+
+ val = readl(ispi->sregs + PREOP_OPTYPE);
+ ispi->preopcodes[0] = val;
+ ispi->preopcodes[1] = val >> 8;
+ }
}
intel_spi_dump_regs(ispi);
@@ -367,14 +380,25 @@ static int intel_spi_init(struct intel_spi *ispi)
return 0;
}
-static int intel_spi_opcode_index(struct intel_spi *ispi, u8 opcode)
+static int intel_spi_opcode_index(struct intel_spi *ispi, u8 opcode, int optype)
{
int i;
+ int preop;
- for (i = 0; i < ARRAY_SIZE(ispi->opcodes); i++)
- if (ispi->opcodes[i] == opcode)
- return i;
- return -EINVAL;
+ if (ispi->locked) {
+ for (i = 0; i < ARRAY_SIZE(ispi->opcodes); i++)
+ if (ispi->opcodes[i] == opcode)
+ return i;
+
+ return -EINVAL;
+ }
+
+ /* The lock is off, so just use index 0 */
+ writel(opcode, ispi->sregs + OPMENU0);
+ preop = readw(ispi->sregs + PREOP_OPTYPE);
+ writel(optype << 16 | preop, ispi->sregs + PREOP_OPTYPE);
+
+ return 0;
}
static int intel_spi_hw_cycle(struct intel_spi *ispi, u8 opcode, int len)
@@ -420,12 +444,14 @@ static int intel_spi_hw_cycle(struct intel_spi *ispi, u8 opcode, int len)
return 0;
}
-static int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, int len)
+static int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, int len,
+ int optype)
{
u32 val = 0, status;
+ u16 preop;
int ret;
- ret = intel_spi_opcode_index(ispi, opcode);
+ ret = intel_spi_opcode_index(ispi, opcode, optype);
if (ret < 0)
return ret;
@@ -438,6 +464,12 @@ static int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, int len)
val |= ret << SSFSTS_CTL_COP_SHIFT;
val |= SSFSTS_CTL_FCERR | SSFSTS_CTL_FDONE;
val |= SSFSTS_CTL_SCGO;
+ preop = readw(ispi->sregs + PREOP_OPTYPE);
+ if (preop) {
+ val |= SSFSTS_CTL_ACS;
+ if (preop >> 8)
+ val |= SSFSTS_CTL_SPOP;
+ }
writel(val, ispi->sregs + SSFSTS_CTL);
ret = intel_spi_wait_sw_busy(ispi);
@@ -462,7 +494,8 @@ static int intel_spi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
writel(0, ispi->base + FADDR);
if (ispi->swseq)
- ret = intel_spi_sw_cycle(ispi, opcode, len);
+ ret = intel_spi_sw_cycle(ispi, opcode, len,
+ OPTYPE_READ_NO_ADDR);
else
ret = intel_spi_hw_cycle(ispi, opcode, len);
@@ -479,10 +512,15 @@ static int intel_spi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
/*
* This is handled with atomic operation and preop code in Intel
- * controller so skip it here now.
+ * controller so skip it here now. If the controller is not locked,
+ * program the opcode to the PREOP register for later use.
*/
- if (opcode == SPINOR_OP_WREN)
+ if (opcode == SPINOR_OP_WREN) {
+ if (!ispi->locked)
+ writel(opcode, ispi->sregs + PREOP_OPTYPE);
+
return 0;
+ }
writel(0, ispi->base + FADDR);
@@ -492,7 +530,8 @@ static int intel_spi_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
return ret;
if (ispi->swseq)
- return intel_spi_sw_cycle(ispi, opcode, len);
+ return intel_spi_sw_cycle(ispi, opcode, len,
+ OPTYPE_WRITE_NO_ADDR);
return intel_spi_hw_cycle(ispi, opcode, len);
}
--
2.9.2