aboutsummaryrefslogtreecommitdiff
path: root/hw/sfc-ctrl.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/sfc-ctrl.c')
-rw-r--r--hw/sfc-ctrl.c523
1 files changed, 523 insertions, 0 deletions
diff --git a/hw/sfc-ctrl.c b/hw/sfc-ctrl.c
new file mode 100644
index 0000000..de163c5
--- /dev/null
+++ b/hw/sfc-ctrl.c
@@ -0,0 +1,523 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <lpc.h>
+#include <sfc-ctrl.h>
+
+#include <libflash/libflash.h>
+#include <libflash/libflash-priv.h>
+
+/* Offset of SFC registers in FW space */
+#define SFC_CMDREG_OFFSET 0x00000c00
+/* Offset of SFC command buffer in FW space */
+#define SFC_CMDBUF_OFFSET 0x00000d00
+/* Offset of flash MMIO mapping in FW space */
+#define SFC_MMIO_OFFSET 0x0c000000
+
+
+/*
+ * Register definitions
+ */
+#define SFC_REG_CONF 0x10 /* CONF: Direct Access Configuration */
+#define SFC_REG_CONF_FRZE (1 << 3)
+#define SFC_REG_CONF_ECCEN (1 << 2)
+#define SFC_REG_CONF_DRCD (1 << 1)
+#define SFC_REG_CONF_FLRLD (1 << 0)
+
+#define SFC_REG_STATUS 0x0C /* STATUS : Status Reg */
+#define SFC_REG_STATUS_NX_ON_SHFT 28
+#define SFC_REG_STATUS_RWP (1 << 27)
+#define SFC_REG_STATUS_FOURBYTEAD (1 << 26)
+#define SFC_REG_STATUS_ILLEGAL (1 << 4)
+#define SFC_REG_STATUS_ECCERRCNTN (1 << 3)
+#define SFC_REG_STATUS_ECCUEN (1 << 2)
+#define SFC_REG_STATUS_DONE (1 << 0)
+
+#define SFC_REG_CMD 0x40 /* CMD : Command */
+#define SFC_REG_CMD_OPCODE_SHFT 9
+#define SFC_REG_CMD_LENGTH_SHFT 0
+
+#define SFC_REG_SPICLK 0x3C /* SPICLK: SPI clock rate config */
+#define SFC_REG_SPICLK_OUTDLY_SHFT 24
+#define SFC_REG_SPICLK_INSAMPDLY_SHFT 16
+#define SFC_REG_SPICLK_CLKHI_SHFT 8
+#define SFC_REG_SPICLK_CLKLO_SHFT 0
+
+#define SFC_REG_ADR 0x44 /* ADR : Address */
+#define SFC_REG_ERASMS 0x48 /* ERASMS : Small Erase Block Size */
+#define SFC_REG_ERASLGS 0x4C /* ERALGS : Large Erase Block Size */
+#define SFC_REG_CONF4 0x54 /* CONF4 : SPI Op Code for Small Erase */
+#define SFC_REG_CONF5 0x58 /* CONF5 : Small Erase Size config reg */
+
+#define SFC_REG_CONF8 0x64 /* CONF8 : Read Command */
+#define SFC_REG_CONF8_CSINACTIVERD_SHFT 18
+#define SFC_REG_CONF8_DUMMY_SHFT 8
+#define SFC_REG_CONF8_READOP_SHFT 0
+
+#define SFC_REG_ADRCBF 0x80 /* ADRCBF : First Intf NOR Addr Offset */
+#define SFC_REG_ADRCMF 0x84 /* ADRCMF : First Intf NOR Allocation */
+#define SFC_REG_ADRCBS 0x88 /* ADRCBS : Second Intf NOR Addr Offset */
+#define SFC_REG_ADRCMS 0x8C /* ADRCMS : Second Intf NOR Allocation */
+#define SFC_REG_OADRNB 0x90 /* OADRNB : Direct Access OBP Window Base Address */
+#define SFC_REG_OADRNS 0x94 /* OADRNS : DIrect Access OPB Window Size */
+
+#define SFC_REG_CHIPIDCONF 0x9C /* CHIPIDCONF : config ChipId CMD */
+#define SFC_REG_CHIPIDCONF_OPCODE_SHFT 24
+#define SFC_REG_CHIPIDCONF_READ (1 << 23)
+#define SFC_REG_CHIPIDCONF_WRITE (1 << 22)
+#define SFC_REG_CHIPIDCONF_USE_ADDR (1 << 21)
+#define SFC_REG_CHIPIDCONF_DUMMY_SHFT 16
+#define SFC_REG_CHIPIDCONF_LEN_SHFT 0
+
+/*
+ * SFC Opcodes
+ */
+#define SFC_OP_READRAW 0x03 /* Read Raw */
+#define SFC_OP_WRITERAW 0x02 /* Write Raw */
+#define SFC_OP_ERASM 0x32 /* Erase Small */
+#define SFC_OP_ERALG 0x34 /* Erase Large */
+#define SFC_OP_ENWRITPROT 0x53 /* Enable WRite Protect */
+#define SFC_OP_CHIPID 0x1F /* Get Chip ID */
+#define SFC_OP_STATUS 0x05 /* Get Status */
+#define SFC_OP_TURNOFF 0x5E /* Turn Off */
+#define SFC_OP_TURNON 0x50 /* Turn On */
+#define SFC_OP_ABORT 0x6F /* Super-Abort */
+#define SFC_OP_START4BA 0x37 /* Start 4BA */
+#define SFC_OP_END4BA 0x69 /* End 4BA */
+
+/* Command buffer size */
+#define SFC_CMDBUF_SIZE 256
+
+struct sfc_ctrl {
+ /* Erase sizes */
+ uint32_t small_er_size;
+ uint32_t large_er_size;
+
+ /* Current 4b mode */
+ bool mode_4b;
+
+ /* Callbacks */
+ struct spi_flash_ctrl ops;
+};
+
+/* Command register support */
+static inline int sfc_reg_read(uint8_t reg, uint32_t *val)
+{
+ uint32_t tmp;
+ int rc;
+
+ *val = 0xffffffff;
+ rc = lpc_fw_read32(&tmp, SFC_CMDREG_OFFSET + reg);
+ if (rc)
+ return rc;
+ *val = be32_to_cpu(tmp);
+ return 0;
+}
+
+static inline int sfc_reg_write(uint8_t reg, uint32_t val)
+{
+ return lpc_fw_write32(cpu_to_be32(val), SFC_CMDREG_OFFSET + reg);
+}
+
+static int sfc_buf_write(uint32_t len, const void *data)
+{
+ uint32_t tmp, off = 0;
+ int rc;
+
+ if (len > SFC_CMDBUF_SIZE)
+ return FLASH_ERR_PARM_ERROR;
+
+ while (len >= 4) {
+ tmp = *(const uint32_t *)data;
+ rc = lpc_fw_write32(tmp, SFC_CMDBUF_OFFSET + off);
+ if (rc)
+ return rc;
+ off += 4;
+ len -= 4;
+ data += 4;
+ }
+ if (!len)
+ return 0;
+
+ /* lpc_fw_write operates on BE values so that's what we layout
+ * in memory with memcpy. The swap in the register on LE doesn't
+ * matter, the result in memory will be in the right order.
+ */
+ tmp = -1;
+ memcpy(&tmp, data, len);
+ return lpc_fw_write32(tmp, SFC_CMDBUF_OFFSET + off);
+}
+
+static int sfc_buf_read(uint32_t len, void *data)
+{
+ uint32_t tmp, off = 0;
+ int rc;
+
+ if (len > SFC_CMDBUF_SIZE)
+ return FLASH_ERR_PARM_ERROR;
+
+ while (len >= 4) {
+ rc = lpc_fw_read32(data, SFC_CMDBUF_OFFSET + off);
+ if (rc)
+ return rc;
+ off += 4;
+ len -= 4;
+ data += 4;
+ }
+ if (!len)
+ return 0;
+
+ rc = lpc_fw_read32(&tmp, SFC_CMDBUF_OFFSET + off);
+ if (rc)
+ return rc;
+ /* We know tmp contains a big endian value, so memcpy is
+ * our friend here
+ */
+ memcpy(data, &tmp, len);
+ return 0;
+}
+
+/* Polls until SFC indicates command is complete */
+static int sfc_poll_complete(void)
+{
+ uint32_t status, timeout;
+ struct timespec ts;
+
+ /*
+ * A full 256 bytes read/write command will take at least
+ * 126us. Smaller commands are faster but we use less of
+ * them. So let's sleep in increments of 100us
+ */
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100000;
+
+ /*
+ * Use a 1s timeout which should be sufficient for the
+ * commands we use
+ */
+ timeout = 10000;
+
+ do {
+ int rc;
+
+ rc = sfc_reg_read(SFC_REG_STATUS, &status);
+ if (rc)
+ return rc;
+ if (status & SFC_REG_STATUS_DONE)
+ break;
+ if (--timeout == 0)
+ return FLASH_ERR_CTRL_TIMEOUT;
+ nanosleep(&ts, NULL);
+ } while (true);
+
+ return 0;
+}
+
+static int sfc_exec_command(uint8_t opcode, uint32_t length)
+{
+ int rc = 0;
+ uint32_t cmd_reg = 0;
+
+ if (opcode > 0x7f || length > 0x1ff)
+ return FLASH_ERR_PARM_ERROR;
+
+ /* Write command register to start execution */
+ cmd_reg |= (opcode << SFC_REG_CMD_OPCODE_SHFT);
+ cmd_reg |= (length << SFC_REG_CMD_LENGTH_SHFT);
+ rc = sfc_reg_write(SFC_REG_CMD, cmd_reg);
+ if (rc)
+ return rc;
+
+ /* Wait for command to complete */
+ return sfc_poll_complete();
+}
+
+static int sfc_chip_id(struct spi_flash_ctrl *ctrl, uint8_t *id_buf,
+ uint32_t *id_size)
+{
+ uint32_t idconf;
+ int rc;
+
+ (void)ctrl;
+
+ if ((*id_size) < 3)
+ return FLASH_ERR_PARM_ERROR;
+
+ /*
+ * XXX This will not work in locked down mode but we assume that
+ * in this case, the chip ID command is already properly programmed
+ * and the SFC will ignore this. However I haven't verified...
+ */
+ idconf = ((uint64_t)CMD_RDID) << SFC_REG_CHIPIDCONF_OPCODE_SHFT;
+ idconf |= SFC_REG_CHIPIDCONF_READ;
+ idconf |= (3ul << SFC_REG_CHIPIDCONF_LEN_SHFT);
+ (void)sfc_reg_write(SFC_REG_CHIPIDCONF, idconf);
+
+ /* Perform command */
+ rc = sfc_exec_command(SFC_OP_CHIPID, 0);
+ if (rc)
+ return rc;
+
+ /* Read chip ID */
+ rc = sfc_buf_read(3, id_buf);
+ if (rc)
+ return rc;
+ *id_size = 3;
+
+ return 0;
+}
+
+
+static int sfc_read(struct spi_flash_ctrl *ctrl, uint32_t pos,
+ void *buf, uint32_t len)
+{
+ (void)ctrl;
+
+ while(len) {
+ uint32_t chunk = len;
+ int rc;
+
+ if (chunk > SFC_CMDBUF_SIZE)
+ chunk = SFC_CMDBUF_SIZE;
+ rc = sfc_reg_write(SFC_REG_ADR, pos);
+ if (rc)
+ return rc;
+ rc = sfc_exec_command(SFC_OP_READRAW, chunk);
+ if (rc)
+ return rc;
+ rc = sfc_buf_read(chunk, buf);
+ if (rc)
+ return rc;
+ len -= chunk;
+ pos += chunk;
+ buf += chunk;
+ }
+ return 0;
+}
+
+static int sfc_write(struct spi_flash_ctrl *ctrl, uint32_t addr,
+ const void *buf, uint32_t size)
+{
+ uint32_t chunk;
+ int rc;
+
+ (void)ctrl;
+
+ while(size) {
+ /* We shall not cross a page boundary */
+ chunk = 0x100 - (addr & 0xff);
+ if (chunk > size)
+ chunk = size;
+
+ /* Write to SFC write buffer */
+ rc = sfc_buf_write(chunk, buf);
+ if (rc)
+ return rc;
+
+ /* Program address */
+ rc = sfc_reg_write(SFC_REG_ADR, addr);
+ if (rc)
+ return rc;
+
+ /* Send command */
+ rc = sfc_exec_command(SFC_OP_WRITERAW, chunk);
+ if (rc)
+ return rc;
+
+ addr += chunk;
+ buf += chunk;
+ size -= chunk;
+ }
+ return 0;
+}
+
+static int sfc_erase(struct spi_flash_ctrl *ctrl, uint32_t addr,
+ uint32_t size)
+{
+ struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);
+ uint32_t sm_mask = ct->small_er_size - 1;
+ uint32_t lg_mask = ct->large_er_size - 1;
+ uint32_t chunk;
+ uint8_t cmd;
+ int rc;
+
+ while(size) {
+ /* Choose erase size for this chunk */
+ if (((addr | size) & lg_mask) == 0) {
+ chunk = ct->large_er_size;
+ cmd = SFC_OP_ERALG;
+ } else if (((addr | size) & sm_mask) == 0) {
+ chunk = ct->small_er_size;
+ cmd = SFC_OP_ERASM;
+ } else
+ return FLASH_ERR_ERASE_BOUNDARY;
+
+ rc = sfc_reg_write(SFC_REG_ADR, addr);
+ if (rc)
+ return rc;
+ rc = sfc_exec_command(cmd, 0);
+ if (rc)
+ return rc;
+ addr += chunk;
+ size -= chunk;
+ }
+ return 0;
+}
+
+static int sfc_setup(struct spi_flash_ctrl *ctrl, struct flash_info *info,
+ uint32_t *tsize)
+{
+ struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);
+ uint32_t er_flags;
+
+ (void)tsize;
+
+ /* Keep non-erase related flags */
+ er_flags = ~FL_ERASE_ALL;
+
+ /* Add supported erase sizes */
+ if (ct->small_er_size == 0x1000 || ct->large_er_size == 0x1000)
+ er_flags |= FL_ERASE_4K;
+ if (ct->small_er_size == 0x8000 || ct->large_er_size == 0x8000)
+ er_flags |= FL_ERASE_32K;
+ if (ct->small_er_size == 0x10000 || ct->large_er_size == 0x10000)
+ er_flags |= FL_ERASE_64K;
+
+ /* Mask the flags out */
+ info->flags &= er_flags;
+
+ return 0;
+}
+
+static int sfc_set_4b(struct spi_flash_ctrl *ctrl, bool enable)
+{
+ struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);
+ int rc;
+
+ rc = sfc_exec_command(enable ? SFC_OP_START4BA : SFC_OP_END4BA, 0);
+ if (rc)
+ return rc;
+ ct->mode_4b = enable;
+ return 0;
+}
+
+static void sfc_validate_er_size(uint32_t *size)
+{
+ if (*size == 0)
+ return;
+
+ /* We only support 4k, 32k and 64k */
+ if (*size != 0x1000 && *size != 0x8000 && *size != 0x10000) {
+ FL_ERR("SFC: Erase size %d bytes unsupported\n", *size);
+ *size = 0;
+ }
+}
+
+static int sfc_init(struct sfc_ctrl *ct)
+{
+ int rc;
+ uint32_t status;
+
+ /*
+ * Assumptions: The controller has been fully initialized
+ * by an earlier FW layer setting the chip ID command, the
+ * erase sizes, and configuring the timings for reads and
+ * writes.
+ *
+ * This driver is meant to be usable if the configuration
+ * is in lock down.
+ *
+ * If that wasn't the case, we could configure some sane
+ * defaults here and tuned values in setup() after the
+ * chip has been identified.
+ */
+
+ /* Read erase sizes from flash */
+ rc = sfc_reg_read(SFC_REG_ERASMS, &ct->small_er_size);
+ if (rc)
+ return rc;
+ sfc_validate_er_size(&ct->small_er_size);
+ rc = sfc_reg_read(SFC_REG_ERASLGS, &ct->large_er_size);
+ if (rc)
+ return rc;
+ sfc_validate_er_size(&ct->large_er_size);
+
+ /* No erase sizes we can cope with ? Ouch... */
+ if ((ct->small_er_size == 0 && ct->large_er_size == 0) ||
+ (ct->large_er_size && (ct->small_er_size > ct->large_er_size))) {
+ FL_ERR("SFC: No supported erase sizes !\n");
+ return FLASH_ERR_CTRL_CONFIG_MISMATCH;
+ }
+
+ FL_INF("SFC: Suppored erase sizes:");
+ if (ct->small_er_size)
+ FL_INF(" %dKB", ct->small_er_size >> 10);
+ if (ct->large_er_size)
+ FL_INF(" %dKB", ct->large_er_size >> 10);
+ FL_INF("\n");
+
+ /* Read current state of 4 byte addressing */
+ rc = sfc_reg_read(SFC_REG_STATUS, &status);
+ if (rc)
+ return rc;
+ ct->mode_4b = !!(status & SFC_REG_STATUS_FOURBYTEAD);
+
+ return 0;
+}
+
+int sfc_open(struct spi_flash_ctrl **ctrl)
+{
+ struct sfc_ctrl *ct;
+ int rc;
+
+ *ctrl = NULL;
+ ct = malloc(sizeof(*ct));
+ if (!ct) {
+ FL_ERR("SFC: Failed to allocate\n");
+ return FLASH_ERR_MALLOC_FAILED;
+ }
+ memset(ct, 0, sizeof(*ct));
+ ct->ops.chip_id = sfc_chip_id;
+ ct->ops.setup = sfc_setup;
+ ct->ops.set_4b = sfc_set_4b;
+ ct->ops.read = sfc_read;
+ ct->ops.write = sfc_write;
+ ct->ops.erase = sfc_erase;
+
+ rc = sfc_init(ct);
+ if (rc)
+ goto fail;
+ *ctrl = &ct->ops;
+ return 0;
+ fail:
+ free(ct);
+ return rc;
+}
+
+void sfc_close(struct spi_flash_ctrl *ctrl)
+{
+ struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops);
+
+ /* Free the whole lot */
+ free(ct);
+}
+