[U-Boot] [PATCH v2 12/38] x86: spi: Add a driver for the Intel Fast SPI interface

Bin Meng bmeng.cn at gmail.com
Wed Oct 9 13:10:10 UTC 2019


Hi Simon,

On Wed, Sep 25, 2019 at 10:12 PM Simon Glass <sjg at chromium.org> wrote:
>
> This provides access to SPI flash both through a read-only memory map and
> with operations to erase and write flash. It supports 4KB or 64KB erase
> sizes.
>

I read the SPI controller chapter in the datasheet of apollolake as
well as some latest x86 desktop processors and believe they are
compatible with the existing LPC SPI controller, the one used in ICH7
and ICH9. In fact, in Linux, these SPI controllers are supported by
the same intel-spi-pci.c driver (probe interface, PCI), and is
actually using the same intel-spi.c core driver.

So I think we should consider expanding the existing ICH SPI driver in
U-Boot to support the PCI variants too.

> This driver is used by Apollolake.
>
> Signed-off-by: Simon Glass <sjg at chromium.org>
> ---
>
> Changes in v2:
> - Add the missing header file
> - Add support for of-platdata for TPL
>
>  drivers/mtd/spi/Kconfig          |   9 +
>  drivers/mtd/spi/Makefile         |   1 +
>  drivers/mtd/spi/intel_fast_spi.c | 360 +++++++++++++++++++++++++++++++
>  3 files changed, 370 insertions(+)
>  create mode 100644 drivers/mtd/spi/intel_fast_spi.c
>
> diff --git a/drivers/mtd/spi/Kconfig b/drivers/mtd/spi/Kconfig
> index d3b007a731d..4a184bbc826 100644
> --- a/drivers/mtd/spi/Kconfig
> +++ b/drivers/mtd/spi/Kconfig
> @@ -78,6 +78,15 @@ config SF_DEFAULT_SPEED
>           speed and mode from platdata values computed from
>           available node.
>
> +config SPI_FLASH_INTEL_FAST
> +       bool "Intel Fast SPI interface"
> +       depends on DM_SPI_FLASH
> +       help
> +         Add support for Intel Fast SPI interface, as used on Apollolake.
> +         This current only supports reading from SPI, enough to load SPL or
> +         U-Boot proper from SPI flash. Future work may enable write and erase
> +         operations.
> +
>  if SPI_FLASH
>
>  config SPI_FLASH_SFDP_SUPPORT
> diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile
> index 20db1015d9e..11a8f55132a 100644
> --- a/drivers/mtd/spi/Makefile
> +++ b/drivers/mtd/spi/Makefile
> @@ -19,5 +19,6 @@ endif
>
>  obj-$(CONFIG_SPI_FLASH) += spi-nor.o
>  obj-$(CONFIG_SPI_FLASH_DATAFLASH) += sf_dataflash.o
> +obj-$(CONFIG_SPI_FLASH_INTEL_FAST) += intel_fast_spi.o
>  obj-$(CONFIG_SPI_FLASH_MTD) += sf_mtd.o
>  obj-$(CONFIG_SPI_FLASH_SANDBOX) += sandbox.o
> diff --git a/drivers/mtd/spi/intel_fast_spi.c b/drivers/mtd/spi/intel_fast_spi.c
> new file mode 100644
> index 00000000000..7074669c938
> --- /dev/null
> +++ b/drivers/mtd/spi/intel_fast_spi.c
> @@ -0,0 +1,360 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Intel 'Fast SPI' support
> + *
> + * Copyright 2019 Google LLC
> + */
> +
> +#include <common.h>
> +#include <dm.h>
> +#include <dt-structs.h>
> +#include <pci.h>
> +#include <spi_flash.h>
> +#include <spl.h>
> +#include <asm/io.h>
> +#include <asm/pci.h>
> +#include <asm/arch/fast_spi.h>
> +#include <asm/arch/iomap.h>
> +
> +struct fast_spi_platdata {
> +#if CONFIG_IS_ENABLED(OF_PLATDATA)
> +       struct dtd_intel_fast_spi dtplat;
> +#endif
> +       ulong mmio_base;
> +       pci_dev_t bdf;
> +};
> +
> +struct fast_spi_priv {
> +       struct fast_spi_regs *regs;
> +       uint page_size;
> +       uint flash_size;
> +       uint map_offset;
> +       ulong map_base;
> +       size_t map_size;
> +};
> +
> +enum {
> +       /* Erase size options */
> +       ERASE_SIZE_SM   = 4 << 10,
> +       ERASE_SIZE_LG   = 64 << 10,
> +};
> +
> +/*
> + * The hardware datasheet is not clear on what HORD values actually do. It
> + * seems that HORD_SFDP provides access to the first 8 bytes of the SFDP, which
> + * is the signature and revision fields. HORD_JEDEC provides access to the
> + * actual flash parameters, and is most likely what you want to use when
> + * probing the flash from software.
> + * It's okay to rely on SFDP, since the SPI flash controller requires an SFDP
> + * 1.5 or newer compliant FAST_SPI flash chip.
> + * NOTE: Due to the register layout of the hardware, all accesses will be
> + * aligned to a 4 byte boundary.
> + */
> +static u32 read_sfdp_param(struct fast_spi_priv *priv, uint sfdp_reg)
> +{
> +       u32 ptinx_index = sfdp_reg & SPIBAR_PTINX_IDX_MASK;
> +
> +       writel(ptinx_index | SPIBAR_PTINX_HORD_JEDEC, &priv->regs->ptinx);
> +
> +       return readl(&priv->regs->ptdata);
> +}
> +
> +/* Fill FDATAn FIFO in preparation for a write transaction */
> +static void fill_xfer_fifo(struct fast_spi_priv *priv, const void *data,
> +                          size_t len)
> +{
> +       memcpy(priv->regs->fdata, data, len);
> +}
> +
> +/* Drain FDATAn FIFO after a read transaction populates data */
> +static void drain_xfer_fifo(struct fast_spi_priv *priv, void *dest, size_t len)
> +{
> +       memcpy(dest, priv->regs->fdata, len);
> +}
> +
> +/* Fire up a transfer using the hardware sequencer */
> +static void start_hwseq_xfer(struct fast_spi_priv *priv, u32 hsfsts_cycle,
> +                            u32 offset, size_t len)
> +{
> +       /* Make sure all W1C status bits get cleared */
> +       u32 hsfsts = SPIBAR_HSFSTS_W1C_BITS;
> +
> +       /* Set up transaction parameters */
> +       hsfsts |= hsfsts_cycle & SPIBAR_HSFSTS_FCYCLE_MASK;
> +       hsfsts |= SPIBAR_HSFSTS_FDBC(len - 1);
> +
> +       writel(offset, &priv->regs->faddr);
> +       writel(hsfsts | SPIBAR_HSFSTS_FGO, &priv->regs->hsfsts_ctl);
> +}
> +
> +static int wait_for_hwseq_xfer(struct fast_spi_priv *priv, u32 offset)
> +{
> +       ulong start;
> +       u32 hsfsts;
> +
> +       start = get_timer(0);
> +       do {
> +               hsfsts = readl(&priv->regs->hsfsts_ctl);
> +
> +               if (hsfsts & SPIBAR_HSFSTS_FCERR) {
> +                       debug("SPI transaction error at offset %x HSFSTS = %08x\n",
> +                             offset, hsfsts);
> +                       return -EIO;
> +               }
> +
> +               if (hsfsts & SPIBAR_HSFSTS_FDONE)
> +                       return 0;
> +       } while ((int)get_timer(start) < SPIBAR_HWSEQ_XFER_TIMEOUT_MS);
> +
> +       debug("SPI transaction timeout at offset %x HSFSTS = %08x, timer %d\n",
> +             offset, hsfsts, (uint)get_timer(start));
> +       return -ETIMEDOUT;
> +}
> +
> +/* Execute FAST_SPI flash transfer. This is a blocking call */
> +static int exec_sync_hwseq_xfer(struct fast_spi_priv *priv,
> +                               u32 hsfsts_cycle, u32 offset,
> +                               size_t len)
> +{
> +       start_hwseq_xfer(priv, hsfsts_cycle, offset, len);
> +
> +       return wait_for_hwseq_xfer(priv, offset);
> +}
> +
> +/*
> + * Ensure read/write xfer len is not greater than SPIBAR_FDATA_FIFO_SIZE and
> + * that the operation does not cross page boundary.
> + */
> +static size_t get_xfer_len(const struct fast_spi_priv *priv, u32 offset,
> +                          size_t len)
> +{
> +       size_t xfer_len = min(len, (size_t)SPIBAR_FDATA_FIFO_SIZE);
> +       size_t bytes_left = ALIGN(offset, priv->page_size) - offset;
> +
> +       if (bytes_left)
> +               xfer_len = min(xfer_len, bytes_left);
> +
> +       return xfer_len;
> +}
> +
> +static int fast_spi_flash_erase(struct udevice *dev, u32 offset, size_t len)
> +{
> +       struct fast_spi_priv *priv = dev_get_priv(dev);
> +       int ret;
> +       size_t erase_size;
> +       u32 erase_cycle;
> +
> +       if (!IS_ALIGNED(offset, ERASE_SIZE_SM) ||
> +           !IS_ALIGNED(len, ERASE_SIZE_SM)) {
> +               debug("SPI erase region not sector-aligned\n");
> +               return -EINVAL;
> +       }
> +
> +       while (len) {
> +               if (IS_ALIGNED(offset, ERASE_SIZE_LG) && len >= ERASE_SIZE_LG) {
> +                       erase_size = ERASE_SIZE_LG;
> +                       erase_cycle = SPIBAR_HSFSTS_CYCLE_64K_ERASE;
> +               } else {
> +                       erase_size = ERASE_SIZE_SM;
> +                       erase_cycle = SPIBAR_HSFSTS_CYCLE_4K_ERASE;
> +               }
> +               debug("Erasing flash addr %x + %x\n", offset, (uint)erase_size);
> +
> +               ret = exec_sync_hwseq_xfer(priv, erase_cycle, offset, 0);
> +               if (ret)
> +                       return ret;
> +
> +               offset += erase_size;
> +               len -= erase_size;
> +       }
> +
> +       return 0;
> +}
> +
> +static int fast_spi_read(struct udevice *dev, u32 offset, size_t len, void *buf)
> +{
> +       struct fast_spi_priv *priv = dev_get_priv(dev);
> +
> +       debug("%s: read at offset %x\n", __func__, offset);
> +       while (len) {
> +               size_t xfer_len = get_xfer_len(priv, offset, len);
> +               int ret;
> +
> +               ret = exec_sync_hwseq_xfer(priv, SPIBAR_HSFSTS_CYCLE_READ,
> +                                          offset, xfer_len);
> +               if (ret)
> +                       return ret;
> +
> +               drain_xfer_fifo(priv, buf, xfer_len);
> +
> +               offset += xfer_len;
> +               buf += xfer_len;
> +               len -= xfer_len;
> +       }
> +
> +       return 0;
> +}
> +
> +static int fast_spi_flash_write(struct udevice *dev, u32 addr, size_t len,
> +                               const void *buf)
> +{
> +       struct fast_spi_priv *priv = dev_get_priv(dev);
> +       const u8 *data = buf;
> +       size_t xfer_len;
> +       int ret;
> +
> +       while (len) {
> +               xfer_len = get_xfer_len(priv, addr, len);
> +               fill_xfer_fifo(priv, data, xfer_len);
> +
> +               ret = exec_sync_hwseq_xfer(priv, SPIBAR_HSFSTS_CYCLE_WRITE,
> +                                          addr, xfer_len);
> +               if (ret)
> +                       return ret;
> +
> +               addr += xfer_len;
> +               data += xfer_len;
> +               len -= xfer_len;
> +       }
> +
> +       return 0;
> +}
> +
> +static int fast_spi_get_mmap(struct udevice *dev, ulong *map_basep,
> +                            size_t *map_sizep, u32 *offsetp)
> +{
> +       struct fast_spi_priv *priv = dev_get_priv(dev);
> +
> +       if (priv) {
> +               *map_basep = priv->map_base;
> +               *map_sizep = priv->map_size;
> +               *offsetp = priv->map_offset;
> +       } else {
> +               return fast_spi_get_bios_mmap(map_basep, map_sizep, offsetp);
> +       }
> +
> +       return 0;
> +}
> +
> +static void fast_spi_early_init(struct udevice *dev)
> +{
> +       struct fast_spi_platdata *plat = dev_get_platdata(dev);
> +       pci_dev_t pdev = plat->bdf;
> +
> +       /* Clear BIT 1-2 SPI Command Register */
> +       //remove
> +       pci_x86_clrset_config(pdev, PCI_COMMAND, PCI_COMMAND_MASTER |
> +                             PCI_COMMAND_MEMORY, 0, PCI_SIZE_8);
> +
> +       /* Program Temporary BAR for SPI */
> +       pci_x86_write_config(pdev, PCI_BASE_ADDRESS_0,
> +                            plat->mmio_base | PCI_BASE_ADDRESS_SPACE_MEMORY,
> +                            PCI_SIZE_32);
> +
> +       /* Enable Bus Master and MMIO Space */
> +       pci_x86_clrset_config(pdev, PCI_COMMAND, 0, PCI_COMMAND_MASTER |
> +                             PCI_COMMAND_MEMORY, PCI_SIZE_8);
> +
> +       /*
> +        * Disable the BIOS write protect so write commands are allowed.
> +        * Enable Prefetching and caching.
> +        */
> +       pci_x86_clrset_config(pdev, SPIBAR_BIOS_CONTROL,
> +                             SPIBAR_BIOS_CONTROL_EISS |
> +                             SPIBAR_BIOS_CONTROL_CACHE_DISABLE,
> +                             SPIBAR_BIOS_CONTROL_WPD |
> +                             SPIBAR_BIOS_CONTROL_PREFETCH_ENABLE, PCI_SIZE_8);

I wonder why are these needed, IIUC we rely on FSP2 to initialize the
SoC and I believe for SPI these are some basic settings and registers
should have already be programmed to sane values, no?

> +}
> +
> +static int fast_spi_ofdata_to_platdata(struct udevice *dev)
> +{
> +       struct fast_spi_platdata *plat = dev_get_platdata(dev);
> +
> +#if !CONFIG_IS_ENABLED(OF_PLATDATA)
> +       int ret;
> +
> +       if (spl_phase() == PHASE_TPL) {
> +               u32 base[2];
> +
> +               /* TPL sets up the initial BAR */
> +               ret = dev_read_u32_array(dev, "early-regs", base,
> +                                        ARRAY_SIZE(base));
> +               if (ret)
> +                       return log_msg_ret("Missing/short early-regs", ret);
> +               plat->mmio_base = base[0];
> +               plat->bdf = pci_x86_get_devfn(dev);
> +               if (plat->bdf < 0)
> +                       return log_msg_ret("Cannot get p2sb PCI address",
> +                                          plat->bdf);
> +       } else {
> +               plat->mmio_base = dev_read_addr_pci(dev);
> +               /* Don't set BDF since it should not be used */
> +               if (plat->mmio_base == FDT_ADDR_T_NONE)
> +                       return -EINVAL;
> +       }
> +#else
> +       plat->mmio_base = plat->dtplat.early_regs[0];
> +       plat->bdf = pci_x86_ofplat_get_devfn(plat->dtplat.reg[0]);
> +#endif
> +
> +       return 0;
> +}
> +
> +static int fast_spi_probe(struct udevice *dev)
> +{
> +       struct fast_spi_platdata *plat = dev_get_platdata(dev);
> +       struct spi_flash *flash = dev_get_uclass_priv(dev);
> +       struct fast_spi_priv *priv = dev_get_priv(dev);
> +       u32 flash_bits;
> +       ulong base;
> +
> +       if (spl_phase() == PHASE_TPL)
> +               fast_spi_early_init(dev);
> +
> +       priv->regs = (struct fast_spi_regs *)plat->mmio_base;
> +
> +       /*
> +        * bytes = (bits + 1) / 8;
> +        * But we need to do the addition in a way which doesn't overflow for
> +        * 4 Gb devices (flash_bits == 0xffffffff).
> +        */
> +       flash_bits = read_sfdp_param(priv, 0x04);
> +       flash->size = (flash_bits >> 3) + 1;
> +
> +       /* Can erase both 4 KiB and 64 KiB chunks. Declare the smaller size */
> +       flash->sector_size = 4 << 10;
> +       flash->page_size = 256;
> +
> +       base = fast_spi_get_bios_region(priv->regs, &priv->map_size);
> +       priv->map_base = (u32)-priv->map_size - base;
> +       priv->map_offset = base;
> +
> +       debug("FAST SPI at %lx, size %x with mapping %x, size %x\n",
> +             plat->mmio_base, flash->size, (uint)priv->map_base,
> +             priv->map_size);
> +
> +       return 0;
> +}
> +
> +static const struct dm_spi_flash_ops fast_spi_ops = {
> +       .read = fast_spi_read,
> +       .write = fast_spi_flash_write,
> +       .erase = fast_spi_flash_erase,
> +       .get_mmap = fast_spi_get_mmap,
> +};
> +
> +static const struct udevice_id fast_spi_ids[] = {
> +       { .compatible = "intel,fast-spi" },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(intel_fast_spi) = {
> +       .name           = "intel_fast_spi",
> +       .id             = UCLASS_SPI_FLASH,
> +       .of_match       = fast_spi_ids,
> +       .ofdata_to_platdata = fast_spi_ofdata_to_platdata,
> +       .probe          = fast_spi_probe,
> +       .platdata_auto_alloc_size = sizeof(struct fast_spi_platdata),
> +       .priv_auto_alloc_size = sizeof(struct fast_spi_priv),
> +       .ops            = &fast_spi_ops,
> +};
> --

Regards,
Bin


More information about the U-Boot mailing list