mtd: spi-nor: intel-spi: Convert to SPI MEM
authorMika Westerberg <mika.westerberg@linux.intel.com>
Wed, 9 Feb 2022 12:27:05 +0000 (15:27 +0300)
committerMark Brown <broonie@kernel.org>
Mon, 14 Feb 2022 12:53:09 +0000 (12:53 +0000)
The preferred way to implement SPI-NOR controller drivers is through SPI
subsubsystem utilizing the SPI MEM core functions. This converts the
Intel SPI flash controller driver over the SPI MEM by moving the driver
from SPI-NOR subsystem to SPI subsystem and in one go make it use the
SPI MEM functions. The driver name will be changed from intel-spi to
spi-intel to match the convention used in the SPI subsystem.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Reviewed-by: Mauro Lima <mauro.lima@eclypsium.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Acked-by: Lee Jones <lee.jones@linaro.org>
Acked-by: Pratyush Yadav <p.yadav@ti.com>
Reviewed-by: Tudor Ambarus <tudor.ambarus@microchip.com>
Link: https://lore.kernel.org/r/20220209122706.42439-3-mika.westerberg@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
15 files changed:
drivers/mtd/spi-nor/controllers/Kconfig
drivers/mtd/spi-nor/controllers/Makefile
drivers/mtd/spi-nor/controllers/intel-spi-pci.c [deleted file]
drivers/mtd/spi-nor/controllers/intel-spi-platform.c [deleted file]
drivers/mtd/spi-nor/controllers/intel-spi.c [deleted file]
drivers/mtd/spi-nor/controllers/intel-spi.h [deleted file]
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/spi-intel-pci.c [new file with mode: 0644]
drivers/spi/spi-intel-platform.c [new file with mode: 0644]
drivers/spi/spi-intel.c [new file with mode: 0644]
drivers/spi/spi-intel.h [new file with mode: 0644]
include/linux/mfd/lpc_ich.h
include/linux/platform_data/x86/intel-spi.h [deleted file]
include/linux/platform_data/x86/spi-intel.h [new file with mode: 0644]

index 5c0e0ec2e6d1f1002cf3f2f421d222e73a790cf8..50f4f3484d42618bf8ad82d5c5339043f6dd28fd 100644 (file)
@@ -26,39 +26,3 @@ config SPI_NXP_SPIFI
          SPIFI is a specialized controller for connecting serial SPI
          Flash. Enable this option if you have a device with a SPIFI
          controller and want to access the Flash as a mtd device.
-
-config SPI_INTEL_SPI
-       tristate
-
-config SPI_INTEL_SPI_PCI
-       tristate "Intel PCH/PCU SPI flash PCI driver (DANGEROUS)"
-       depends on X86 && PCI
-       select SPI_INTEL_SPI
-       help
-         This enables PCI support for the Intel PCH/PCU SPI controller in
-         master mode. This controller is present in modern Intel hardware
-         and is used to hold BIOS and other persistent settings. Using
-         this driver it is possible to upgrade BIOS directly from Linux.
-
-         Say N here unless you know what you are doing. Overwriting the
-         SPI flash may render the system unbootable.
-
-         To compile this driver as a module, choose M here: the module
-         will be called intel-spi-pci.
-
-config SPI_INTEL_SPI_PLATFORM
-       tristate "Intel PCH/PCU SPI flash platform driver (DANGEROUS)"
-       depends on X86
-       select SPI_INTEL_SPI
-       help
-         This enables platform support for the Intel PCH/PCU SPI
-         controller in master mode. This controller is present in modern
-         Intel hardware and is used to hold BIOS and other persistent
-         settings. Using this driver it is possible to upgrade BIOS
-         directly from Linux.
-
-         Say N here unless you know what you are doing. Overwriting the
-         SPI flash may render the system unbootable.
-
-         To compile this driver as a module, choose M here: the module
-         will be called intel-spi-platform.
index e7abba491d98329fbeb565e70f6ea75299102143..6e2a1dc684662aeac48c5f9cc1da722ded2a2f48 100644 (file)
@@ -2,6 +2,3 @@
 obj-$(CONFIG_SPI_ASPEED_SMC)   += aspeed-smc.o
 obj-$(CONFIG_SPI_HISI_SFC)     += hisi-sfc.o
 obj-$(CONFIG_SPI_NXP_SPIFI)    += nxp-spifi.o
