summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/spi-nor
diff options
context:
space:
mode:
authorBin Meng <bmeng.cn@gmail.com>2017-09-11 11:41:57 +0200
committerCyrille Pitchen <cyrille.pitchen@wedev4u.fr>2017-10-11 09:47:04 +0200
commit8c473dd61bb55f4d1576b50947b54d101553517b (patch)
tree2059a08eec49f1152904be6a2cbfb9bf4008adaf /drivers/mtd/spi-nor
parentspi-nor: intel-spi: Remove 'Atomic Cycle Sequence' in intel_spi_write() (diff)
downloadlinux-8c473dd61bb55f4d1576b50947b54d101553517b.tar.xz
linux-8c473dd61bb55f4d1576b50947b54d101553517b.zip
spi-nor: intel-spi: Don't assume OPMENU0/1 to be programmed by BIOS
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@gmail.com> Acked-by: Mika Westerberg <mika.westerberg@linux.intel.com> Signed-off-by: Cyrille Pitchen <cyrille.pitchen@wedev4u.fr>
Diffstat (limited to 'drivers/mtd/spi-nor')
-rw-r--r--drivers/mtd/spi-nor/intel-spi.c91
1 files changed, 65 insertions, 26 deletions
diff --git a/drivers/mtd/spi-nor/intel-spi.c b/drivers/mtd/spi-nor/intel-spi.c
index 757b9f14fd73..07146ab19cac 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);
}