Message ID | 20191021033913.220758-79-sjg@chromium.org |
---|---|
State | Superseded |
Delegated to: | Bin Meng |
Headers | show |
Series | x86: Add initial support for apollolake | expand |
Hi Simon, On Mon, Oct 21, 2019 at 11:40 AM Simon Glass <sjg@chromium.org> wrote: > > Apollolake (APL) only supports hardware sequencing. Add support for this > into the SPI driver, as an option. > > Signed-off-by: Simon Glass <sjg@chromium.org> > --- > > Changes in v3: None > Changes in v2: None > > drivers/spi/ich.c | 205 +++++++++++++++++++++++++++++++++++++++++++++- > drivers/spi/ich.h | 39 +++++++++ > 2 files changed, 241 insertions(+), 3 deletions(-) > > diff --git a/drivers/spi/ich.c b/drivers/spi/ich.c > index ae3bff36bba..ae1dc64bde8 100644 > --- a/drivers/spi/ich.c > +++ b/drivers/spi/ich.c > @@ -17,7 +17,9 @@ > #include <pci.h> > #include <pci_ids.h> > #include <spi.h> > +#include <spi_flash.h> > #include <spi-mem.h> > +#include <asm/fast_spi.h> > #include <asm/io.h> > > #include "ich.h" > @@ -36,6 +38,7 @@ struct ich_spi_platdata { > bool lockdown; /* lock down controller settings? */ > ulong mmio_base; /* Base of MMIO registers */ > pci_dev_t bdf; /* PCI address used by of-platdata */ > + bool hwseq; /* Use hardware sequencing (not s/w) */ > }; > > static u8 ich_readb(struct ich_spi_priv *priv, int reg) > @@ -244,7 +247,8 @@ static void ich_spi_config_opcode(struct udevice *dev) > ich_writel(ctlr, SPI_OPMENU_UPPER, ctlr->opmenu + sizeof(u32)); > } > > -static int ich_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) > +static int ich_spi_exec_op_swseq(struct spi_slave *slave, > + const struct spi_mem_op *op) > { > struct udevice *bus = dev_get_parent(slave->dev); > struct ich_spi_platdata *plat = dev_get_platdata(bus); > @@ -415,6 +419,197 @@ static int ich_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) > return 0; > } > > +/* > + * Ensure read/write xfer len is not greater than SPIBAR_FDATA_FIFO_SIZE and > + * that the operation does not cross page boundary. > + */ > +static uint get_xfer_len(u32 offset, int len, int page_size) > +{ > + uint xfer_len = min(len, SPIBAR_FDATA_FIFO_SIZE); > + uint bytes_left = ALIGN(offset, page_size) - offset; > + > + if (bytes_left) > + xfer_len = min(xfer_len, bytes_left); > + > + return xfer_len; > +} > + > +/* Fill FDATAn FIFO in preparation for a write transaction */ > +static void fill_xfer_fifo(struct fast_spi_regs *regs, const void *data, > + uint len) > +{ > + memcpy(regs->fdata, data, len); > +} > + > +/* Drain FDATAn FIFO after a read transaction populates data */ > +static void drain_xfer_fifo(struct fast_spi_regs *regs, void *dest, uint len) > +{ > + memcpy(dest, regs->fdata, len); > +} > + > +/* Fire up a transfer using the hardware sequencer */ > +static void start_hwseq_xfer(struct fast_spi_regs *regs, uint hsfsts_cycle, > + uint offset, uint len) > +{ > + /* Make sure all W1C status bits get cleared */ > + u32 hsfsts; > + > + hsfsts = readl(®s->hsfsts_ctl); > + hsfsts &= ~(HSFSTS_FCYCLE_MASK | HSFSTS_FDBC_MASK); > + hsfsts |= HSFSTS_AEL | HSFSTS_FCERR | HSFSTS_FDONE; > + > + /* Set up transaction parameters */ > + hsfsts |= hsfsts_cycle << HSFSTS_FCYCLE_SHIFT; > + hsfsts |= ((len - 1) << HSFSTS_FDBC_SHIFT) & HSFSTS_FDBC_MASK; > + hsfsts |= HSFSTS_FGO; > + > + writel(offset, ®s->faddr); > + writel(hsfsts, ®s->hsfsts_ctl); > +} > + > +static int wait_for_hwseq_xfer(struct fast_spi_regs *regs, uint offset) > +{ > + ulong start; > + u32 hsfsts; > + > + start = get_timer(0); > + do { > + hsfsts = readl(®s->hsfsts_ctl); > + if (hsfsts & HSFSTS_FCERR) { > + debug("SPI transaction error at offset %x HSFSTS = %08x\n", > + offset, hsfsts); > + return -EIO; > + } > + if (hsfsts & HSFSTS_AEL) > + return -EPERM; > + > + if (hsfsts & HSFSTS_FDONE) > + return 0; > + } while (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; > +} > + > +/** > + * exec_sync_hwseq_xfer() - Execute FAST_SPI flash transfer Should we just mention this is flash transfer using hardware sequencer, instead of FAST_SPI? > + * > + * This waits until complete or timeout > + * > + * @regs: SPI registers > + * @hsfsts_cycle: Cycle type (enum hsfsts_cycle_t) > + * @offset: Offset to access > + * @len: Number of bytes to transfer (can be 0) > + * @return 9 if OK, -EIO on flash-cycle error (FCERR), -EPERM on access error 0 if OK > + * (AEL), -ETIMEDOUT on timeout > + */ > +static int exec_sync_hwseq_xfer(struct fast_spi_regs *regs, uint hsfsts_cycle, > + uint offset, uint len) > +{ > + start_hwseq_xfer(regs, hsfsts_cycle, offset, len); > + > + return wait_for_hwseq_xfer(regs, offset); > +} > + > +static int ich_spi_exec_op_hwseq(struct spi_slave *slave, > + const struct spi_mem_op *op) > +{ > + struct spi_flash *flash = dev_get_uclass_priv(slave->dev); > + struct udevice *bus = dev_get_parent(slave->dev); > + struct ich_spi_priv *priv = dev_get_priv(bus); > + struct fast_spi_regs *regs = priv->base; > + uint page_size; > + uint offset; > + int cycle; > + uint len; > + bool out; > + int ret; > + u8 *buf; > + > + offset = op->addr.val; > + len = op->data.nbytes; > + > + switch (op->cmd.opcode) { > + case SPINOR_OP_RDID: > + cycle = HSFSTS_CYCLE_RDID; > + break; > + case SPINOR_OP_READ_FAST: > + cycle = HSFSTS_CYCLE_READ; > + break; > + case SPINOR_OP_PP: > + cycle = HSFSTS_CYCLE_WRITE; > + break; > + case SPINOR_OP_WREN: > + /* Nothing needs to be done */ > + return 0; > + case SPINOR_OP_WRSR: > + cycle = HSFSTS_CYCLE_WR_STATUS; > + break; > + case SPINOR_OP_RDSR: > + cycle = HSFSTS_CYCLE_RD_STATUS; > + break; > + case SPINOR_OP_WRDI: > + return 0; /* ignore */ > + case SPINOR_OP_BE_4K: > + cycle = HSFSTS_CYCLE_4K_ERASE; > + while (len) { > + uint xfer_len = 0x1000; > + > + ret = exec_sync_hwseq_xfer(regs, cycle, offset, 0); > + if (ret) > + return ret; > + offset += xfer_len; > + len -= xfer_len; > + } > + return 0; > + default: > + debug("Unknown cycle %x\n", op->cmd.opcode); > + return -EINVAL; > + }; > + > + out = op->data.dir == SPI_MEM_DATA_OUT; > + buf = out ? (u8 *)op->data.buf.out : op->data.buf.in; > + page_size = flash->page_size ? : 256; > + > + while (len) { > + uint xfer_len = get_xfer_len(offset, len, page_size); > + > + if (out) > + fill_xfer_fifo(regs, buf, xfer_len); > + > + ret = exec_sync_hwseq_xfer(regs, cycle, offset, xfer_len); > + if (ret) > + return ret; > + > + if (!out) > + drain_xfer_fifo(regs, buf, xfer_len); > + > + offset += xfer_len; > + buf += xfer_len; > + len -= xfer_len; > + } > + > + return 0; > +} > + > +static int ich_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) > +{ > + struct udevice *bus = dev_get_parent(slave->dev); > + struct ich_spi_platdata *plat = dev_get_platdata(bus); > + int ret; > + > + bootstage_start(BOOTSTAGE_ID_ACCUM_SPI, "fast_spi"); > + if (plat->hwseq) > + ret = ich_spi_exec_op_hwseq(slave, op); > + else > + ret = ich_spi_exec_op_swseq(slave, op); > + bootstage_accum(BOOTSTAGE_ID_ACCUM_SPI); > + > + return ret; > +} > + > static int ich_spi_adjust_size(struct spi_slave *slave, struct spi_mem_op *op) > { > unsigned int page_offset; > @@ -585,9 +780,11 @@ static int ich_spi_child_pre_probe(struct udevice *dev) > > /* > * Yes this controller can only write a small number of bytes at > - * once! The limit is typically 64 bytes. > + * once! The limit is typically 64 bytes. For hardware sequencing a > + * a loop is used to get around this. > */ > - slave->max_write_size = priv->databytes; > + if (!plat->hwseq) > + slave->max_write_size = priv->databytes; > /* > * ICH 7 SPI controller only supports array read command > * and byte program command for SST flash > @@ -606,10 +803,12 @@ static int ich_spi_ofdata_to_platdata(struct udevice *dev) > plat->ich_version = dev_get_driver_data(dev); > plat->lockdown = dev_read_bool(dev, "intel,spi-lock-down"); > pch_get_spi_base(dev->parent, &plat->mmio_base); > + plat->hwseq = dev_read_u32_default(dev, "intel,hardware-seq", 0); > #else > plat->ich_version = ICHV_APL; > plat->mmio_base = plat->dtplat.early_regs[0]; > plat->bdf = pci_x86_ofplat_get_devfn(plat->dtplat.reg[0]); > + plat->hwseq = plat->dtplat.intel_hardware_seq; > #endif > debug("%s: mmio_base=%lx\n", __func__, plat->mmio_base); > > diff --git a/drivers/spi/ich.h b/drivers/spi/ich.h > index 623b2c547a6..c7cf37b9321 100644 > --- a/drivers/spi/ich.h > +++ b/drivers/spi/ich.h > @@ -163,6 +163,45 @@ struct spi_trans { > > #define ICH_BOUNDARY 0x1000 > > +#define HSFSTS_FDBC_SHIFT 24 > +#define HSFSTS_FDBC_MASK (0x3f << HSFSTS_FDBC_SHIFT) > +#define HSFSTS_WET BIT(21) > +#define HSFSTS_FCYCLE_SHIFT 17 > +#define HSFSTS_FCYCLE_MASK (0xf << HSFSTS_FCYCLE_SHIFT) > + > +/* Supported flash cycle types */ > +enum hsfsts_cycle_t { > + HSFSTS_CYCLE_READ = 0, > + HSFSTS_CYCLE_WRITE = 2, > + HSFSTS_CYCLE_4K_ERASE, > + HSFSTS_CYCLE_64K_ERASE, > + HSFSTS_CYCLE_RDSFDP, > + HSFSTS_CYCLE_RDID, > + HSFSTS_CYCLE_WR_STATUS, > + HSFSTS_CYCLE_RD_STATUS, > +}; > + > +#define HSFSTS_FGO BIT(16) > +#define HSFSTS_FLOCKDN BIT(15) > +#define HSFSTS_FDV BIT(14) > +#define HSFSTS_FDOPSS BIT(13) > +#define HSFSTS_WRSDIS BIT(11) > +#define HSFSTS_SAF_CE BIT(8) > +#define HSFSTS_SAF_ACTIVE BIT(7) > +#define HSFSTS_SAF_LE BIT(6) > +#define HSFSTS_SCIP BIT(5) > +#define HSFSTS_SAF_DLE BIT(4) > +#define HSFSTS_SAF_ERROR BIT(3) > +#define HSFSTS_AEL BIT(2) > +#define HSFSTS_FCERR BIT(1) > +#define HSFSTS_FDONE BIT(0) > +#define HSFSTS_W1C_BITS 0xff > + > +/* Maximum bytes of data that can fit in FDATAn (0x10) registers */ > +#define SPIBAR_FDATA_FIFO_SIZE 0x40 > + > +#define SPIBAR_HWSEQ_XFER_TIMEOUT_MS 5000 > + > enum ich_version { > ICHV_7, > ICHV_9, > -- Regards, Bin
diff --git a/drivers/spi/ich.c b/drivers/spi/ich.c index ae3bff36bba..ae1dc64bde8 100644 --- a/drivers/spi/ich.c +++ b/drivers/spi/ich.c @@ -17,7 +17,9 @@ #include <pci.h> #include <pci_ids.h> #include <spi.h> +#include <spi_flash.h> #include <spi-mem.h> +#include <asm/fast_spi.h> #include <asm/io.h> #include "ich.h" @@ -36,6 +38,7 @@ struct ich_spi_platdata { bool lockdown; /* lock down controller settings? */ ulong mmio_base; /* Base of MMIO registers */ pci_dev_t bdf; /* PCI address used by of-platdata */ + bool hwseq; /* Use hardware sequencing (not s/w) */ }; static u8 ich_readb(struct ich_spi_priv *priv, int reg) @@ -244,7 +247,8 @@ static void ich_spi_config_opcode(struct udevice *dev) ich_writel(ctlr, SPI_OPMENU_UPPER, ctlr->opmenu + sizeof(u32)); } -static int ich_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) +static int ich_spi_exec_op_swseq(struct spi_slave *slave, + const struct spi_mem_op *op) { struct udevice *bus = dev_get_parent(slave->dev); struct ich_spi_platdata *plat = dev_get_platdata(bus); @@ -415,6 +419,197 @@ static int ich_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) return 0; } +/* + * Ensure read/write xfer len is not greater than SPIBAR_FDATA_FIFO_SIZE and + * that the operation does not cross page boundary. + */ +static uint get_xfer_len(u32 offset, int len, int page_size) +{ + uint xfer_len = min(len, SPIBAR_FDATA_FIFO_SIZE); + uint bytes_left = ALIGN(offset, page_size) - offset; + + if (bytes_left) + xfer_len = min(xfer_len, bytes_left); + + return xfer_len; +} + +/* Fill FDATAn FIFO in preparation for a write transaction */ +static void fill_xfer_fifo(struct fast_spi_regs *regs, const void *data, + uint len) +{ + memcpy(regs->fdata, data, len); +} + +/* Drain FDATAn FIFO after a read transaction populates data */ +static void drain_xfer_fifo(struct fast_spi_regs *regs, void *dest, uint len) +{ + memcpy(dest, regs->fdata, len); +} + +/* Fire up a transfer using the hardware sequencer */ +static void start_hwseq_xfer(struct fast_spi_regs *regs, uint hsfsts_cycle, + uint offset, uint len) +{ + /* Make sure all W1C status bits get cleared */ + u32 hsfsts; + + hsfsts = readl(®s->hsfsts_ctl); + hsfsts &= ~(HSFSTS_FCYCLE_MASK | HSFSTS_FDBC_MASK); + hsfsts |= HSFSTS_AEL | HSFSTS_FCERR | HSFSTS_FDONE; + + /* Set up transaction parameters */ + hsfsts |= hsfsts_cycle << HSFSTS_FCYCLE_SHIFT; + hsfsts |= ((len - 1) << HSFSTS_FDBC_SHIFT) & HSFSTS_FDBC_MASK; + hsfsts |= HSFSTS_FGO; + + writel(offset, ®s->faddr); + writel(hsfsts, ®s->hsfsts_ctl); +} + +static int wait_for_hwseq_xfer(struct fast_spi_regs *regs, uint offset) +{ + ulong start; + u32 hsfsts; + + start = get_timer(0); + do { + hsfsts = readl(®s->hsfsts_ctl); + if (hsfsts & HSFSTS_FCERR) { + debug("SPI transaction error at offset %x HSFSTS = %08x\n", + offset, hsfsts); + return -EIO; + } + if (hsfsts & HSFSTS_AEL) + return -EPERM; + + if (hsfsts & HSFSTS_FDONE) + return 0; + } while (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; +} + +/** + * exec_sync_hwseq_xfer() - Execute FAST_SPI flash transfer + * + * This waits until complete or timeout + * + * @regs: SPI registers + * @hsfsts_cycle: Cycle type (enum hsfsts_cycle_t) + * @offset: Offset to access + * @len: Number of bytes to transfer (can be 0) + * @return 9 if OK, -EIO on flash-cycle error (FCERR), -EPERM on access error + * (AEL), -ETIMEDOUT on timeout + */ +static int exec_sync_hwseq_xfer(struct fast_spi_regs *regs, uint hsfsts_cycle, + uint offset, uint len) +{ + start_hwseq_xfer(regs, hsfsts_cycle, offset, len); + + return wait_for_hwseq_xfer(regs, offset); +} + +static int ich_spi_exec_op_hwseq(struct spi_slave *slave, + const struct spi_mem_op *op) +{ + struct spi_flash *flash = dev_get_uclass_priv(slave->dev); + struct udevice *bus = dev_get_parent(slave->dev); + struct ich_spi_priv *priv = dev_get_priv(bus); + struct fast_spi_regs *regs = priv->base; + uint page_size; + uint offset; + int cycle; + uint len; + bool out; + int ret; + u8 *buf; + + offset = op->addr.val; + len = op->data.nbytes; + + switch (op->cmd.opcode) { + case SPINOR_OP_RDID: + cycle = HSFSTS_CYCLE_RDID; + break; + case SPINOR_OP_READ_FAST: + cycle = HSFSTS_CYCLE_READ; + break; + case SPINOR_OP_PP: + cycle = HSFSTS_CYCLE_WRITE; + break; + case SPINOR_OP_WREN: + /* Nothing needs to be done */ + return 0; + case SPINOR_OP_WRSR: + cycle = HSFSTS_CYCLE_WR_STATUS; + break; + case SPINOR_OP_RDSR: + cycle = HSFSTS_CYCLE_RD_STATUS; + break; + case SPINOR_OP_WRDI: + return 0; /* ignore */ + case SPINOR_OP_BE_4K: + cycle = HSFSTS_CYCLE_4K_ERASE; + while (len) { + uint xfer_len = 0x1000; + + ret = exec_sync_hwseq_xfer(regs, cycle, offset, 0); + if (ret) + return ret; + offset += xfer_len; + len -= xfer_len; + } + return 0; + default: + debug("Unknown cycle %x\n", op->cmd.opcode); + return -EINVAL; + }; + + out = op->data.dir == SPI_MEM_DATA_OUT; + buf = out ? (u8 *)op->data.buf.out : op->data.buf.in; + page_size = flash->page_size ? : 256; + + while (len) { + uint xfer_len = get_xfer_len(offset, len, page_size); + + if (out) + fill_xfer_fifo(regs, buf, xfer_len); + + ret = exec_sync_hwseq_xfer(regs, cycle, offset, xfer_len); + if (ret) + return ret; + + if (!out) + drain_xfer_fifo(regs, buf, xfer_len); + + offset += xfer_len; + buf += xfer_len; + len -= xfer_len; + } + + return 0; +} + +static int ich_spi_exec_op(struct spi_slave *slave, const struct spi_mem_op *op) +{ + struct udevice *bus = dev_get_parent(slave->dev); + struct ich_spi_platdata *plat = dev_get_platdata(bus); + int ret; + + bootstage_start(BOOTSTAGE_ID_ACCUM_SPI, "fast_spi"); + if (plat->hwseq) + ret = ich_spi_exec_op_hwseq(slave, op); + else + ret = ich_spi_exec_op_swseq(slave, op); + bootstage_accum(BOOTSTAGE_ID_ACCUM_SPI); + + return ret; +} + static int ich_spi_adjust_size(struct spi_slave *slave, struct spi_mem_op *op) { unsigned int page_offset; @@ -585,9 +780,11 @@ static int ich_spi_child_pre_probe(struct udevice *dev) /* * Yes this controller can only write a small number of bytes at - * once! The limit is typically 64 bytes. + * once! The limit is typically 64 bytes. For hardware sequencing a + * a loop is used to get around this. */ - slave->max_write_size = priv->databytes; + if (!plat->hwseq) + slave->max_write_size = priv->databytes; /* * ICH 7 SPI controller only supports array read command * and byte program command for SST flash @@ -606,10 +803,12 @@ static int ich_spi_ofdata_to_platdata(struct udevice *dev) plat->ich_version = dev_get_driver_data(dev); plat->lockdown = dev_read_bool(dev, "intel,spi-lock-down"); pch_get_spi_base(dev->parent, &plat->mmio_base); + plat->hwseq = dev_read_u32_default(dev, "intel,hardware-seq", 0); #else plat->ich_version = ICHV_APL; plat->mmio_base = plat->dtplat.early_regs[0]; plat->bdf = pci_x86_ofplat_get_devfn(plat->dtplat.reg[0]); + plat->hwseq = plat->dtplat.intel_hardware_seq; #endif debug("%s: mmio_base=%lx\n", __func__, plat->mmio_base); diff --git a/drivers/spi/ich.h b/drivers/spi/ich.h index 623b2c547a6..c7cf37b9321 100644 --- a/drivers/spi/ich.h +++ b/drivers/spi/ich.h @@ -163,6 +163,45 @@ struct spi_trans { #define ICH_BOUNDARY 0x1000 +#define HSFSTS_FDBC_SHIFT 24 +#define HSFSTS_FDBC_MASK (0x3f << HSFSTS_FDBC_SHIFT) +#define HSFSTS_WET BIT(21) +#define HSFSTS_FCYCLE_SHIFT 17 +#define HSFSTS_FCYCLE_MASK (0xf << HSFSTS_FCYCLE_SHIFT) + +/* Supported flash cycle types */ +enum hsfsts_cycle_t { + HSFSTS_CYCLE_READ = 0, + HSFSTS_CYCLE_WRITE = 2, + HSFSTS_CYCLE_4K_ERASE, + HSFSTS_CYCLE_64K_ERASE, + HSFSTS_CYCLE_RDSFDP, + HSFSTS_CYCLE_RDID, + HSFSTS_CYCLE_WR_STATUS, + HSFSTS_CYCLE_RD_STATUS, +}; + +#define HSFSTS_FGO BIT(16) +#define HSFSTS_FLOCKDN BIT(15) +#define HSFSTS_FDV BIT(14) +#define HSFSTS_FDOPSS BIT(13) +#define HSFSTS_WRSDIS BIT(11) +#define HSFSTS_SAF_CE BIT(8) +#define HSFSTS_SAF_ACTIVE BIT(7) +#define HSFSTS_SAF_LE BIT(6) +#define HSFSTS_SCIP BIT(5) +#define HSFSTS_SAF_DLE BIT(4) +#define HSFSTS_SAF_ERROR BIT(3) +#define HSFSTS_AEL BIT(2) +#define HSFSTS_FCERR BIT(1) +#define HSFSTS_FDONE BIT(0) +#define HSFSTS_W1C_BITS 0xff + +/* Maximum bytes of data that can fit in FDATAn (0x10) registers */ +#define SPIBAR_FDATA_FIFO_SIZE 0x40 + +#define SPIBAR_HWSEQ_XFER_TIMEOUT_MS 5000 + enum ich_version { ICHV_7, ICHV_9,
Apollolake (APL) only supports hardware sequencing. Add support for this into the SPI driver, as an option. Signed-off-by: Simon Glass <sjg@chromium.org> --- Changes in v3: None Changes in v2: None drivers/spi/ich.c | 205 +++++++++++++++++++++++++++++++++++++++++++++- drivers/spi/ich.h | 39 +++++++++ 2 files changed, 241 insertions(+), 3 deletions(-)