-obj-$(CONFIG_SPI_INTEL_SPI)    += intel-spi.o
-obj-$(CONFIG_SPI_INTEL_SPI_PCI)        += intel-spi-pci.o
-obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM)   += intel-spi-platform.o
diff --git a/drivers/mtd/spi-nor/controllers/intel-spi-pci.c b/drivers/mtd/spi-nor/controllers/intel-spi-pci.c
deleted file mode 100644 (file)
index 508f7ca..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Intel PCH/PCU SPI flash PCI driver.
- *
- * Copyright (C) 2016, Intel Corporation
- * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
- */
-
-#include <linux/ioport.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/pci.h>
-
-#include "intel-spi.h"
-
-#define BCR            0xdc
-#define BCR_WPD                BIT(0)
-
-static bool intel_spi_pci_set_writeable(void __iomem *base, void *data)
-{
-       struct pci_dev *pdev = data;
-       u32 bcr;
-
-       /* Try to make the chip read/write */
-       pci_read_config_dword(pdev, BCR, &bcr);
-       if (!(bcr & BCR_WPD)) {
-               bcr |= BCR_WPD;
-               pci_write_config_dword(pdev, BCR, bcr);
-               pci_read_config_dword(pdev, BCR, &bcr);
-       }
-
-       return bcr & BCR_WPD;
-}
-
-static const struct intel_spi_boardinfo bxt_info = {
-       .type = INTEL_SPI_BXT,
-       .set_writeable = intel_spi_pci_set_writeable,
-};
-
-static const struct intel_spi_boardinfo cnl_info = {
-       .type = INTEL_SPI_CNL,
-       .set_writeable = intel_spi_pci_set_writeable,
-};
-
-static int intel_spi_pci_probe(struct pci_dev *pdev,
-                              const struct pci_device_id *id)
-{
-       struct intel_spi_boardinfo *info;
-       struct intel_spi *ispi;
-       int ret;
-
-       ret = pcim_enable_device(pdev);
-       if (ret)
-               return ret;
-
-       info = devm_kmemdup(&pdev->dev, (void *)id->driver_data, sizeof(*info),
-                           GFP_KERNEL);
-       if (!info)
-               return -ENOMEM;
-
-       info->data = pdev;
-       ispi = intel_spi_probe(&pdev->dev, &pdev->resource[0], info);
-       if (IS_ERR(ispi))
-               return PTR_ERR(ispi);
-
-       pci_set_drvdata(pdev, ispi);
-       return 0;
-}
-
-static void intel_spi_pci_remove(struct pci_dev *pdev)
-{
-       intel_spi_remove(pci_get_drvdata(pdev));
-}
-
-static const struct pci_device_id intel_spi_pci_ids[] = {
-       { PCI_VDEVICE(INTEL, 0x02a4), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x06a4), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x18e0), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x19e0), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x1bca), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x34a4), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x43a4), (unsigned long)&cnl_info },
-       { PCI_VDEVICE(INTEL, 0x4b24), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x4da4), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0x51a4), (unsigned long)&cnl_info },
-       { PCI_VDEVICE(INTEL, 0x54a4), (unsigned long)&cnl_info },
-       { PCI_VDEVICE(INTEL, 0x7aa4), (unsigned long)&cnl_info },
-       { PCI_VDEVICE(INTEL, 0xa0a4), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0xa1a4), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0xa224), (unsigned long)&bxt_info },
-       { PCI_VDEVICE(INTEL, 0xa324), (unsigned long)&cnl_info },
-       { PCI_VDEVICE(INTEL, 0xa3a4), (unsigned long)&bxt_info },
-       { },
-};
-MODULE_DEVICE_TABLE(pci, intel_spi_pci_ids);
-
-static struct pci_driver intel_spi_pci_driver = {
-       .name = "intel-spi",
-       .id_table = intel_spi_pci_ids,
-       .probe = intel_spi_pci_probe,
-       .remove = intel_spi_pci_remove,
-};
-
-module_pci_driver(intel_spi_pci_driver);
-
-MODULE_DESCRIPTION("Intel PCH/PCU SPI flash PCI driver");
-MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/mtd/spi-nor/controllers/intel-spi-platform.c b/drivers/mtd/spi-nor/controllers/intel-spi-platform.c
deleted file mode 100644 (file)
index f80f108..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Intel PCH/PCU SPI flash platform driver.
- *
- * Copyright (C) 2016, Intel Corporation
- * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
- */
-
-#include <linux/ioport.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-
-#include "intel-spi.h"
-
-static int intel_spi_platform_probe(struct platform_device *pdev)
-{
-       struct intel_spi_boardinfo *info;
-       struct intel_spi *ispi;
-       struct resource *mem;
-
-       info = dev_get_platdata(&pdev->dev);
-       if (!info)
-               return -EINVAL;
-
-       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       ispi = intel_spi_probe(&pdev->dev, mem, info);
-       if (IS_ERR(ispi))
-               return PTR_ERR(ispi);
-
-       platform_set_drvdata(pdev, ispi);
-       return 0;
-}
-
-static int intel_spi_platform_remove(struct platform_device *pdev)
-{
-       struct intel_spi *ispi = platform_get_drvdata(pdev);
-
-       return intel_spi_remove(ispi);
-}
-
-static struct platform_driver intel_spi_platform_driver = {
-       .probe = intel_spi_platform_probe,
-       .remove = intel_spi_platform_remove,
-       .driver = {
-               .name = "intel-spi",
-       },
-};
-
-module_platform_driver(intel_spi_platform_driver);
-
-MODULE_DESCRIPTION("Intel PCH/PCU SPI flash platform driver");
-MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:intel-spi");
diff --git a/drivers/mtd/spi-nor/controllers/intel-spi.c b/drivers/mtd/spi-nor/controllers/intel-spi.c
deleted file mode 100644 (file)
index f35597c..0000000
+++ /dev/null
@@ -1,965 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Intel PCH/PCU SPI flash driver.
- *
- * Copyright (C) 2016, Intel Corporation
- * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
- */
-
-#include <linux/err.h>
-#include <linux/io.h>
-#include <linux/iopoll.h>
-#include <linux/module.h>
-#include <linux/sched.h>
-#include <linux/sizes.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/mtd/spi-nor.h>
-
-#include "intel-spi.h"
-
-/* Offsets are from @ispi->base */
-#define BFPREG                         0x00
-
-#define HSFSTS_CTL                     0x04
-#define HSFSTS_CTL_FSMIE               BIT(31)
-#define HSFSTS_CTL_FDBC_SHIFT          24
-#define HSFSTS_CTL_FDBC_MASK           (0x3f << HSFSTS_CTL_FDBC_SHIFT)
-
-#define HSFSTS_CTL_FCYCLE_SHIFT                17
-#define HSFSTS_CTL_FCYCLE_MASK         (0x0f << HSFSTS_CTL_FCYCLE_SHIFT)
-/* HW sequencer opcodes */
-#define HSFSTS_CTL_FCYCLE_READ         (0x00 << HSFSTS_CTL_FCYCLE_SHIFT)
-#define HSFSTS_CTL_FCYCLE_WRITE                (0x02 << HSFSTS_CTL_FCYCLE_SHIFT)
-#define HSFSTS_CTL_FCYCLE_ERASE                (0x03 << HSFSTS_CTL_FCYCLE_SHIFT)
-#define HSFSTS_CTL_FCYCLE_ERASE_64K    (0x04 << HSFSTS_CTL_FCYCLE_SHIFT)
-#define HSFSTS_CTL_FCYCLE_RDID         (0x06 << HSFSTS_CTL_FCYCLE_SHIFT)
-#define HSFSTS_CTL_FCYCLE_WRSR         (0x07 << HSFSTS_CTL_FCYCLE_SHIFT)
-#define HSFSTS_CTL_FCYCLE_RDSR         (0x08 << HSFSTS_CTL_FCYCLE_SHIFT)
-
-#define HSFSTS_CTL_FGO                 BIT(16)
-#define HSFSTS_CTL_FLOCKDN             BIT(15)
-#define HSFSTS_CTL_FDV                 BIT(14)
-#define HSFSTS_CTL_SCIP                        BIT(5)
-#define HSFSTS_CTL_AEL                 BIT(2)
-#define HSFSTS_CTL_FCERR               BIT(1)
-#define HSFSTS_CTL_FDONE               BIT(0)
-
-#define FADDR                          0x08
-#define DLOCK                          0x0c
-#define FDATA(n)                       (0x10 + ((n) * 4))
-
-#define FRACC                          0x50
-
-#define FREG(n)                                (0x54 + ((n) * 4))
-#define FREG_BASE_MASK                 0x3fff
-#define FREG_LIMIT_SHIFT               16
-#define FREG_LIMIT_MASK                        (0x03fff << FREG_LIMIT_SHIFT)
-
-/* Offset is from @ispi->pregs */
-#define PR(n)                          ((n) * 4)
-#define PR_WPE                         BIT(31)
-#define PR_LIMIT_SHIFT                 16
-#define PR_LIMIT_MASK                  (0x3fff << PR_LIMIT_SHIFT)
-#define PR_RPE                         BIT(15)
-#define PR_BASE_MASK                   0x3fff
-
-/* Offsets are from @ispi->sregs */
-#define SSFSTS_CTL                     0x00
-#define SSFSTS_CTL_FSMIE               BIT(23)
-#define SSFSTS_CTL_DS                  BIT(22)
-#define SSFSTS_CTL_DBC_SHIFT           16
-#define SSFSTS_CTL_SPOP                        BIT(11)
-#define SSFSTS_CTL_ACS                 BIT(10)
-#define SSFSTS_CTL_SCGO                        BIT(9)
-#define SSFSTS_CTL_COP_SHIFT           12
-#define SSFSTS_CTL_FRS                 BIT(7)
-#define SSFSTS_CTL_DOFRS               BIT(6)
-#define SSFSTS_CTL_AEL                 BIT(4)
-#define SSFSTS_CTL_FCERR               BIT(3)
-#define SSFSTS_CTL_FDONE               BIT(2)
-#define SSFSTS_CTL_SCIP                        BIT(0)
-
-#define PREOP_OPTYPE                   0x04
-#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
-#define BYT_BCR                                0xfc
-#define BYT_BCR_WPD                    BIT(0)
-#define BYT_FREG_NUM                   5
-#define BYT_PR_NUM                     5
-
-#define LPT_PR                         0x74
-#define LPT_SSFSTS_CTL                 0x90
-#define LPT_FREG_NUM                   5
-#define LPT_PR_NUM                     5
-
-#define BXT_PR                         0x84
-#define BXT_SSFSTS_CTL                 0xa0
-#define BXT_FREG_NUM                   12
-#define BXT_PR_NUM                     6
-
-#define CNL_PR                         0x84
-#define CNL_FREG_NUM                   6
-#define CNL_PR_NUM                     5
-
-#define LVSCC                          0xc4
-#define UVSCC                          0xc8
-#define ERASE_OPCODE_SHIFT             8
-#define ERASE_OPCODE_MASK              (0xff << ERASE_OPCODE_SHIFT)
-#define ERASE_64K_OPCODE_SHIFT         16
-#define ERASE_64K_OPCODE_MASK          (0xff << ERASE_OPCODE_SHIFT)
-
-#define INTEL_SPI_TIMEOUT              5000 /* ms */
-#define INTEL_SPI_FIFO_SZ              64
-
-/**
- * struct intel_spi - Driver private data
- * @dev: Device pointer
- * @info: Pointer to board specific info
- * @nor: SPI NOR layer structure
- * @base: Beginning of MMIO space
- * @pregs: Start of protection registers
- * @sregs: Start of software sequencer registers
- * @nregions: Maximum number of regions
- * @pr_num: Maximum number of protected range registers
- * @locked: Is SPI setting locked
- * @swseq_reg: Use SW sequencer in register reads/writes
- * @swseq_erase: Use SW sequencer in erase operation
- * @erase_64k: 64k erase supported
- * @atomic_preopcode: Holds preopcode when atomic sequence is requested
- * @opcodes: Opcodes which are supported. This are programmed by BIOS
- *           before it locks down the controller.
- */
-struct intel_spi {
-       struct device *dev;
-       const struct intel_spi_boardinfo *info;
-       struct spi_nor nor;
-       void __iomem *base;
-       void __iomem *pregs;
-       void __iomem *sregs;
-       size_t nregions;
-       size_t pr_num;
-       bool locked;
-       bool swseq_reg;
-       bool swseq_erase;
-       bool erase_64k;
-       u8 atomic_preopcode;
-       u8 opcodes[8];
-};
-
-static bool writeable;
-module_param(writeable, bool, 0);
-MODULE_PARM_DESC(writeable, "Enable write access to SPI flash chip (default=0)");
-
-static void intel_spi_dump_regs(struct intel_spi *ispi)
-{
-       u32 value;
-       int i;
-
-       dev_dbg(ispi->dev, "BFPREG=0x%08x\n", readl(ispi->base + BFPREG));
-
-       value = readl(ispi->base + HSFSTS_CTL);
-       dev_dbg(ispi->dev, "HSFSTS_CTL=0x%08x\n", value);
-       if (value & HSFSTS_CTL_FLOCKDN)
-               dev_dbg(ispi->dev, "-> Locked\n");
-
-       dev_dbg(ispi->dev, "FADDR=0x%08x\n", readl(ispi->base + FADDR));
-       dev_dbg(ispi->dev, "DLOCK=0x%08x\n", readl(ispi->base + DLOCK));
-
-       for (i = 0; i < 16; i++)
-               dev_dbg(ispi->dev, "FDATA(%d)=0x%08x\n",
-                       i, readl(ispi->base + FDATA(i)));
-
-       dev_dbg(ispi->dev, "FRACC=0x%08x\n", readl(ispi->base + FRACC));
-
-       for (i = 0; i < ispi->nregions; i++)
-               dev_dbg(ispi->dev, "FREG(%d)=0x%08x\n", i,
-                       readl(ispi->base + FREG(i)));
-       for (i = 0; i < ispi->pr_num; i++)
-               dev_dbg(ispi->dev, "PR(%d)=0x%08x\n", i,
-                       readl(ispi->pregs + PR(i)));
-
-       if (ispi->sregs) {
-               value = readl(ispi->sregs + SSFSTS_CTL);
-               dev_dbg(ispi->dev, "SSFSTS_CTL=0x%08x\n", value);
-               dev_dbg(ispi->dev, "PREOP_OPTYPE=0x%08x\n",
-                       readl(ispi->sregs + PREOP_OPTYPE));
-               dev_dbg(ispi->dev, "OPMENU0=0x%08x\n",
-                       readl(ispi->sregs + OPMENU0));
-               dev_dbg(ispi->dev, "OPMENU1=0x%08x\n",
-                       readl(ispi->sregs + OPMENU1));
-       }
-
-       if (ispi->info->type == INTEL_SPI_BYT)
-               dev_dbg(ispi->dev, "BCR=0x%08x\n", readl(ispi->base + BYT_BCR));
-
-       dev_dbg(ispi->dev, "LVSCC=0x%08x\n", readl(ispi->base + LVSCC));
-       dev_dbg(ispi->dev, "UVSCC=0x%08x\n", readl(ispi->base + UVSCC));
-
-       dev_dbg(ispi->dev, "Protected regions:\n");
-       for (i = 0; i < ispi->pr_num; i++) {
-               u32 base, limit;
-
-               value = readl(ispi->pregs + PR(i));
-               if (!(value & (PR_WPE | PR_RPE)))
-                       continue;
-
-               limit = (value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT;
-               base = value & PR_BASE_MASK;
-
-               dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x [%c%c]\n",
-                        i, base << 12, (limit << 12) | 0xfff,
-                        value & PR_WPE ? 'W' : '.',
-                        value & PR_RPE ? 'R' : '.');
-       }
-
-       dev_dbg(ispi->dev, "Flash regions:\n");
-       for (i = 0; i < ispi->nregions; i++) {
-               u32 region, base, limit;
-
-               region = readl(ispi->base + FREG(i));
-               base = region & FREG_BASE_MASK;
-               limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT;
-
-               if (base >= limit || (i > 0 && limit == 0))
-                       dev_dbg(ispi->dev, " %02d disabled\n", i);
-               else
-                       dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x\n",
-                                i, base << 12, (limit << 12) | 0xfff);
-       }
-
-       dev_dbg(ispi->dev, "Using %cW sequencer for register access\n",
-               ispi->swseq_reg ? 'S' : 'H');
-       dev_dbg(ispi->dev, "Using %cW sequencer for erase operation\n",
-               ispi->swseq_erase ? 'S' : 'H');
-}
-
-/* Reads max INTEL_SPI_FIFO_SZ bytes from the device fifo */
-static int intel_spi_read_block(struct intel_spi *ispi, void *buf, size_t size)
-{
-       size_t bytes;
-       int i = 0;
-
-       if (size > INTEL_SPI_FIFO_SZ)
-               return -EINVAL;
-
-       while (size > 0) {
-               bytes = min_t(size_t, size, 4);
-               memcpy_fromio(buf, ispi->base + FDATA(i), bytes);
-               size -= bytes;
-               buf += bytes;
-               i++;
-       }
-
-       return 0;
-}
-
-/* Writes max INTEL_SPI_FIFO_SZ bytes to the device fifo */
-static int intel_spi_write_block(struct intel_spi *ispi, const void *buf,
-                                size_t size)
-{
-       size_t bytes;
-       int i = 0;
-
-       if (size > INTEL_SPI_FIFO_SZ)
-               return -EINVAL;
-
-       while (size > 0) {
-               bytes = min_t(size_t, size, 4);
-               memcpy_toio(ispi->base + FDATA(i), buf, bytes);
-               size -= bytes;
-               buf += bytes;
-               i++;
-       }
-
-       return 0;
-}
-
-static int intel_spi_wait_hw_busy(struct intel_spi *ispi)
-{
-       u32 val;
-
-       return readl_poll_timeout(ispi->base + HSFSTS_CTL, val,
-                                 !(val & HSFSTS_CTL_SCIP), 0,
-                                 INTEL_SPI_TIMEOUT * 1000);
-}
-
-static int intel_spi_wait_sw_busy(struct intel_spi *ispi)
-{
-       u32 val;
-
-       return readl_poll_timeout(ispi->sregs + SSFSTS_CTL, val,
-                                 !(val & SSFSTS_CTL_SCIP), 0,
-                                 INTEL_SPI_TIMEOUT * 1000);
-}
-
-static bool intel_spi_set_writeable(struct intel_spi *ispi)
-{
-       if (!ispi->info->set_writeable)
-               return false;
-
-       return ispi->info->set_writeable(ispi->base, ispi->info->data);
-}
-
-static int intel_spi_init(struct intel_spi *ispi)
-{
-       u32 opmenu0, opmenu1, lvscc, uvscc, val;
-       int i;
-
-       switch (ispi->info->type) {
-       case INTEL_SPI_BYT:
-               ispi->sregs = ispi->base + BYT_SSFSTS_CTL;
-               ispi->pregs = ispi->base + BYT_PR;
-               ispi->nregions = BYT_FREG_NUM;
-               ispi->pr_num = BYT_PR_NUM;
-               ispi->swseq_reg = true;
-               break;
-
-       case INTEL_SPI_LPT:
-               ispi->sregs = ispi->base + LPT_SSFSTS_CTL;
-               ispi->pregs = ispi->base + LPT_PR;
-               ispi->nregions = LPT_FREG_NUM;
-               ispi->pr_num = LPT_PR_NUM;
-               ispi->swseq_reg = true;
-               break;
-
-       case INTEL_SPI_BXT:
-               ispi->sregs = ispi->base + BXT_SSFSTS_CTL;
-               ispi->pregs = ispi->base + BXT_PR;
-               ispi->nregions = BXT_FREG_NUM;
-               ispi->pr_num = BXT_PR_NUM;
-               ispi->erase_64k = true;
-               break;
-
-       case INTEL_SPI_CNL:
-               ispi->sregs = NULL;
-               ispi->pregs = ispi->base + CNL_PR;
-               ispi->nregions = CNL_FREG_NUM;
-               ispi->pr_num = CNL_PR_NUM;
-               break;
-
-       default:
-               return -EINVAL;
-       }
-
-       /* Try to disable write protection if user asked to do so */
-       if (writeable && !intel_spi_set_writeable(ispi)) {
-               dev_warn(ispi->dev, "can't disable chip write protection\n");
-               writeable = false;
-       }
-
-       /* Disable #SMI generation from HW sequencer */
-       val = readl(ispi->base + HSFSTS_CTL);
-       val &= ~HSFSTS_CTL_FSMIE;
-       writel(val, ispi->base + HSFSTS_CTL);
-
-       /*
-        * Determine whether erase operation should use HW or SW sequencer.
-        *
-        * The HW sequencer has a predefined list of opcodes, with only the
-        * erase opcode being programmable in LVSCC and UVSCC registers.
-        * If these registers don't contain a valid erase opcode, erase
-        * cannot be done using HW sequencer.
-        */
-       lvscc = readl(ispi->base + LVSCC);
-       uvscc = readl(ispi->base + UVSCC);
-       if (!(lvscc & ERASE_OPCODE_MASK) || !(uvscc & ERASE_OPCODE_MASK))
-               ispi->swseq_erase = true;
-       /* SPI controller on Intel BXT supports 64K erase opcode */
-       if (ispi->info->type == INTEL_SPI_BXT && !ispi->swseq_erase)
-               if (!(lvscc & ERASE_64K_OPCODE_MASK) ||
-                   !(uvscc & ERASE_64K_OPCODE_MASK))
-                       ispi->erase_64k = false;
-
-       if (ispi->sregs == NULL && (ispi->swseq_reg || ispi->swseq_erase)) {
-               dev_err(ispi->dev, "software sequencer not supported, but required\n");
-               return -EINVAL;
-       }
-
-       /*
-        * Some controllers can only do basic operations using hardware
-        * sequencer. All other operations are supposed to be carried out
-        * using software sequencer.
-        */
-       if (ispi->swseq_reg) {
-               /* Disable #SMI generation from SW sequencer */
-               val = readl(ispi->sregs + SSFSTS_CTL);
-               val &= ~SSFSTS_CTL_FSMIE;
-               writel(val, ispi->sregs + SSFSTS_CTL);
-       }
-
-       /* Check controller's lock status */
-       val = readl(ispi->base + HSFSTS_CTL);
-       ispi->locked = !!(val & HSFSTS_CTL_FLOCKDN);
-
-       if (ispi->locked && ispi->sregs) {
-               /*
-                * 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);
-
-               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;
-                       }
-               }
-       }
-
-       intel_spi_dump_regs(ispi);
-
-       return 0;
-}
-
-static int intel_spi_opcode_index(struct intel_spi *ispi, u8 opcode, int optype)
-{
-       int i;
-       int preop;
-
-       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, size_t len)
-{
-       u32 val, status;
-       int ret;
-
-       val = readl(ispi->base + HSFSTS_CTL);
-       val &= ~(HSFSTS_CTL_FCYCLE_MASK | HSFSTS_CTL_FDBC_MASK);
-
-       switch (opcode) {
-       case SPINOR_OP_RDID:
-               val |= HSFSTS_CTL_FCYCLE_RDID;
-               break;
-       case SPINOR_OP_WRSR:
-               val |= HSFSTS_CTL_FCYCLE_WRSR;
-               break;
-       case SPINOR_OP_RDSR:
-               val |= HSFSTS_CTL_FCYCLE_RDSR;
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       if (len > INTEL_SPI_FIFO_SZ)
-               return -EINVAL;
-
-       val |= (len - 1) << HSFSTS_CTL_FDBC_SHIFT;
-       val |= HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
-       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)
-               return -EIO;
-       else if (status & HSFSTS_CTL_AEL)
-               return -EACCES;
-
-       return 0;
-}
-
-static int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, size_t len,
-                             int optype)
-{
-       u32 val = 0, status;
-       u8 atomic_preopcode;
-       int ret;
-
-       ret = intel_spi_opcode_index(ispi, opcode, optype);
-       if (ret < 0)
-               return ret;
-
-       if (len > INTEL_SPI_FIFO_SZ)
-               return -EINVAL;
-
-       /*
-        * Always clear it after each SW sequencer operation regardless
-        * of whether it is successful or not.
-        */
-       atomic_preopcode = ispi->atomic_preopcode;
-       ispi->atomic_preopcode = 0;
-
-       /* Only mark 'Data Cycle' bit when there is data to be transferred */
-       if (len > 0)
-               val = ((len - 1) << SSFSTS_CTL_DBC_SHIFT) | SSFSTS_CTL_DS;
-       val |= ret << SSFSTS_CTL_COP_SHIFT;
-       val |= SSFSTS_CTL_FCERR | SSFSTS_CTL_FDONE;
-       val |= SSFSTS_CTL_SCGO;
-       if (atomic_preopcode) {
-               u16 preop;
-
-               switch (optype) {
-               case OPTYPE_WRITE_NO_ADDR:
-               case OPTYPE_WRITE_WITH_ADDR:
-                       /* Pick matching preopcode for the atomic sequence */
-                       preop = readw(ispi->sregs + PREOP_OPTYPE);
-                       if ((preop & 0xff) == atomic_preopcode)
-                               ; /* Do nothing */
-                       else if ((preop >> 8) == atomic_preopcode)
-                               val |= SSFSTS_CTL_SPOP;
-                       else
-                               return -EINVAL;
-
-                       /* Enable atomic sequence */
-                       val |= SSFSTS_CTL_ACS;
-                       break;
-
-               default:
-                       return -EINVAL;
-               }
-
-       }
-       writel(val, ispi->sregs + SSFSTS_CTL);
-
-       ret = intel_spi_wait_sw_busy(ispi);
-       if (ret)
-               return ret;
-
-       status = readl(ispi->sregs + SSFSTS_CTL);
-       if (status & SSFSTS_CTL_FCERR)
-               return -EIO;
-       else if (status & SSFSTS_CTL_AEL)
-               return -EACCES;
-
-       return 0;
-}
-
-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);
-
-       if (ispi->swseq_reg)
-               ret = intel_spi_sw_cycle(ispi, opcode, len,
-                                        OPTYPE_READ_NO_ADDR);
-       else
-               ret = intel_spi_hw_cycle(ispi, opcode, len);
-
-       if (ret)
-               return ret;
-
-       return intel_spi_read_block(ispi, buf, len);
-}
-
-static int intel_spi_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf,
-                              size_t len)
-{
-       struct intel_spi *ispi = nor->priv;
-       int ret;
-
-       /*
-        * This is handled with atomic operation and preop code in Intel
-        * controller so we only verify that it is available. If the
-        * controller is not locked, program the opcode to the PREOP
-        * register for later use.
-        *
-        * When hardware sequencer is used there is no need to program
-        * any opcodes (it handles them automatically as part of a command).
-        */
-       if (opcode == SPINOR_OP_WREN) {
-               u16 preop;
-
-               if (!ispi->swseq_reg)
-                       return 0;
-
-               preop = readw(ispi->sregs + PREOP_OPTYPE);
-               if ((preop & 0xff) != opcode && (preop >> 8) != opcode) {
-                       if (ispi->locked)
-                               return -EINVAL;
-                       writel(opcode, ispi->sregs + PREOP_OPTYPE);
-               }
-
-               /*
-                * This enables atomic sequence on next SW sycle. Will
-                * be cleared after next operation.
-                */
-               ispi->atomic_preopcode = opcode;
-               return 0;
-       }
-
-       /*
-        * We hope that HW sequencer will do the right thing automatically and
-        * with the SW sequencer we cannot use preopcode anyway, so just ignore
-        * the Write Disable operation and pretend it was completed
-        * successfully.
-        */
-       if (opcode == SPINOR_OP_WRDI)
-               return 0;
-
-       writel(0, ispi->base + FADDR);
-
-       /* Write the value beforehand */
-       ret = intel_spi_write_block(ispi, buf, len);
-       if (ret)
-               return ret;
-
-       if (ispi->swseq_reg)
-               return intel_spi_sw_cycle(ispi, opcode, len,
-                                         OPTYPE_WRITE_NO_ADDR);
-       return intel_spi_hw_cycle(ispi, opcode, 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;
-
-       /*
-        * Atomic sequence is not expected with HW sequencer reads. Make
-        * sure it is cleared regardless.
-        */
-       if (WARN_ON_ONCE(ispi->atomic_preopcode))
-               ispi->atomic_preopcode = 0;
-
-       switch (nor->read_opcode) {
-       case SPINOR_OP_READ:
-       case SPINOR_OP_READ_FAST:
-       case SPINOR_OP_READ_4B:
-       case SPINOR_OP_READ_FAST_4B:
-               break;
-       default:
-               return -EINVAL;
-       }
-
-       while (len > 0) {
-               block_size = min_t(size_t, len, INTEL_SPI_FIFO_SZ);
-
-               /* Read cannot cross 4K boundary */
-               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);
-               if (ret)
-                       return ret;
-
-               len -= block_size;
-               from += block_size;
-               retlen += block_size;
-               read_buf += block_size;
-       }
-
-       return retlen;
-}
-
-static ssize_t intel_spi_write(struct spi_nor *nor, loff_t to, size_t len,
-                              const u_char *write_buf)
-{
-       struct intel_spi *ispi = nor->priv;
-       size_t block_size, retlen = 0;
-       u32 val, status;
-       ssize_t ret;
-
-       /* Not needed with HW sequencer write, make sure it is cleared */
-       ispi->atomic_preopcode = 0;
-
-       while (len > 0) {
-               block_size = min_t(size_t, len, INTEL_SPI_FIFO_SZ);
-
-               /* Write cannot cross 4K boundary */
-               block_size = min_t(loff_t, to + block_size,
-                                  round_up(to + 1, SZ_4K)) - to;
-
-               writel(to, 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_WRITE;
-
-               ret = intel_spi_write_block(ispi, write_buf, block_size);
-               if (ret) {
-                       dev_err(ispi->dev, "failed to write block\n");
-                       return ret;
-               }
-
-               /* Start the write now */
-               val |= HSFSTS_CTL_FGO;
-               writel(val, ispi->base + HSFSTS_CTL);
-
-               ret = intel_spi_wait_hw_busy(ispi);
-               if (ret) {
-                       dev_err(ispi->dev, "timeout\n");
-                       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, "write error: %llx: %#x\n", to,
-                               status);
-                       return ret;
-               }
-
-               len -= block_size;
-               to += block_size;
-               retlen += block_size;
-               write_buf += block_size;
-       }
-
-       return retlen;
-}
-
-static int intel_spi_erase(struct spi_nor *nor, loff_t offs)
-{
-       size_t erase_size, len = nor->mtd.erasesize;
-       struct intel_spi *ispi = nor->priv;
-       u32 val, status, cmd;
-       int ret;
-
-       /* If the hardware can do 64k erase use that when possible */
-       if (len >= SZ_64K && ispi->erase_64k) {
-               cmd = HSFSTS_CTL_FCYCLE_ERASE_64K;
-               erase_size = SZ_64K;
-       } else {
-               cmd = HSFSTS_CTL_FCYCLE_ERASE;
-               erase_size = SZ_4K;
-       }
-
-       if (ispi->swseq_erase) {
-               while (len > 0) {
-                       writel(offs, ispi->base + FADDR);
-
-                       ret = intel_spi_sw_cycle(ispi, nor->erase_opcode,
-                                                0, OPTYPE_WRITE_WITH_ADDR);
-                       if (ret)
-                               return ret;
-
-                       offs += erase_size;
-                       len -= erase_size;
-               }
-
-               return 0;
-       }
-
-       /* Not needed with HW sequencer erase, make sure it is cleared */
-       ispi->atomic_preopcode = 0;
-
-       while (len > 0) {
-               writel(offs, 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 |= cmd;
-               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)
-                       return -EIO;
-               else if (status & HSFSTS_CTL_AEL)
-                       return -EACCES;
-
-               offs += erase_size;
-               len -= erase_size;
-       }
-
-       return 0;
-}
-
-static bool intel_spi_is_protected(const struct intel_spi *ispi,
-                                  unsigned int base, unsigned int limit)
-{
-       int i;
-
-       for (i = 0; i < ispi->pr_num; i++) {
-               u32 pr_base, pr_limit, pr_value;
-
-               pr_value = readl(ispi->pregs + PR(i));
-               if (!(pr_value & (PR_WPE | PR_RPE)))
-                       continue;
-
-               pr_limit = (pr_value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT;
-               pr_base = pr_value & PR_BASE_MASK;
-
-               if (pr_base >= base && pr_limit <= limit)
-                       return true;
-       }
-
-       return false;
-}
-
-/*
- * There will be a single partition holding all enabled flash regions. We
- * call this "BIOS".
- */
-static void intel_spi_fill_partition(struct intel_spi *ispi,
-                                    struct mtd_partition *part)
-{
-       u64 end;
-       int i;
-
-       memset(part, 0, sizeof(*part));
-
-       /* Start from the mandatory descriptor region */
-       part->size = 4096;
-       part->name = "BIOS";
-
-       /*
-        * Now try to find where this partition ends based on the flash
-        * region registers.
-        */
-       for (i = 1; i < ispi->nregions; i++) {
-               u32 region, base, limit;
-
-               region = readl(ispi->base + FREG(i));
-               base = region & FREG_BASE_MASK;
-               limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT;
-
-               if (base >= limit || limit == 0)
-                       continue;
-
-               /*
-                * If any of the regions have protection bits set, make the
-                * whole partition read-only to be on the safe side.
-                *
-                * Also if the user did not ask the chip to be writeable
-                * mask the bit too.
-                */
-               if (!writeable || intel_spi_is_protected(ispi, base, limit))
-                       part->mask_flags |= MTD_WRITEABLE;
-
-               end = (limit << 12) + 4096;
-               if (end > part->size)
-                       part->size = end;
-       }
-}
-
-static const struct spi_nor_controller_ops intel_spi_controller_ops = {
-       .read_reg = intel_spi_read_reg,
-       .write_reg = intel_spi_write_reg,
-       .read = intel_spi_read,
-       .write = intel_spi_write,
-       .erase = intel_spi_erase,
-};
-
-struct intel_spi *intel_spi_probe(struct device *dev,
-       struct resource *mem, const struct intel_spi_boardinfo *info)
-{
-       const struct spi_nor_hwcaps hwcaps = {
-               .mask = SNOR_HWCAPS_READ |
-                       SNOR_HWCAPS_READ_FAST |
-                       SNOR_HWCAPS_PP,
-       };
-       struct mtd_partition part;
-       struct intel_spi *ispi;
-       int ret;
-
-       if (!info || !mem)
-               return ERR_PTR(-EINVAL);
-
-       ispi = devm_kzalloc(dev, sizeof(*ispi), GFP_KERNEL);
-       if (!ispi)
-               return ERR_PTR(-ENOMEM);
-
-       ispi->base = devm_ioremap_resource(dev, mem);
-       if (IS_ERR(ispi->base))
-               return ERR_CAST(ispi->base);
-
-       ispi->dev = dev;
-       ispi->info = info;
-
-       ret = intel_spi_init(ispi);
-       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");
-               return ERR_PTR(ret);
-       }
-
-       intel_spi_fill_partition(ispi, &part);
-
-       ret = mtd_device_register(&ispi->nor.mtd, &part, 1);
-       if (ret)
-               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);
-}
-EXPORT_SYMBOL_GPL(intel_spi_remove);
-
-MODULE_DESCRIPTION("Intel PCH/PCU SPI flash core driver");
-MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/mtd/spi-nor/controllers/intel-spi.h b/drivers/mtd/spi-nor/controllers/intel-spi.h
deleted file mode 100644 (file)
index f287117..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * Intel PCH/PCU SPI flash driver.
- *
- * Copyright (C) 2016, Intel Corporation
- * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
- */
-
-#ifndef INTEL_SPI_H
-#define INTEL_SPI_H
-
-#include <linux/platform_data/x86/intel-spi.h>
-
-struct intel_spi;
-struct resource;
-
-struct intel_spi *intel_spi_probe(struct device *dev,
-       struct resource *mem, const struct intel_spi_boardinfo *info);
-int intel_spi_remove(struct intel_spi *ispi);
-
-#endif /* INTEL_SPI_H */
index b2a8821971e1d184953a40ab2a44040d977e2201..0201257511fbe93786522df5763cba0b64c9fc53 100644 (file)
@@ -427,6 +427,45 @@ config SPI_INGENIC
          To compile this driver as a module, choose M here: the module
          will be called spi-ingenic.
 
