diff options
author | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
---|---|---|
committer | Angelos Mouzakitis <a.mouzakitis@virtualopensystems.com> | 2023-10-10 14:33:42 +0000 |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/hw/sfc-ctrl.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/hw/sfc-ctrl.c')
-rw-r--r-- | roms/skiboot/hw/sfc-ctrl.c | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/roms/skiboot/hw/sfc-ctrl.c b/roms/skiboot/hw/sfc-ctrl.c new file mode 100644 index 000000000..34b5b8e20 --- /dev/null +++ b/roms/skiboot/hw/sfc-ctrl.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2014 IBM Corp. */ + +#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) +{ + int rc; + + *val = 0xffffffff; + rc = lpc_fw_read32(val, SFC_CMDREG_OFFSET + reg); + if (rc) + return rc; + return 0; +} + +static inline int sfc_reg_write(uint8_t reg, uint32_t val) +{ + return lpc_fw_write32(val, SFC_CMDREG_OFFSET + reg); +} + +static int sfc_buf_write(uint32_t len, const void *data) +{ + __be32 tmp; + uint32_t off = 0; + int rc; + + if (len > SFC_CMDBUF_SIZE) + return FLASH_ERR_PARM_ERROR; + + while (len >= 4) { + tmp = cpu_to_be32(*(const uint32_t *)data); + rc = lpc_fw_write32((u32)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 = cpu_to_be32(-1); + memcpy(&tmp, data, len); /* XXX: is this right? */ + return lpc_fw_write32((u32)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, uint32_t *tsize) +{ + struct sfc_ctrl *ct = container_of(ctrl, struct sfc_ctrl, ops); + struct flash_info *info = ctrl->finfo; + 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); +} + |