
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