text: /*
   * Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
   *
   * SPDX-License-Identifier: BSD-3-Clause
   */
  
  #include <errno.h>
  
  #include <drivers/cfi/v2m_flash.h>
  #include <lib/mmio.h>
  
  /*
   * This file supplies a low level interface to the vexpress NOR flash
   * memory of juno and fvp. This memory is organized as an interleaved
   * memory of two chips with a 16 bit word. It means that every 32 bit
   * access is going to access to two different chips. This is very
   * important when we send commands or read status of the chips.
   */
  
  /*
   * DWS ready poll retries. The number of retries in this driver have been
   * obtained empirically from Juno. FVP implements a zero wait state NOR flash
   * model
   */
  #define DWS_WORD_PROGRAM_RETRIES	1000
  #define DWS_WORD_ERASE_RETRIES		3000000
  #define DWS_WORD_LOCK_RETRIES		1000
  
  /* Helper macro to detect end of command */
  #define NOR_CMD_END (NOR_DWS | (NOR_DWS << 16l))
  
  /* Helper macros to access two flash banks in parallel */
  #define NOR_2X16(d)			((d << 16) | (d & 0xffff))
  
  static unsigned int nor_status(uintptr_t base_addr)
  {
  	unsigned long status;
  
  	nor_send_cmd(base_addr, NOR_CMD_READ_STATUS_REG);
  	status = mmio_read_32(base_addr);
  	status |= status >> 16; /* merge status from both flash banks */
  
  	return status & 0xFFFF;
  }
  
  /*
   * Poll Write State Machine.
   * Return values:
   *    0      = WSM ready
   *    -EBUSY = WSM busy after the number of retries
   */
  static int nor_poll_dws(uintptr_t base_addr, unsigned long int retries)
  {
  	unsigned long status;
  
  	do {
  		nor_send_cmd(base_addr, NOR_CMD_READ_STATUS_REG);
  		status = mmio_read_32(base_addr);
  		if ((status & NOR_CMD_END) == NOR_CMD_END)
  			return 0;
  	} while (retries-- > 0);
  
  	return -EBUSY;
  }
  
  /*
   * Return values:
   *    0      = success
   *    -EPERM = Device protected or Block locked
   *    -EIO   = General I/O error
   */
  static int nor_full_status_check(uintptr_t base_addr)
  {
  	unsigned long status;
  
  	/* Full status check */
  	status = nor_status(base_addr);
  
  	if (status & (NOR_PS | NOR_BLS | NOR_ESS | NOR_PSS))
  		return -EPERM;
  	if (status & (NOR_VPPS | NOR_ES))
  		return -EIO;
  	return 0;
  }
  
  void nor_send_cmd(uintptr_t base_addr, unsigned long cmd)
  {
  	mmio_write_32(base_addr, NOR_2X16(cmd));
  }
  
  /*
   * This function programs a word in the flash. Be aware that it only
   * can reset bits that were previously set. It cannot set bits that
   * were previously reset. The resulting bits = old_bits & new bits.
   * Return values:
   *  0 = success
   *  otherwise it returns a negative value
   */
  int nor_word_program(uintptr_t base_addr, unsigned long data)
  {
  	uint32_t status;
  	int ret;
  
  	nor_send_cmd(base_addr, NOR_CMD_CLEAR_STATUS_REG);
  
  	/* Set the device in write word mode */
  	nor_send_cmd(base_addr, NOR_CMD_WORD_PROGRAM);
  	mmio_write_32(base_addr, data);
  
  	ret = nor_poll_dws(base_addr, DWS_WORD_PROGRAM_RETRIES);
  	if (ret == 0) {
  		/* Full status check */
  		nor_send_cmd(base_addr, NOR_CMD_READ_STATUS_REG);
  		status = mmio_read_32(base_addr);
  
  		if (status & (NOR_PS | NOR_BLS)) {
  			nor_send_cmd(base_addr, NOR_CMD_CLEAR_STATUS_REG);
  			ret = -EPERM;
  		}
  	}
  
  	if (ret == 0)
  		ret = nor_full_status_check(base_addr);
  	nor_send_cmd(base_addr, NOR_CMD_READ_ARRAY);
  
  	return ret;
  }
  
  /*
   * Erase a full 256K block
   * Return values:
   *  0 = success
   *  otherwise it returns a negative value
   */
  int nor_erase(uintptr_t base_addr)
  {
  	int ret;
  
  	nor_send_cmd(base_addr, NOR_CMD_CLEAR_STATUS_REG);
  
  	nor_send_cmd(base_addr, NOR_CMD_BLOCK_ERASE);
  	nor_send_cmd(base_addr, NOR_CMD_BLOCK_ERASE_ACK);
  
  	ret = nor_poll_dws(base_addr, DWS_WORD_ERASE_RETRIES);
  	if (ret == 0)
  		ret = nor_full_status_check(base_addr);
  	nor_send_cmd(base_addr, NOR_CMD_READ_ARRAY);
  
  	return ret;
  }
  
  /*
   * Lock a full 256 block
   * Return values:
   *  0 = success
   *  otherwise it returns a negative value
   */
  int nor_lock(uintptr_t base_addr)
  {
  	int ret;
  
  	nor_send_cmd(base_addr, NOR_CMD_CLEAR_STATUS_REG);
  
  	nor_send_cmd(base_addr, NOR_CMD_LOCK_UNLOCK);
  	nor_send_cmd(base_addr, NOR_LOCK_BLOCK);
  
  	ret = nor_poll_dws(base_addr, DWS_WORD_LOCK_RETRIES);
  	if (ret == 0)
  		ret = nor_full_status_check(base_addr);
  	nor_send_cmd(base_addr, NOR_CMD_READ_ARRAY);
  
  	return ret;
  }
  
  /*
   * unlock a full 256 block
   * Return values:
   *  0 = success
   *  otherwise it returns a negative value
   */
  int nor_unlock(uintptr_t base_addr)
  {
  	int ret;
  
  	nor_send_cmd(base_addr, NOR_CMD_CLEAR_STATUS_REG);
  
  	nor_send_cmd(base_addr, NOR_CMD_LOCK_UNLOCK);
  	nor_send_cmd(base_addr, NOR_UNLOCK_BLOCK);
  
  	ret = nor_poll_dws(base_addr, DWS_WORD_LOCK_RETRIES);
  	if (ret == 0)
  		ret = nor_full_status_check(base_addr);
  	nor_send_cmd(base_addr, NOR_CMD_READ_ARRAY);
  
  	return ret;
  }
  