+config SPI_INTEL
+       tristate
+
+config SPI_INTEL_PCI
+       tristate "Intel PCH/PCU SPI flash PCI driver (DANGEROUS)"
+       depends on PCI
+       depends on X86 || COMPILE_TEST
+       depends on SPI_MEM
+       select SPI_INTEL
+       help
+         This enables PCI support for the Intel PCH/PCU SPI controller in
+         master mode. This controller is present in modern Intel hardware
+         and is used to hold BIOS and other persistent settings. Using
+         this driver it is possible to upgrade BIOS directly from Linux.
+
+         Say N here unless you know what you are doing. Overwriting the
+         SPI flash may render the system unbootable.
+
+         To compile this driver as a module, choose M here: the module
+         will be called spi-intel-pci.
+
+config SPI_INTEL_PLATFORM
+       tristate "Intel PCH/PCU SPI flash platform driver (DANGEROUS)"
+       depends on X86 || COMPILE_TEST
+       depends on SPI_MEM
+       select SPI_INTEL
+       help
+         This enables platform support for the Intel PCH/PCU SPI
+         controller in master mode. This controller is present in modern
+         Intel hardware and is used to hold BIOS and other persistent
+         settings. Using this driver it is possible to upgrade BIOS
+         directly from Linux.
+
+         Say N here unless you know what you are doing. Overwriting the
+         SPI flash may render the system unbootable.
+
+         To compile this driver as a module, choose M here: the module
+         will be called spi-intel-platform.
+
 config SPI_JCORE
        tristate "J-Core SPI Master"
        depends on OF && (SUPERH || COMPILE_TEST)
index dd7393a6046fa434831ffd02d97c94bedf4ee873..36b2045f08d253b16fe2f9849cf0a06e10ba5a0e 100644 (file)
@@ -61,6 +61,9 @@ obj-$(CONFIG_SPI_HISI_SFC_V3XX)               += spi-hisi-sfc-v3xx.o
 obj-$(CONFIG_SPI_IMG_SPFI)             += spi-img-spfi.o
 obj-$(CONFIG_SPI_IMX)                  += spi-imx.o
 obj-$(CONFIG_SPI_INGENIC)              += spi-ingenic.o
+obj-$(CONFIG_SPI_INTEL)                        += spi-intel.o
+obj-$(CONFIG_SPI_INTEL_PCI)            += spi-intel-pci.o
+obj-$(CONFIG_SPI_INTEL_PLATFORM)       += spi-intel-platform.o
 obj-$(CONFIG_SPI_LANTIQ_SSC)           += spi-lantiq-ssc.o
 obj-$(CONFIG_SPI_JCORE)                        += spi-jcore.o
 obj-$(CONFIG_SPI_LM70_LLP)             += spi-lm70llp.o
diff --git a/drivers/spi/spi-intel-pci.c b/drivers/spi/spi-intel-pci.c
new file mode 100644 (file)
index 0000000..a9cb4d7
--- /dev/null
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel PCH/PCU SPI flash PCI driver.
+ *
+ * Copyright (C) 2016 - 2022, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "spi-intel.h"
+
+#define BCR            0xdc
+#define BCR_WPD                BIT(0)
+
+static bool intel_spi_pci_set_writeable(void __iomem *base, void *data)
+{
+       struct pci_dev *pdev = data;
+       u32 bcr;
+
+       /* Try to make the chip read/write */
+       pci_read_config_dword(pdev, BCR, &bcr);
+       if (!(bcr & BCR_WPD)) {
+               bcr |= BCR_WPD;
+               pci_write_config_dword(pdev, BCR, bcr);
+               pci_read_config_dword(pdev, BCR, &bcr);
+       }
+
+       return bcr & BCR_WPD;
+}
+
+static const struct intel_spi_boardinfo bxt_info = {
+       .type = INTEL_SPI_BXT,
+       .set_writeable = intel_spi_pci_set_writeable,
+};
+
+static const struct intel_spi_boardinfo cnl_info = {
+       .type = INTEL_SPI_CNL,
+       .set_writeable = intel_spi_pci_set_writeable,
+};
+
+static int intel_spi_pci_probe(struct pci_dev *pdev,
+                              const struct pci_device_id *id)
+{
+       struct intel_spi_boardinfo *info;
+       int ret;
+
+       ret = pcim_enable_device(pdev);
+       if (ret)
+               return ret;
+
+       info = devm_kmemdup(&pdev->dev, (void *)id->driver_data, sizeof(*info),
+                           GFP_KERNEL);
+       if (!info)
+               return -ENOMEM;
+
+       info->data = pdev;
+       return intel_spi_probe(&pdev->dev, &pdev->resource[0], info);
+}
+
+static const struct pci_device_id intel_spi_pci_ids[] = {
+       { PCI_VDEVICE(INTEL, 0x02a4), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x06a4), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x18e0), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x19e0), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x1bca), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x34a4), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x43a4), (unsigned long)&cnl_info },
+       { PCI_VDEVICE(INTEL, 0x4b24), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x4da4), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0x51a4), (unsigned long)&cnl_info },
+       { PCI_VDEVICE(INTEL, 0x54a4), (unsigned long)&cnl_info },
+       { PCI_VDEVICE(INTEL, 0x7aa4), (unsigned long)&cnl_info },
+       { PCI_VDEVICE(INTEL, 0xa0a4), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0xa1a4), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0xa224), (unsigned long)&bxt_info },
+       { PCI_VDEVICE(INTEL, 0xa324), (unsigned long)&cnl_info },
+       { PCI_VDEVICE(INTEL, 0xa3a4), (unsigned long)&bxt_info },
+       { },
+};
+MODULE_DEVICE_TABLE(pci, intel_spi_pci_ids);
+
+static struct pci_driver intel_spi_pci_driver = {
+       .name = "intel-spi",
+       .id_table = intel_spi_pci_ids,
+       .probe = intel_spi_pci_probe,
+};
+
+module_pci_driver(intel_spi_pci_driver);
+
+MODULE_DESCRIPTION("Intel PCH/PCU SPI flash PCI driver");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/spi/spi-intel-platform.c b/drivers/spi/spi-intel-platform.c
new file mode 100644 (file)
index 0000000..2ef09fa
--- /dev/null
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel PCH/PCU SPI flash platform driver.
+ *
+ * Copyright (C) 2016 - 2022, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "spi-intel.h"
+
+static int intel_spi_platform_probe(struct platform_device *pdev)
+{
+       struct intel_spi_boardinfo *info;
+       struct resource *mem;
+
+       info = dev_get_platdata(&pdev->dev);
+       if (!info)
+               return -EINVAL;
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       return intel_spi_probe(&pdev->dev, mem, info);
+}
+
+static struct platform_driver intel_spi_platform_driver = {
+       .probe = intel_spi_platform_probe,
+       .driver = {
+               .name = "intel-spi",
+       },
+};
+
+module_platform_driver(intel_spi_platform_driver);
+
+MODULE_DESCRIPTION("Intel PCH/PCU SPI flash platform driver");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:intel-spi");
diff --git a/drivers/spi/spi-intel.c b/drivers/spi/spi-intel.c
new file mode 100644 (file)
index 0000000..e937cfe
--- /dev/null
@@ -0,0 +1,1250 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel PCH/PCU SPI flash driver.
+ *
+ * Copyright (C) 2016 - 2022, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#include <linux/iopoll.h>
+#include <linux/module.h>
+
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nor.h>
+
+#include <linux/spi/flash.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi-mem.h>
+
+#include "spi-intel.h"
+
+/* Offsets are from @ispi->base */
+#define BFPREG                         0x00
+
+#define HSFSTS_CTL                     0x04
+#define HSFSTS_CTL_FSMIE               BIT(31)
+#define HSFSTS_CTL_FDBC_SHIFT          24
+#define HSFSTS_CTL_FDBC_MASK           (0x3f << HSFSTS_CTL_FDBC_SHIFT)
+
+#define HSFSTS_CTL_FCYCLE_SHIFT                17
+#define HSFSTS_CTL_FCYCLE_MASK         (0x0f << HSFSTS_CTL_FCYCLE_SHIFT)
+/* HW sequencer opcodes */
+#define HSFSTS_CTL_FCYCLE_READ         (0x00 << HSFSTS_CTL_FCYCLE_SHIFT)
+#define HSFSTS_CTL_FCYCLE_WRITE                (0x02 << HSFSTS_CTL_FCYCLE_SHIFT)
+#define HSFSTS_CTL_FCYCLE_ERASE                (0x03 << HSFSTS_CTL_FCYCLE_SHIFT)
+#define HSFSTS_CTL_FCYCLE_ERASE_64K    (0x04 << HSFSTS_CTL_FCYCLE_SHIFT)
+#define HSFSTS_CTL_FCYCLE_RDID         (0x06 << HSFSTS_CTL_FCYCLE_SHIFT)
+#define HSFSTS_CTL_FCYCLE_WRSR         (0x07 << HSFSTS_CTL_FCYCLE_SHIFT)
+#define HSFSTS_CTL_FCYCLE_RDSR         (0x08 << HSFSTS_CTL_FCYCLE_SHIFT)
+
+#define HSFSTS_CTL_FGO                 BIT(16)
+#define HSFSTS_CTL_FLOCKDN             BIT(15)
+#define HSFSTS_CTL_FDV                 BIT(14)
+#define HSFSTS_CTL_SCIP                        BIT(5)
+#define HSFSTS_CTL_AEL                 BIT(2)
+#define HSFSTS_CTL_FCERR               BIT(1)
+#define HSFSTS_CTL_FDONE               BIT(0)
+
+#define FADDR                          0x08
+#define DLOCK                          0x0c
+#define FDATA(n)                       (0x10 + ((n) * 4))
+
+#define FRACC                          0x50
+
+#define FREG(n)                                (0x54 + ((n) * 4))
+#define FREG_BASE_MASK                 0x3fff
+#define FREG_LIMIT_SHIFT               16
+#define FREG_LIMIT_MASK                        (0x03fff << FREG_LIMIT_SHIFT)
+
+/* Offset is from @ispi->pregs */
+#define PR(n)                          ((n) * 4)
+#define PR_WPE                         BIT(31)
+#define PR_LIMIT_SHIFT                 16
+#define PR_LIMIT_MASK                  (0x3fff << PR_LIMIT_SHIFT)
+#define PR_RPE                         BIT(15)
+#define PR_BASE_MASK                   0x3fff
+
+/* Offsets are from @ispi->sregs */
+#define SSFSTS_CTL                     0x00
+#define SSFSTS_CTL_FSMIE               BIT(23)
+#define SSFSTS_CTL_DS                  BIT(22)
+#define SSFSTS_CTL_DBC_SHIFT           16
+#define SSFSTS_CTL_SPOP                        BIT(11)
+#define SSFSTS_CTL_ACS                 BIT(10)
+#define SSFSTS_CTL_SCGO                        BIT(9)
+#define SSFSTS_CTL_COP_SHIFT           12
+#define SSFSTS_CTL_FRS                 BIT(7)
+#define SSFSTS_CTL_DOFRS               BIT(6)
+#define SSFSTS_CTL_AEL                 BIT(4)
+#define SSFSTS_CTL_FCERR               BIT(3)
+#define SSFSTS_CTL_FDONE               BIT(2)
+#define SSFSTS_CTL_SCIP                        BIT(0)
+
+#define PREOP_OPTYPE                   0x04
+#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
+#define BYT_FREG_NUM                   5
+#define BYT_PR_NUM                     5
+
+#define LPT_PR                         0x74
+#define LPT_SSFSTS_CTL                 0x90
+#define LPT_FREG_NUM                   5
+#define LPT_PR_NUM                     5
+
+#define BXT_PR                         0x84
+#define BXT_SSFSTS_CTL                 0xa0
+#define BXT_FREG_NUM                   12
+#define BXT_PR_NUM                     6
+
+#define CNL_PR                         0x84
+#define CNL_FREG_NUM                   6
+#define CNL_PR_NUM                     5
+
+#define LVSCC                          0xc4
+#define UVSCC                          0xc8
+#define ERASE_OPCODE_SHIFT             8
+#define ERASE_OPCODE_MASK              (0xff << ERASE_OPCODE_SHIFT)
+#define ERASE_64K_OPCODE_SHIFT         16
+#define ERASE_64K_OPCODE_MASK          (0xff << ERASE_OPCODE_SHIFT)
+
+#define INTEL_SPI_TIMEOUT              5000 /* ms */
+#define INTEL_SPI_FIFO_SZ              64
+
+/**
+ * struct intel_spi - Driver private data
+ * @dev: Device pointer
+ * @info: Pointer to board specific info
+ * @base: Beginning of MMIO space
+ * @pregs: Start of protection registers
+ * @sregs: Start of software sequencer registers
+ * @master: Pointer to the SPI controller structure
+ * @nregions: Maximum number of regions
+ * @pr_num: Maximum number of protected range registers
+ * @locked: Is SPI setting locked
+ * @swseq_reg: Use SW sequencer in register reads/writes
+ * @swseq_erase: Use SW sequencer in erase operation
+ * @atomic_preopcode: Holds preopcode when atomic sequence is requested
+ * @opcodes: Opcodes which are supported. This are programmed by BIOS
+ *           before it locks down the controller.
+ * @mem_ops: Pointer to SPI MEM ops supported by the controller
+ */
+struct intel_spi {
+       struct device *dev;
+       const struct intel_spi_boardinfo *info;
+       void __iomem *base;
+       void __iomem *pregs;
+       void __iomem *sregs;
+       struct spi_controller *master;
+       size_t nregions;
+       size_t pr_num;
+       bool locked;
+       bool swseq_reg;
+       bool swseq_erase;
+       u8 atomic_preopcode;
+       u8 opcodes[8];
+       const struct intel_spi_mem_op *mem_ops;
+};
+
+struct intel_spi_mem_op {
+       struct spi_mem_op mem_op;
+       u32 replacement_op;
+       int (*exec_op)(struct intel_spi *ispi,
+                      const struct intel_spi_mem_op *iop,
+                      const struct spi_mem_op *op);
+};
+
+static bool writeable;
+module_param(writeable, bool, 0);
+MODULE_PARM_DESC(writeable, "Enable write access to SPI flash chip (default=0)");
+
+static void intel_spi_dump_regs(struct intel_spi *ispi)
+{
+       u32 value;
+       int i;
+
+       dev_dbg(ispi->dev, "BFPREG=0x%08x\n", readl(ispi->base + BFPREG));
+
+       value = readl(ispi->base + HSFSTS_CTL);
+       dev_dbg(ispi->dev, "HSFSTS_CTL=0x%08x\n", value);
+       if (value & HSFSTS_CTL_FLOCKDN)
+               dev_dbg(ispi->dev, "-> Locked\n");
+
+       dev_dbg(ispi->dev, "FADDR=0x%08x\n", readl(ispi->base + FADDR));
+       dev_dbg(ispi->dev, "DLOCK=0x%08x\n", readl(ispi->base + DLOCK));
+
+       for (i = 0; i < 16; i++)
+               dev_dbg(ispi->dev, "FDATA(%d)=0x%08x\n",
+                       i, readl(ispi->base + FDATA(i)));
+
+       dev_dbg(ispi->dev, "FRACC=0x%08x\n", readl(ispi->base + FRACC));
+
+       for (i = 0; i < ispi->nregions; i++)
+               dev_dbg(ispi->dev, "FREG(%d)=0x%08x\n", i,
+                       readl(ispi->base + FREG(i)));
+       for (i = 0; i < ispi->pr_num; i++)
+               dev_dbg(ispi->dev, "PR(%d)=0x%08x\n", i,
+                       readl(ispi->pregs + PR(i)));
+
+       if (ispi->sregs) {
+               value = readl(ispi->sregs + SSFSTS_CTL);
+               dev_dbg(ispi->dev, "SSFSTS_CTL=0x%08x\n", value);
+               dev_dbg(ispi->dev, "PREOP_OPTYPE=0x%08x\n",
+                       readl(ispi->sregs + PREOP_OPTYPE));
+               dev_dbg(ispi->dev, "OPMENU0=0x%08x\n",
+                       readl(ispi->sregs + OPMENU0));
+               dev_dbg(ispi->dev, "OPMENU1=0x%08x\n",
+                       readl(ispi->sregs + OPMENU1));
+       }
+
+       dev_dbg(ispi->dev, "LVSCC=0x%08x\n", readl(ispi->base + LVSCC));
+       dev_dbg(ispi->dev, "UVSCC=0x%08x\n", readl(ispi->base + UVSCC));
+
+       dev_dbg(ispi->dev, "Protected regions:\n");
+       for (i = 0; i < ispi->pr_num; i++) {
+               u32 base, limit;
+
+               value = readl(ispi->pregs + PR(i));
+               if (!(value & (PR_WPE | PR_RPE)))
+                       continue;
+
+               limit = (value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT;
+               base = value & PR_BASE_MASK;
+
+               dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x [%c%c]\n",
+                       i, base << 12, (limit << 12) | 0xfff,
+                       value & PR_WPE ? 'W' : '.', value & PR_RPE ? 'R' : '.');
+       }
+
+       dev_dbg(ispi->dev, "Flash regions:\n");
+       for (i = 0; i < ispi->nregions; i++) {
+               u32 region, base, limit;
+
+               region = readl(ispi->base + FREG(i));
+               base = region & FREG_BASE_MASK;
+               limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT;
+
+               if (base >= limit || (i > 0 && limit == 0))
+                       dev_dbg(ispi->dev, " %02d disabled\n", i);
+               else
+                       dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x\n",
+                               i, base << 12, (limit << 12) | 0xfff);
+       }
+
+       dev_dbg(ispi->dev, "Using %cW sequencer for register access\n",
+               ispi->swseq_reg ? 'S' : 'H');
+       dev_dbg(ispi->dev, "Using %cW sequencer for erase operation\n",
+               ispi->swseq_erase ? 'S' : 'H');
+}
+
+/* Reads max INTEL_SPI_FIFO_SZ bytes from the device fifo */
+static int intel_spi_read_block(struct intel_spi *ispi, void *buf, size_t size)
+{
+       size_t bytes;
+       int i = 0;
+
+       if (size > INTEL_SPI_FIFO_SZ)
+               return -EINVAL;
+
+       while (size > 0) {
+               bytes = min_t(size_t, size, 4);
+               memcpy_fromio(buf, ispi->base + FDATA(i), bytes);
+               size -= bytes;
+               buf += bytes;
+               i++;
+       }
+
+       return 0;
+}
+
+/* Writes max INTEL_SPI_FIFO_SZ bytes to the device fifo */
+static int intel_spi_write_block(struct intel_spi *ispi, const void *buf,
+                                size_t size)
+{
+       size_t bytes;
+       int i = 0;
+
+       if (size > INTEL_SPI_FIFO_SZ)
+               return -EINVAL;
+
+       while (size > 0) {
+               bytes = min_t(size_t, size, 4);
+               memcpy_toio(ispi->base + FDATA(i), buf, bytes);
+               size -= bytes;
+               buf += bytes;
+               i++;
+       }
+
+       return 0;
+}
+
+static int intel_spi_wait_hw_busy(struct intel_spi *ispi)
+{
+       u32 val;
+
+       return readl_poll_timeout(ispi->base + HSFSTS_CTL, val,
+                                 !(val & HSFSTS_CTL_SCIP), 0,
+                                 INTEL_SPI_TIMEOUT * 1000);
+}
+
+static int intel_spi_wait_sw_busy(struct intel_spi *ispi)
+{
+       u32 val;
+
+       return readl_poll_timeout(ispi->sregs + SSFSTS_CTL, val,
+                                 !(val & SSFSTS_CTL_SCIP), 0,
+                                 INTEL_SPI_TIMEOUT * 1000);
+}
+
+static bool intel_spi_set_writeable(struct intel_spi *ispi)
+{
+       if (!ispi->info->set_writeable)
+               return false;
+
+       return ispi->info->set_writeable(ispi->base, ispi->info->data);
+}
+
+static int intel_spi_opcode_index(struct intel_spi *ispi, u8 opcode, int optype)
+{
+       int i;
+       int preop;
+
+       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, size_t len)
+{
+       u32 val, status;
+       int ret;
+
+       val = readl(ispi->base + HSFSTS_CTL);
+       val &= ~(HSFSTS_CTL_FCYCLE_MASK | HSFSTS_CTL_FDBC_MASK);
+
+       switch (opcode) {
+       case SPINOR_OP_RDID:
+               val |= HSFSTS_CTL_FCYCLE_RDID;
+               break;
+       case SPINOR_OP_WRSR:
+               val |= HSFSTS_CTL_FCYCLE_WRSR;
+               break;
+       case SPINOR_OP_RDSR:
+               val |= HSFSTS_CTL_FCYCLE_RDSR;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (len > INTEL_SPI_FIFO_SZ)
+               return -EINVAL;
+
+       val |= (len - 1) << HSFSTS_CTL_FDBC_SHIFT;
+       val |= HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE;
+       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)
+               return -EIO;
+       else if (status & HSFSTS_CTL_AEL)
+               return -EACCES;
+
+       return 0;
+}
+
+static int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, size_t len,
+                             int optype)
+{
+       u32 val = 0, status;
+       u8 atomic_preopcode;
+       int ret;
+
+       ret = intel_spi_opcode_index(ispi, opcode, optype);
+       if (ret < 0)
+               return ret;
+
+       if (len > INTEL_SPI_FIFO_SZ)
+               return -EINVAL;
+
+       /*
+        * Always clear it after each SW sequencer operation regardless
+        * of whether it is successful or not.
+        */
+       atomic_preopcode = ispi->atomic_preopcode;
+       ispi->atomic_preopcode = 0;
+
+       /* Only mark 'Data Cycle' bit when there is data to be transferred */
+       if (len > 0)
+               val = ((len - 1) << SSFSTS_CTL_DBC_SHIFT) | SSFSTS_CTL_DS;
+       val |= ret << SSFSTS_CTL_COP_SHIFT;
+       val |= SSFSTS_CTL_FCERR | SSFSTS_CTL_FDONE;
+       val |= SSFSTS_CTL_SCGO;
+       if (atomic_preopcode) {
+               u16 preop;
+
+               switch (optype) {
+               case OPTYPE_WRITE_NO_ADDR:
+               case OPTYPE_WRITE_WITH_ADDR:
+                       /* Pick matching preopcode for the atomic sequence */
+                       preop = readw(ispi->sregs + PREOP_OPTYPE);
+                       if ((preop & 0xff) == atomic_preopcode)
+                               ; /* Do nothing */
+                       else if ((preop >> 8) == atomic_preopcode)
+                               val |= SSFSTS_CTL_SPOP;
+                       else
+                               return -EINVAL;
+
+                       /* Enable atomic sequence */
+                       val |= SSFSTS_CTL_ACS;
+                       break;
+
+               default:
+                       return -EINVAL;
+               }
+       }
+       writel(val, ispi->sregs + SSFSTS_CTL);
+
+       ret = intel_spi_wait_sw_busy(ispi);
+       if (ret)
+               return ret;
+
+       status = readl(ispi->sregs + SSFSTS_CTL);
+       if (status & SSFSTS_CTL_FCERR)
+               return -EIO;
+       else if (status & SSFSTS_CTL_AEL)
+               return -EACCES;
+
+       return 0;
+}
+
+static int intel_spi_read_reg(struct intel_spi *ispi,
+                             const struct intel_spi_mem_op *iop,
+                             const struct spi_mem_op *op)
+{
+       size_t nbytes = op->data.nbytes;
+       u8 opcode = op->cmd.opcode;
+       int ret;
+
+       /* Address of the first chip */
+       writel(0, ispi->base + FADDR);
+
+       if (ispi->swseq_reg)
+               ret = intel_spi_sw_cycle(ispi, opcode, nbytes,
+                                        OPTYPE_READ_NO_ADDR);
+       else
+               ret = intel_spi_hw_cycle(ispi, opcode, nbytes);
+
+       if (ret)
+               return ret;
+
+       return intel_spi_read_block(ispi, op->data.buf.in, nbytes);
+}
+
+static int intel_spi_write_reg(struct intel_spi *ispi,
+                              const struct intel_spi_mem_op *iop,
+                              const struct spi_mem_op *op)
+{
+       size_t nbytes = op->data.nbytes;
+       u8 opcode = op->cmd.opcode;
+       int ret;
+
+       /*
+        * This is handled with atomic operation and preop code in Intel
+        * controller so we only verify that it is available. If the
+        * controller is not locked, program the opcode to the PREOP
+        * register for later use.
+        *
+        * When hardware sequencer is used there is no need to program
+        * any opcodes (it handles them automatically as part of a command).
+        */
+       if (opcode == SPINOR_OP_WREN) {
+               u16 preop;
+
+               if (!ispi->swseq_reg)
+                       return 0;
+
+               preop = readw(ispi->sregs + PREOP_OPTYPE);
+               if ((preop & 0xff) != opcode && (preop >> 8) != opcode) {
+                       if (ispi->locked)
+                               return -EINVAL;
+                       writel(opcode, ispi->sregs + PREOP_OPTYPE);
+               }
+
+               /*
+                * This enables atomic sequence on next SW sycle. Will
+                * be cleared after next operation.
+                */
+               ispi->atomic_preopcode = opcode;
+               return 0;
+       }
+
+       /*
+        * We hope that HW sequencer will do the right thing automatically and
+        * with the SW sequencer we cannot use preopcode anyway, so just ignore
+        * the Write Disable operation and pretend it was completed
+        * successfully.
+        */
+       if (opcode == SPINOR_OP_WRDI)
+               return 0;
+
+       writel(0, ispi->base + FADDR);
+
+       /* Write the value beforehand */
+       ret = intel_spi_write_block(ispi, op->data.buf.out, nbytes);
+       if (ret)
+               return ret;
+
+       if (ispi->swseq_reg)
+               return intel_spi_sw_cycle(ispi, opcode, nbytes,
+                                         OPTYPE_WRITE_NO_ADDR);
+       return intel_spi_hw_cycle(ispi, opcode, nbytes);
+}
+
+static int intel_spi_read(struct intel_spi *ispi,
+                         const struct intel_spi_mem_op *iop,
+                         const struct spi_mem_op *op)
+{
+       void *read_buf = op->data.buf.in;
+       size_t block_size, nbytes = op->data.nbytes;
+       u32 addr = op->addr.val;
+       u32 val, status;
+       int ret;
+
+       /*
+        * Atomic sequence is not expected with HW sequencer reads. Make
+        * sure it is cleared regardless.
+        */
+       if (WARN_ON_ONCE(ispi->atomic_preopcode))
+               ispi->atomic_preopcode = 0;
+
+       while (nbytes > 0) {
+               block_size = min_t(size_t, nbytes, INTEL_SPI_FIFO_SZ);
+
+               /* Read cannot cross 4K boundary */
+               block_size = min_t(loff_t, addr + block_size,
+                                  round_up(addr + 1, SZ_4K)) - addr;
+
+               writel(addr, 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: %x: %#x\n", addr, status);
+                       return ret;
+               }
+
+               ret = intel_spi_read_block(ispi, read_buf, block_size);
+               if (ret)
+                       return ret;
+
+               nbytes -= block_size;
+               addr += block_size;
+               read_buf += block_size;
+       }
+
+       return 0;
+}
+
+static int intel_spi_write(struct intel_spi *ispi,
+                          const struct intel_spi_mem_op *iop,
+                          const struct spi_mem_op *op)
+{
+       size_t block_size, nbytes = op->data.nbytes;
+       const void *write_buf = op->data.buf.out;
+       u32 addr = op->addr.val;
+       u32 val, status;
+       int ret;
+
+       /* Not needed with HW sequencer write, make sure it is cleared */
+       ispi->atomic_preopcode = 0;
+
+       while (nbytes > 0) {
+               block_size = min_t(size_t, nbytes, INTEL_SPI_FIFO_SZ);
+
+               /* Write cannot cross 4K boundary */
+               block_size = min_t(loff_t, addr + block_size,
+                                  round_up(addr + 1, SZ_4K)) - addr;
+
+               writel(addr, 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_WRITE;
+
+               ret = intel_spi_write_block(ispi, write_buf, block_size);
+               if (ret) {
+                       dev_err(ispi->dev, "failed to write block\n");
+                       return ret;
+               }
+
+               /* Start the write now */
+               val |= HSFSTS_CTL_FGO;
+               writel(val, ispi->base + HSFSTS_CTL);
+
+               ret = intel_spi_wait_hw_busy(ispi);
+               if (ret) {
+                       dev_err(ispi->dev, "timeout\n");
+                       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, "write error: %x: %#x\n", addr, status);
+                       return ret;
+               }
+
+               nbytes -= block_size;
+               addr += block_size;
+               write_buf += block_size;
+       }
+
+       return 0;
+}
+
+static int intel_spi_erase(struct intel_spi *ispi,
+                          const struct intel_spi_mem_op *iop,
+                          const struct spi_mem_op *op)
+{
+       u8 opcode = op->cmd.opcode;
+       u32 addr = op->addr.val;
+       u32 val, status;
+       int ret;
+
+       writel(addr, ispi->base + FADDR);
+
+       if (ispi->swseq_erase)
+               return intel_spi_sw_cycle(ispi, opcode, 0,
+                                         OPTYPE_WRITE_WITH_ADDR);
+
+       /* Not needed with HW sequencer erase, make sure it is cleared */
+       ispi->atomic_preopcode = 0;
+
+       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 |= HSFSTS_CTL_FGO;
+       val |= iop->replacement_op;
+       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)
+               return -EIO;
+       if (status & HSFSTS_CTL_AEL)
+               return -EACCES;
+
+       return 0;
+}
+
+static bool intel_spi_cmp_mem_op(const struct intel_spi_mem_op *iop,
+                                const struct spi_mem_op *op)
+{
+       if (iop->mem_op.cmd.nbytes != op->cmd.nbytes ||
+           iop->mem_op.cmd.buswidth != op->cmd.buswidth ||
+           iop->mem_op.cmd.dtr != op->cmd.dtr ||
+           iop->mem_op.cmd.opcode != op->cmd.opcode)
+               return false;
+
+       if (iop->mem_op.addr.nbytes != op->addr.nbytes ||
+           iop->mem_op.addr.dtr != op->addr.dtr)
+               return false;
+
+       if (iop->mem_op.data.dir != op->data.dir ||
+           iop->mem_op.data.dtr != op->data.dtr)
+               return false;
+
+       if (iop->mem_op.data.dir != SPI_MEM_NO_DATA) {
+               if (iop->mem_op.data.buswidth != op->data.buswidth)
+                       return false;
+       }
+
+       return true;
+}
+
+static const struct intel_spi_mem_op *
+intel_spi_match_mem_op(struct intel_spi *ispi, const struct spi_mem_op *op)
+{
+       const struct intel_spi_mem_op *iop;
+
+       for (iop = ispi->mem_ops; iop->mem_op.cmd.opcode; iop++) {
+               if (intel_spi_cmp_mem_op(iop, op))
+                       break;
+       }
+
+       return iop->mem_op.cmd.opcode ? iop : NULL;
+}
+
+static bool intel_spi_supports_mem_op(struct spi_mem *mem,
+                                     const struct spi_mem_op *op)
+{
+       struct intel_spi *ispi = spi_master_get_devdata(mem->spi->master);
+       const struct intel_spi_mem_op *iop;
+
+       iop = intel_spi_match_mem_op(ispi, op);
+       if (!iop) {
+               dev_dbg(ispi->dev, "%#x not supported\n", op->cmd.opcode);
+               return false;
+       }
+
+       /*
+        * For software sequencer check that the opcode is actually
+        * present in the opmenu if it is locked.
+        */
+       if (ispi->swseq_reg && ispi->locked) {
+               int i;
+
+               /* Check if it is in the locked opcodes list */
+               for (i = 0; i < ARRAY_SIZE(ispi->opcodes); i++) {
+                       if (ispi->opcodes[i] == op->cmd.opcode)
+                               return true;
+               }
+
+               dev_dbg(ispi->dev, "%#x not supported\n", op->cmd.opcode);
+               return false;
+       }
+
+       return true;
+}
+
+static int intel_spi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
+{
+       struct intel_spi *ispi = spi_master_get_devdata(mem->spi->master);
+       const struct intel_spi_mem_op *iop;
+
+       iop = intel_spi_match_mem_op(ispi, op);
+       if (!iop)
+               return -EOPNOTSUPP;
+
+       return iop->exec_op(ispi, iop, op);
+}
+
+static const char *intel_spi_get_name(struct spi_mem *mem)
+{
+       const struct intel_spi *ispi = spi_master_get_devdata(mem->spi->master);
+
+       /*
+        * Return name of the flash controller device to be compatible
+        * with the MTD version.
+        */
+       return dev_name(ispi->dev);
+}
+
+static const struct spi_controller_mem_ops intel_spi_mem_ops = {
+       .supports_op = intel_spi_supports_mem_op,
+       .exec_op = intel_spi_exec_mem_op,
+       .get_name = intel_spi_get_name,
+};
+
+#define INTEL_SPI_OP_ADDR(__nbytes)                                    \
+       {                                                               \
+               .nbytes = __nbytes,                                     \
+       }
+
+#define INTEL_SPI_OP_NO_DATA                                           \
+       {                                                               \
+               .dir = SPI_MEM_NO_DATA,                                 \
+       }
+
+#define INTEL_SPI_OP_DATA_IN(__buswidth)                               \
+       {                                                               \
+               .dir = SPI_MEM_DATA_IN,                                 \
+               .buswidth = __buswidth,                                 \
+       }
+
+#define INTEL_SPI_OP_DATA_OUT(__buswidth)                              \
+       {                                                               \
+               .dir = SPI_MEM_DATA_OUT,                                \
+               .buswidth = __buswidth,                                 \
+       }
+
+#define INTEL_SPI_MEM_OP(__cmd, __addr, __data, __exec_op)             \
+       {                                                               \
+               .mem_op = {                                             \
+                       .cmd = __cmd,                                   \
+                       .addr = __addr,                                 \
+                       .data = __data,                                 \
+               },                                                      \
+               .exec_op = __exec_op,                                   \
+       }
+
+#define INTEL_SPI_MEM_OP_REPL(__cmd, __addr, __data, __exec_op, __repl)        \
+       {                                                               \
+               .mem_op = {                                             \
+                       .cmd = __cmd,                                   \
+                       .addr = __addr,                                 \
+                       .data = __data,                                 \
+               },                                                      \
+               .exec_op = __exec_op,                                   \
+               .replacement_op = __repl,                               \
+       }
+
+/*
+ * The controller handles pretty much everything internally based on the
+ * SFDP data but we want to make sure we only support the operations
+ * actually possible. Only check buswidth and transfer direction, the
+ * core validates data.
+ */
+#define INTEL_SPI_GENERIC_OPS                                          \
+       /* Status register operations */                                \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),             \
+                        SPI_MEM_OP_NO_ADDR,                            \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read_reg),                           \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 1),             \
+                        SPI_MEM_OP_NO_ADDR,                            \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read_reg),                           \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 1),             \
+                        SPI_MEM_OP_NO_ADDR,                            \
+                        INTEL_SPI_OP_DATA_OUT(1),                      \
+                        intel_spi_write_reg),                          \
+       /* Normal read */                                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ, 1),             \
+                        INTEL_SPI_OP_ADDR(3),                          \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ, 1),             \
+                        INTEL_SPI_OP_ADDR(3),                          \
+                        INTEL_SPI_OP_DATA_IN(2),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ, 1),             \
+                        INTEL_SPI_OP_ADDR(3),                          \
+                        INTEL_SPI_OP_DATA_IN(4),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ, 1),             \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ, 1),             \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(2),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ, 1),             \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(4),                       \
+                        intel_spi_read),                               \
+       /* Fast read */                                                 \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST, 1),        \
+                        INTEL_SPI_OP_ADDR(3),                          \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST, 1),        \
+                        INTEL_SPI_OP_ADDR(3),                          \
+                        INTEL_SPI_OP_DATA_IN(2),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST, 1),        \
+                        INTEL_SPI_OP_ADDR(3),                          \
+                        INTEL_SPI_OP_DATA_IN(4),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST, 1),        \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST, 1),        \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(2),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST, 1),        \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(4),                       \
+                        intel_spi_read),                               \
+       /* Read with 4-byte address opcode */                           \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_4B, 1),          \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_4B, 1),          \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(2),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_4B, 1),          \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(4),                       \
+                        intel_spi_read),                               \
+       /* Fast read with 4-byte address opcode */                      \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST_4B, 1),     \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(1),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST_4B, 1),     \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(2),                       \
+                        intel_spi_read),                               \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_READ_FAST_4B, 1),     \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_IN(4),                       \
+                        intel_spi_read),                               \
+       /* Write operations */                                          \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_PP, 1),               \
+                        INTEL_SPI_OP_ADDR(3),                          \
+                        INTEL_SPI_OP_DATA_OUT(1),                      \
+                        intel_spi_write),                              \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_PP, 1),               \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_OUT(1),                      \
+                        intel_spi_write),                              \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_PP_4B, 1),            \
+                        INTEL_SPI_OP_ADDR(4),                          \
+                        INTEL_SPI_OP_DATA_OUT(1),                      \
+                        intel_spi_write),                              \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WREN, 1),             \
+                        SPI_MEM_OP_NO_ADDR,                            \
+                        SPI_MEM_OP_NO_DATA,                            \
+                        intel_spi_write_reg),                          \
+       INTEL_SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRDI, 1),             \
+                        SPI_MEM_OP_NO_ADDR,                            \
+                        SPI_MEM_OP_NO_DATA,                            \
+                        intel_spi_write_reg),                          \
+       /* Erase operations */                                          \
+       INTEL_SPI_MEM_OP_REPL(SPI_MEM_OP_CMD(SPINOR_OP_BE_4K, 1),       \
+                             INTEL_SPI_OP_ADDR(3),                     \
+                             SPI_MEM_OP_NO_DATA,                       \
+                             intel_spi_erase,                          \
+                             HSFSTS_CTL_FCYCLE_ERASE),                 \
+       INTEL_SPI_MEM_OP_REPL(SPI_MEM_OP_CMD(SPINOR_OP_BE_4K, 1),       \
+                             INTEL_SPI_OP_ADDR(4),                     \
+                             SPI_MEM_OP_NO_DATA,                       \
+                             intel_spi_erase,                          \
+                             HSFSTS_CTL_FCYCLE_ERASE),                 \
+       INTEL_SPI_MEM_OP_REPL(SPI_MEM_OP_CMD(SPINOR_OP_BE_4K_4B, 1),    \
+                             INTEL_SPI_OP_ADDR(4),                     \
+                             SPI_MEM_OP_NO_DATA,                       \
+                             intel_spi_erase,                          \
+                             HSFSTS_CTL_FCYCLE_ERASE)                  \
+
+static const struct intel_spi_mem_op generic_mem_ops[] = {
+       INTEL_SPI_GENERIC_OPS,
+       { },
+};
+
+static const struct intel_spi_mem_op erase_64k_mem_ops[] = {
+       INTEL_SPI_GENERIC_OPS,
+       /* 64k sector erase operations */
+       INTEL_SPI_MEM_OP_REPL(SPI_MEM_OP_CMD(SPINOR_OP_SE, 1),
+                             INTEL_SPI_OP_ADDR(3),
+                             SPI_MEM_OP_NO_DATA,
+                             intel_spi_erase,
+                             HSFSTS_CTL_FCYCLE_ERASE_64K),
+       INTEL_SPI_MEM_OP_REPL(SPI_MEM_OP_CMD(SPINOR_OP_SE, 1),
+                             INTEL_SPI_OP_ADDR(4),
+                             SPI_MEM_OP_NO_DATA,
+                             intel_spi_erase,
+                             HSFSTS_CTL_FCYCLE_ERASE_64K),
+       INTEL_SPI_MEM_OP_REPL(SPI_MEM_OP_CMD(SPINOR_OP_SE_4B, 1),
+                             INTEL_SPI_OP_ADDR(4),
+                             SPI_MEM_OP_NO_DATA,
+                             intel_spi_erase,
+                             HSFSTS_CTL_FCYCLE_ERASE_64K),
+       { },
+};
+
+static int intel_spi_init(struct intel_spi *ispi)
+{
+       u32 opmenu0, opmenu1, lvscc, uvscc, val;
+       bool erase_64k = false;
+       int i;
+
+       switch (ispi->info->type) {
+       case INTEL_SPI_BYT:
+               ispi->sregs = ispi->base + BYT_SSFSTS_CTL;
+               ispi->pregs = ispi->base + BYT_PR;
+               ispi->nregions = BYT_FREG_NUM;
+               ispi->pr_num = BYT_PR_NUM;
+               ispi->swseq_reg = true;
+               break;
+
+       case INTEL_SPI_LPT:
+               ispi->sregs = ispi->base + LPT_SSFSTS_CTL;
+               ispi->pregs = ispi->base + LPT_PR;
+               ispi->nregions = LPT_FREG_NUM;
+               ispi->pr_num = LPT_PR_NUM;
+               ispi->swseq_reg = true;
+               break;
+
+       case INTEL_SPI_BXT:
+               ispi->sregs = ispi->base + BXT_SSFSTS_CTL;
+               ispi->pregs = ispi->base + BXT_PR;
+               ispi->nregions = BXT_FREG_NUM;
+               ispi->pr_num = BXT_PR_NUM;
+               erase_64k = true;
+               break;
+
+       case INTEL_SPI_CNL:
+               ispi->sregs = NULL;
+               ispi->pregs = ispi->base + CNL_PR;
+               ispi->nregions = CNL_FREG_NUM;
+               ispi->pr_num = CNL_PR_NUM;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       /* Try to disable write protection if user asked to do so */
+       if (writeable && !intel_spi_set_writeable(ispi)) {
+               dev_warn(ispi->dev, "can't disable chip write protection\n");
+               writeable = false;
+       }
+
+       /* Disable #SMI generation from HW sequencer */
+       val = readl(ispi->base + HSFSTS_CTL);
+       val &= ~HSFSTS_CTL_FSMIE;
+       writel(val, ispi->base + HSFSTS_CTL);
+
+       /*
+        * Determine whether erase operation should use HW or SW sequencer.
+        *
+        * The HW sequencer has a predefined list of opcodes, with only the
+        * erase opcode being programmable in LVSCC and UVSCC registers.
+        * If these registers don't contain a valid erase opcode, erase
+        * cannot be done using HW sequencer.
+        */
+       lvscc = readl(ispi->base + LVSCC);
+       uvscc = readl(ispi->base + UVSCC);
+       if (!(lvscc & ERASE_OPCODE_MASK) || !(uvscc & ERASE_OPCODE_MASK))
+               ispi->swseq_erase = true;
+       /* SPI controller on Intel BXT supports 64K erase opcode */
+       if (ispi->info->type == INTEL_SPI_BXT && !ispi->swseq_erase)
+               if (!(lvscc & ERASE_64K_OPCODE_MASK) ||
+                   !(uvscc & ERASE_64K_OPCODE_MASK))
+                       erase_64k = false;
+
+       if (!ispi->sregs && (ispi->swseq_reg || ispi->swseq_erase)) {
+               dev_err(ispi->dev, "software sequencer not supported, but required\n");
+               return -EINVAL;
+       }
+
+       /*
+        * Some controllers can only do basic operations using hardware
+        * sequencer. All other operations are supposed to be carried out
+        * using software sequencer.
+        */
+       if (ispi->swseq_reg) {
+               /* Disable #SMI generation from SW sequencer */
+               val = readl(ispi->sregs + SSFSTS_CTL);
+               val &= ~SSFSTS_CTL_FSMIE;
+               writel(val, ispi->sregs + SSFSTS_CTL);
+       }
+
+       /* Check controller's lock status */
+       val = readl(ispi->base + HSFSTS_CTL);
+       ispi->locked = !!(val & HSFSTS_CTL_FLOCKDN);
+
+       if (ispi->locked && ispi->sregs) {
+               /*
+                * 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);
+
+               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 (erase_64k) {
+               dev_dbg(ispi->dev, "Using erase_64k memory operations");
+               ispi->mem_ops = erase_64k_mem_ops;
+       } else {
+               dev_dbg(ispi->dev, "Using generic memory operations");
+               ispi->mem_ops = generic_mem_ops;
+       }
+
+       intel_spi_dump_regs(ispi);
+       return 0;
+}
+
+static bool intel_spi_is_protected(const struct intel_spi *ispi,
+                                  unsigned int base, unsigned int limit)
+{
+       int i;
+
+       for (i = 0; i < ispi->pr_num; i++) {
+               u32 pr_base, pr_limit, pr_value;
+
+               pr_value = readl(ispi->pregs + PR(i));
+               if (!(pr_value & (PR_WPE | PR_RPE)))
+                       continue;
+
+               pr_limit = (pr_value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT;
+               pr_base = pr_value & PR_BASE_MASK;
+
+               if (pr_base >= base && pr_limit <= limit)
+                       return true;
+       }
+
+       return false;
+}
+
+/*
+ * There will be a single partition holding all enabled flash regions. We
+ * call this "BIOS".
+ */
+static void intel_spi_fill_partition(struct intel_spi *ispi,
+                                    struct mtd_partition *part)
+{
+       u64 end;
+       int i;
+
+       memset(part, 0, sizeof(*part));
+
+       /* Start from the mandatory descriptor region */
+       part->size = 4096;
+       part->name = "BIOS";
+
+       /*
+        * Now try to find where this partition ends based on the flash
+        * region registers.
+        */
+       for (i = 1; i < ispi->nregions; i++) {
+               u32 region, base, limit;
+
+               region = readl(ispi->base + FREG(i));
+               base = region & FREG_BASE_MASK;
+               limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT;
+
+               if (base >= limit || limit == 0)
+                       continue;
+
+               /*
+                * If any of the regions have protection bits set, make the
+                * whole partition read-only to be on the safe side.
+                *
+                * Also if the user did not ask the chip to be writeable
+                * mask the bit too.
+                */
+               if (!writeable || intel_spi_is_protected(ispi, base, limit))
+                       part->mask_flags |= MTD_WRITEABLE;
+
+               end = (limit << 12) + 4096;
+               if (end > part->size)
+                       part->size = end;
+       }
+}
+
+static int intel_spi_populate_chip(struct intel_spi *ispi)
+{
+       struct flash_platform_data *pdata;
+       struct spi_board_info chip;
+
+       pdata = devm_kzalloc(ispi->dev, sizeof(*pdata), GFP_KERNEL);
+       if (!pdata)
+               return -ENOMEM;
+
+       pdata->nr_parts = 1;
+       pdata->parts = devm_kcalloc(ispi->dev, sizeof(*pdata->parts),
+                                   pdata->nr_parts, GFP_KERNEL);
+       if (!pdata->parts)
+               return -ENOMEM;
+
+       intel_spi_fill_partition(ispi, pdata->parts);
+
+       memset(&chip, 0, sizeof(chip));
+       snprintf(chip.modalias, 8, "spi-nor");
+       chip.platform_data = pdata;
+
+       return spi_new_device(ispi->master, &chip) ? 0 : -ENODEV;
+}
+
+/**
+ * intel_spi_probe() - Probe the Intel SPI flash controller
+ * @dev: Pointer to the parent device
+ * @mem: MMIO resource
+ * @info: Platform spefific information
+ *
+ * Probes Intel SPI flash controller and creates the flash chip device.
+ * Returns %0 on success and negative errno in case of failure.
+ */
+int intel_spi_probe(struct device *dev, struct resource *mem,
+                   const struct intel_spi_boardinfo *info)
+{
+       struct spi_controller *master;
+       struct intel_spi *ispi;
+       int ret;
+
+       master = devm_spi_alloc_master(dev, sizeof(*ispi));
+       if (!master)
+               return -ENOMEM;
+
+       master->mem_ops = &intel_spi_mem_ops;
+
+       ispi = spi_master_get_devdata(master);
+
+       ispi->base = devm_ioremap_resource(dev, mem);
+       if (IS_ERR(ispi->base))
+               return PTR_ERR(ispi->base);
+
+       ispi->dev = dev;
+       ispi->master = master;
+       ispi->info = info;
+
+       ret = intel_spi_init(ispi);
+       if (ret)
+               return ret;
+
+       ret = devm_spi_register_master(dev, master);
+       if (ret)
+               return ret;
+
+       return intel_spi_populate_chip(ispi);
+}
+EXPORT_SYMBOL_GPL(intel_spi_probe);
+
+MODULE_DESCRIPTION("Intel PCH/PCU SPI flash core driver");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/spi/spi-intel.h b/drivers/spi/spi-intel.h
new file mode 100644 (file)
index 0000000..a4f0327
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel PCH/PCU SPI flash driver.
+ *
+ * Copyright (C) 2016 - 2022, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#ifndef SPI_INTEL_H
+#define SPI_INTEL_H
+
+#include <linux/platform_data/x86/spi-intel.h>
+
+struct resource;
+
+int intel_spi_probe(struct device *dev, struct resource *mem,
+                   const struct intel_spi_boardinfo *info);
+
+#endif /* SPI_INTEL_H */
index 39967a5eca6d6f9754293c3afe2df8ea895fd3db..ea4a4b1b246a29e494036ad5a885ba2854e5a09a 100644 (file)
@@ -8,7 +8,7 @@
 #ifndef LPC_ICH_H
 #define LPC_ICH_H
 
-#include <linux/platform_data/x86/intel-spi.h>
+#include <linux/platform_data/x86/spi-intel.h>
 
 /* GPIO resources */
 #define ICH_RES_GPIO   0
diff --git a/include/linux/platform_data/x86/intel-spi.h b/include/linux/platform_data/x86/intel-spi.h
deleted file mode 100644 (file)
index 7dda3f6..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-/*
- * Intel PCH/PCU SPI flash driver.
- *
- * Copyright (C) 2016, Intel Corporation
- * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
- */
-
-#ifndef INTEL_SPI_PDATA_H
-#define INTEL_SPI_PDATA_H
-
-enum intel_spi_type {
-       INTEL_SPI_BYT = 1,
-       INTEL_SPI_LPT,
-       INTEL_SPI_BXT,
-       INTEL_SPI_CNL,
-};
-
-/**
- * struct intel_spi_boardinfo - Board specific data for Intel SPI driver
- * @type: Type which this controller is compatible with
- * @set_writeable: Try to make the chip writeable (optional)
- * @data: Data to be passed to @set_writeable can be %NULL
- */
-struct intel_spi_boardinfo {
-       enum intel_spi_type type;
-       bool (*set_writeable)(void __iomem *base, void *data);
-       void *data;
-};
-
-#endif /* INTEL_SPI_PDATA_H */
diff --git a/include/linux/platform_data/x86/spi-intel.h b/include/linux/platform_data/x86/spi-intel.h
new file mode 100644 (file)
index 0000000..a512ec3
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel PCH/PCU SPI flash driver.
+ *
+ * Copyright (C) 2016, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#ifndef SPI_INTEL_PDATA_H
+#define SPI_INTEL_PDATA_H
+
+enum intel_spi_type {
+       INTEL_SPI_BYT = 1,
+       INTEL_SPI_LPT,
+       INTEL_SPI_BXT,
+       INTEL_SPI_CNL,
+};
+
+/**
+ * struct intel_spi_boardinfo - Board specific data for Intel SPI driver
+ * @type: Type which this controller is compatible with
+ * @set_writeable: Try to make the chip writeable (optional)
+ * @data: Data to be passed to @set_writeable can be %NULL
+ */
+struct intel_spi_boardinfo {
+       enum intel_spi_type type;
+       bool (*set_writeable)(void __iomem *base, void *data);
+       void *data;
+};
+
+#endif /* SPI_INTEL_PDATA_H */