ossc/ip/altera_epcq_controller_mod/HAL/src/altera_epcq_controller_mod.c

798 lines
24 KiB
C
Raw Normal View History

2016-02-22 23:03:50 +00:00
/******************************************************************************
* *
* License Agreement *
* *
* Copyright (c) 2015 Altera Corporation, San Jose, California, USA. *
* All rights reserved. *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
* DEALINGS IN THE SOFTWARE. *
* *
* This agreement shall be governed in all respects by the laws of the State *
* of California and by the laws of the United States of America. *
* *
******************************************************************************/
#include <errno.h>
#include <io.h>
#include <string.h>
#include <stddef.h>
#include "sys/param.h"
#include "alt_types.h"
#include "altera_epcq_controller_mod_regs.h"
#include "altera_epcq_controller_mod.h"
#include "priv/alt_busy_sleep.h"
#include "sys/alt_debug.h"
#include "sys/alt_cache.h"
ALT_INLINE alt_32 static alt_epcq_validate_read_write_arguments(alt_epcq_controller_dev *flash_info,alt_u32 offset, alt_u32 length);
alt_32 static alt_epcq_poll_for_write_in_progress(alt_epcq_controller_dev* epcq_flash_info);
/*
* Public API
*
* Refer to Using Flash Devices in the
* Developing Programs Using the Hardware Abstraction Layer chapter
* of the Nios II Software Developers Handbook.
*/
/**
* alt_epcq_controller_lock
*
* Locks the range of the memory sectors, which
* protected from write and erase.
*
* Arguments:
* - *flash_info: Pointer to general flash device structure.
* - sectors_to_lock: Block protection bits in EPCQ ==> Bit4 | Bit3 | Bit2 | Bit1 | Bit0
* TB | BP3 | BP2 | BP1 | BP0
* For details of setting sectors protection, please refer to EPCQ datasheet.
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
* -ETIME -> Time out and skipping the looping after 0.7 sec.
* -ENOLCK -> Sectors lock failed.
**/
int alt_epcq_controller_lock(alt_flash_dev *flash_info, alt_u32 sectors_to_lock)
{
alt_u32 mem_op_value = 0; /* value to write to EPCQ_MEM_OP register */
alt_epcq_controller_dev* epcq_flash_info = NULL;
alt_u32 result = 0;
alt_32 status = 0;
/* return -EINVAL if flash_info is NULL */
if(NULL == flash_info || 0 > sectors_to_lock)
{
return -EINVAL;
}
epcq_flash_info = (alt_epcq_controller_dev*)flash_info;
/* sector value should occupy bits 17:8 */
mem_op_value = sectors_to_lock << 8;
/* sector protect commands 0b11 occupies lower 2 bits */
mem_op_value |= ALTERA_EPCQ_CONTROLLER_MEM_OP_SECTOR_PROTECT_CMD;
/* write sector protect command to EPCQ_MEM_OP register to protect sectors */
IOWR_ALTERA_EPCQ_CONTROLLER_MEM_OP(epcq_flash_info->csr_base, mem_op_value);
/* poll write in progress to make sure no operation is in progress */
status = alt_epcq_poll_for_write_in_progress(epcq_flash_info);
if(status != 0)
{
return status;
}
status = IORD_ALTERA_EPCQ_CONTROLLER_STATUS(epcq_flash_info->csr_base);
result |= (status >> 2) & 0x07; /* extract out BP3 - BP0 */
result |= (status >> 3) & 0x08; /* extract out BP4 */
result |= (status >> 1) & 0x10; /* extract out TOP/BOTTOM bit */
if(result != sectors_to_lock)
{
return -ENOLCK;
}
return 0;
}
/**
* alt_epcq_controller_get_info
*
* Pass the table of erase blocks to the user. This flash will return a single
* flash_region that gives the number and size of sectors for the device used.
*
* Arguments:
* - *fd: Pointer to general flash device structure.
* - **info: Pointer to flash region
* - *number_of_regions: Pointer to number of regions
*
* For details of setting sectors protection, please refer to EPCQ datasheet.
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
* -EIO -> Could be hardware problem.
**/
int alt_epcq_controller_get_info
(
alt_flash_fd *fd, /** flash device descriptor */
flash_region **info, /** pointer to flash_region will be stored here */
int *number_of_regions /** number of regions will be stored here */
)
{
alt_flash_dev* flash = NULL;
/* return -EINVAL if fd,info and number_of_regions are NULL */
if(NULL == fd || NULL == info || NULL == number_of_regions)
{
return -EINVAL;
}
flash = (alt_flash_dev*)fd;
*number_of_regions = flash->number_of_regions;
if (!flash->number_of_regions)
{
return -EIO;
}
else
{
*info = &flash->region_info[0];
}
return 0;
}
/**
* alt_epcq_controller_erase_block
*
* This function erases a single flash sector.
*
* Arguments:
* - *flash_info: Pointer to EPCQ flash device structure.
* - block_offset: byte-addressed offset, from start of flash, of the sector to be erased
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
* -EIO -> write failed, sector might be protected
**/
int alt_epcq_controller_erase_block(alt_flash_dev *flash_info, int block_offset)
{
alt_32 ret_code = 0;
alt_u32 mem_op_value = 0; /* value to write to EPCQ_MEM_OP register */
alt_epcq_controller_dev* epcq_flash_info = NULL;
alt_u32 sector_number = 0;
/* return -EINVAL if flash_info is NULL */
if(NULL == flash_info)
{
return -EINVAL;
}
epcq_flash_info = (alt_epcq_controller_dev*)flash_info;
/*
* Sanity checks that block_offset is within the flash memory span and that the
* block offset is sector aligned.
*
*/
if((block_offset < 0)
|| (block_offset >= epcq_flash_info->size_in_bytes)
|| (block_offset & (epcq_flash_info->sector_size - 1)) != 0)
{
return -EINVAL;
}
/* calculate current sector/block number */
sector_number = (block_offset/(epcq_flash_info->sector_size));
/* sector value should occupy bits 23:8 */
mem_op_value = (sector_number << 8) & ALTERA_EPCQ_CONTROLLER_MEM_OP_SECTOR_VALUE_MASK;
/* sector erase commands 0b10 occupies lower 2 bits */
mem_op_value |= ALTERA_EPCQ_CONTROLLER_MEM_OP_SECTOR_ERASE_CMD;
/* write sector erase command to EPCQ_MEM_OP register to erase sector "sector_number" */
IOWR_ALTERA_EPCQ_CONTROLLER_MEM_OP(epcq_flash_info->csr_base, mem_op_value);
/* check whether erase triggered a illegal erase interrupt */
if((IORD_ALTERA_EPCQ_CONTROLLER_ISR(epcq_flash_info->csr_base) &
ALTERA_EPCQ_CONTROLLER_ISR_ILLEGAL_ERASE_MASK) ==
ALTERA_EPCQ_CONTROLLER_ISR_ILLEGAL_ERASE_ACTIVE)
{
/* clear register */
/* EPCQ_ISR access is write one to clear (W1C) */
IOWR_ALTERA_EPCQ_CONTROLLER_ISR(epcq_flash_info->csr_base,
ALTERA_EPCQ_CONTROLLER_ISR_ILLEGAL_ERASE_MASK );
return -EIO; /* erase failed, sector might be protected */
}
return ret_code;
}
/**
* alt_epcq_controller_write_block
*
* This function writes one block/sector of data to flash. The length of the write can NOT
* spill into the adjacent sector.
*
* It assumes that someone has already erased the appropriate sector(s).
*
* Arguments:
* - *flash_info: Pointer to EPCQ flash device structure.
* - block_offset: byte-addressed offset, from the start of flash, of the sector to written to
* - data-offset: Byte offset (unaligned access) of write into flash memory.
* For best performance, word(32 bits - aligned access) offset of write is recommended.
* - *src_addr: source buffer
* - length: size of writing
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
* -EIO -> write failed, sector might be protected
**/
int alt_epcq_controller_write_block
(
alt_flash_dev *flash_info, /** flash device info */
int block_offset, /** sector/block offset in byte addressing */
int data_offset, /** offset of write from base address */
const void *data, /** data to be written */
int length /** bytes of data to be written, >0 */
)
{
alt_u32 buffer_offset = 0; /** offset into data buffer to get write data */
alt_u32 remaining_length = length; /** length left to write */
alt_u32 write_offset = data_offset; /** offset into flash to write too */
alt_epcq_controller_dev *epcq_flash_info = (alt_epcq_controller_dev*)flash_info;
/*
* Sanity checks that data offset is not larger then a sector, that block offset is
* sector aligned and within the valid flash memory range and a write doesn't spill into
* the adjacent flash sector.
*/
if(block_offset < 0
|| data_offset < 0
|| NULL == flash_info
|| NULL == data
|| data_offset >= epcq_flash_info->size_in_bytes
|| block_offset >= epcq_flash_info->size_in_bytes
|| length > (epcq_flash_info->sector_size - (data_offset - block_offset))
|| length < 0
|| (block_offset & (epcq_flash_info->sector_size - 1)) != 0)
{
return -EINVAL;
}
/*
* Do writes one 32-bit word at a time.
* We need to make sure that we pad the first few bytes so they're word aligned if they are
* not already.
*/
while (remaining_length > 0)
{
alt_u32 word_to_write = 0xFFFFFFFF; /** initialize word to write to blank word */
alt_u32 padding = 0; /** bytes to pad the next word that is written */
alt_u32 bytes_to_copy = sizeof(alt_u32); /** number of bytes from source to copy */
/*
* we need to make sure the write is word aligned
* this should only be true at most 1 time
*/
if (0 != (write_offset & (sizeof(alt_u32) - 1)))
{
/*
* data is not word aligned
* calculate padding bytes need to add before start of a data offset
*/
padding = write_offset & (sizeof(alt_u32) - 1);
/* update variables to account for padding being added */
bytes_to_copy -= padding;
if(bytes_to_copy > remaining_length)
{
bytes_to_copy = remaining_length;
}
write_offset = write_offset - padding;
if(0 != (write_offset & (sizeof(alt_u32) - 1)))
{
return -EINVAL;
}
}
else
{
if(bytes_to_copy > remaining_length)
{
bytes_to_copy = remaining_length;
}
}
/* prepare the word to be written */
memcpy((((void*)&word_to_write)) + padding, ((void*)data) + buffer_offset, bytes_to_copy);
/* update offset and length variables */
buffer_offset += bytes_to_copy;
remaining_length -= bytes_to_copy;
/* write to flash 32 bits at a time */
IOWR_32DIRECT(epcq_flash_info->data_base, write_offset, word_to_write);
/* check whether write triggered a illegal write interrupt */
if((IORD_ALTERA_EPCQ_CONTROLLER_ISR(epcq_flash_info->csr_base) &
ALTERA_EPCQ_CONTROLLER_ISR_ILLEGAL_WRITE_MASK) ==
ALTERA_EPCQ_CONTROLLER_ISR_ILLEGAL_WRITE_ACTIVE)
{
/* clear register */
IOWR_ALTERA_EPCQ_CONTROLLER_ISR(epcq_flash_info->csr_base,
ALTERA_EPCQ_CONTROLLER_ISR_ILLEGAL_WRITE_MASK );
return -EIO; /** write failed, sector might be protected */
}
/* update current offset */
write_offset = write_offset + sizeof(alt_u32);
}
return 0;
}
/**
* alt_epcq_controller_write
*
* Program the data into the flash at the selected address.
*
* The different between this function and alt_epcq_controller_write_block function
* is that this function (alt_epcq_controller_write) will automatically erase a block as needed
* Arguments:
* - *flash_info: Pointer to EPCQ flash device structure.
* - offset: Byte offset (unaligned access) of write to flash memory. For best performance,
* word(32 bits - aligned access) offset of write is recommended.
* - *src_addr: source buffer
* - length: size of writing
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
* -EIO -> write failed, sector might be protected
*
**/
int alt_epcq_controller_write(
alt_flash_dev *flash_info, /** device info */
int offset, /** offset of write from base address */
const void *src_addr, /** source buffer */
int length /** size of writing */
)
{
alt_32 ret_code = 0;
alt_epcq_controller_dev *epcq_flash_info = NULL;
alt_u32 write_offset = offset; /** address of next byte to write */
alt_u32 remaining_length = length; /** length of write data left to be written */
alt_u32 buffer_offset = 0; /** offset into source buffer to get write data */
alt_u32 i = 0;
/* return -EINVAL if flash_info and src_addr are NULL */
if(NULL == flash_info || NULL == src_addr)
{
return -EINVAL;
}
epcq_flash_info = (alt_epcq_controller_dev*)flash_info;
/* make sure the write parameters are within the bounds of the flash */
ret_code = alt_epcq_validate_read_write_arguments(epcq_flash_info, offset, length);
if(0 != ret_code)
{
return ret_code;
}
/*
* This loop erases and writes data one sector at a time. We check for write completion
* before starting the next sector.
*/
for(i = offset/epcq_flash_info->sector_size ; i < epcq_flash_info->number_of_sectors; i++)
{
alt_u32 block_offset = 0; /** block offset in byte addressing */
alt_u32 offset_within_current_sector = 0; /** offset into current sector to write */
alt_u32 length_to_write = 0; /** length to write to current sector */
if(0 >= remaining_length)
{
break; /* out of data to write */
}
/* calculate current sector/block offset in byte addressing */
block_offset = write_offset & ~(epcq_flash_info->sector_size - 1);
/* calculate offset into sector/block if there is one */
if(block_offset != write_offset)
{
offset_within_current_sector = write_offset - block_offset;
}
/* erase sector */
ret_code = alt_epcq_controller_erase_block(flash_info, block_offset);
if(0 != ret_code)
{
return ret_code;
}
/* calculate the byte size of data to be written in a sector */
length_to_write = MIN(epcq_flash_info->sector_size - offset_within_current_sector,
remaining_length);
/* write data to erased block */
ret_code = alt_epcq_controller_write_block(flash_info, block_offset, write_offset,
src_addr + buffer_offset, length_to_write);
if(0 != ret_code)
{
return ret_code;
}
/* update remaining length and buffer_offset pointer */
remaining_length -= length_to_write;
buffer_offset += length_to_write;
write_offset += length_to_write;
}
return ret_code;
}
/**
* alt_epcq_controller_read
*
* There's no real need to use this function as opposed to using memcpy directly. It does
* do some sanity checks on the bounds of the read.
*
* Arguments:
* - *flash_info: Pointer to general flash device structure.
* - offset: offset read from flash memory.
* - *dest_addr: destination buffer
* - length: size of reading
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
**/
int alt_epcq_controller_read
(
alt_flash_dev *flash_info, /** device info */
int offset, /** offset of read from base address */
void *dest_addr, /** destination buffer */
int length /** size of read */
)
{
alt_32 ret_code = 0;
alt_epcq_controller_dev *epcq_flash_info = NULL;
/* return -EINVAL if flash_info and dest_addr are NULL */
if(NULL == flash_info || NULL == dest_addr)
{
return -EINVAL;
}
epcq_flash_info = (alt_epcq_controller_dev*)flash_info;
/* validate arguments */
ret_code = alt_epcq_validate_read_write_arguments(epcq_flash_info, offset, length);
/* copy data from flash to destination address */
if(0 == ret_code)
{
memcpy(dest_addr, (alt_u8*)epcq_flash_info->data_base + offset, length);
}
return ret_code;
}
/**
* altera_epcq_controller_init
*
* alt_sys_init.c will call this function automatically through macro
*
* Information in system.h is checked against expected values that are determined by the silicon_id.
* If the information doesn't match then this system is configured incorrectly. Most likely the wrong
* type of EPCS or EPCQ device was selected when instantiating the soft IP.
*
* Arguments:
* - *flash: Pointer to EPCQ flash device structure.
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments.
* -ENODEV -> System is configured incorrectly.
**/
alt_32 altera_epcq_controller_init(alt_epcq_controller_dev *flash)
{
alt_u32 silicon_id = 0;
alt_u32 size_in_bytes = 0;
alt_u32 number_of_sectors = 0;
/* return -EINVAL if flash is NULL */
if(NULL == flash)
{
return -EINVAL;
}
/* return -ENODEV if CSR slave is not attached */
if(NULL == (void *)flash->csr_base)
{
return -ENODEV;
}
/*
* If flash is an EPCQ device, we read the EPCQ_RD_RDID register for the ID
* If flash is an EPCS device, we read the EPCQ_RD_SID register for the ID
*
* Whether or not the flash is a EPCQ or EPCS is indicated in the system.h. The system.h gets
* this value from the hw.tcl of the IP. If this value is set incorrectly, then things will go
* badly.
*
* In both cases, we can determine the number of sectors, which we can use
* to calculate a size. We compare that size to the system.h value to make sure
* the EPCQ soft IP was configured correctly.
*/
if(0 == flash->is_epcs)
{
/* If we're an EPCQ, we read EPCQ_RD_RDID for the silicon ID */
silicon_id = IORD_ALTERA_EPCQ_CONTROLLER_RDID(flash->csr_base);
silicon_id &= ALTERA_EPCQ_CONTROLLER_RDID_MASK;
/* Determine which EPCQ device so we can figure out the number of sectors */
/* EPCQ share the same ID for the same capacity*/
switch(silicon_id)
{
case ALTERA_EPCQ_CONTROLLER_RDID_EPCQ16:
{
number_of_sectors = 32;
break;
}
case ALTERA_EPCQ_CONTROLLER_RDID_EPCQ32:
{
number_of_sectors = 64;
break;
}
case ALTERA_EPCQ_CONTROLLER_RDID_EPCQ64:
{
number_of_sectors = 128;
break;
}
case ALTERA_EPCQ_CONTROLLER_RDID_EPCQ128:
{
number_of_sectors = 256;
break;
}
case ALTERA_EPCQ_CONTROLLER_RDID_EPCQ256:
{
number_of_sectors = 512;
break;
}
case ALTERA_EPCQ_CONTROLLER_RDID_EPCQ512:
{
number_of_sectors = 1024;
break;
}
case ALTERA_EPCQ_CONTROLLER_RDID_EPCQ1024:
{
number_of_sectors = 2048;
break;
}
default:
{
return -ENODEV;
}
}
}
else {
/* If we're an EPCS, we read EPCQ_RD_SID for the silicon ID */
silicon_id = IORD_ALTERA_EPCQ_CONTROLLER_SID(flash->csr_base);
silicon_id &= ALTERA_EPCQ_CONTROLLER_SID_MASK;
/* Determine which EPCS device so we can figure out various properties */
switch(silicon_id)
{
case ALTERA_EPCQ_CONTROLLER_SID_EPCS16:
{
number_of_sectors = 32;
break;
}
case ALTERA_EPCQ_CONTROLLER_SID_EPCS64:
{
number_of_sectors = 128;
break;
}
case ALTERA_EPCQ_CONTROLLER_SID_EPCS128:
{
number_of_sectors = 256;
break;
}
default:
{
return -ENODEV;
}
}
}
/* Calculate size of flash based on number of sectors */
size_in_bytes = number_of_sectors * flash->sector_size;
/*
* Make sure calculated size is the same size given in system.h
* Also check number of sectors is the same number given in system.h
* Otherwise the EPCQ IP was not configured correctly
*/
if( size_in_bytes != flash->size_in_bytes ||
number_of_sectors != flash->number_of_sectors)
{
flash->dev.number_of_regions = 0;
return -ENODEV;
}
else
{
flash->silicon_id = silicon_id;
flash->number_of_sectors = number_of_sectors;
/*
* populate fields of region_info required to conform to HAL API
* create 1 region that composed of "number_of_sectors" blocks
*/
flash->dev.number_of_regions = 1;
flash->dev.region_info[0].offset = 0;
flash->dev.region_info[0].region_size = size_in_bytes;
flash->dev.region_info[0].number_of_blocks = number_of_sectors;
flash->dev.region_info[0].block_size = flash->sector_size;
}
/*
* Register this device as a valid flash device type
*
* Only register the device if it's configured correctly.
*/
alt_flash_device_register(&(flash->dev));
return 0;
}
/*
* Private API
*
* Helper functions used by Public API functions.
*
* Arguments:
* - *flash_info: Pointer to EPCQ flash device structure.
* - offset: Offset of read/write from base address.
* - length: Length of read/write in bytes.
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
*/
/**
* Used to check that arguments to a read or write are valid
*/
ALT_INLINE alt_32 static alt_epcq_validate_read_write_arguments
(
alt_epcq_controller_dev *flash_info, /** device info */
alt_u32 offset, /** offset of read/write */
alt_u32 length /** length of read/write */
)
{
alt_epcq_controller_dev *epcq_flash_info = NULL;
alt_u32 start_address = 0;
alt_32 end_address = 0;
/* return -EINVAL if flash_info is NULL */
if(NULL == flash_info)
{
return -EINVAL;
}
epcq_flash_info = (alt_epcq_controller_dev*)flash_info;
start_address = epcq_flash_info->data_base + offset; /** first address of read or write */
end_address = start_address + length; /** last address of read or write (not inclusive) */
/* make sure start and end address is less then the end address of the flash */
if(
start_address >= epcq_flash_info->data_end ||
end_address >= epcq_flash_info->data_end ||
offset < 0 ||
length < 0
)
{
return -EINVAL;
}
return 0;
}
/*
* Private function that polls write in progress bit EPCQ_RD_STATUS.
*
* Write in progress will be set if any of the following operations are in progress:
* -WRITE STATUS REGISTER
* -WRITE NONVOLATILE CONFIGURATION REGISTER
* -PROGRAM
* -ERASE
*
* Assumes EPCQ was configured correctly.
*
* If ALTERA_EPCQ_CONTROLLER_1US_TIMEOUT_VALUE is set, the function will time out after
* a period of time determined by that value.
*
* Arguments:
* - *epcq_flash_info: Pointer to EPCQ flash device structure.
*
* Returns:
* 0 -> success
* -EINVAL -> Invalid arguments
* -ETIME -> Time out and skipping the looping after 0.7 sec.
*/
alt_32 static alt_epcq_poll_for_write_in_progress(alt_epcq_controller_dev* epcq_flash_info)
{
/* we'll want to implement timeout if a timeout value is specified */
#if ALTERA_EPCQ_CONTROLLER_1US_TIMEOUT_VALUE > 0
alt_u32 timeout = ALTERA_EPCQ_CONTROLLER_1US_TIMEOUT_VALUE;
alt_u16 counter = 0;
#endif
/* return -EINVAL if epcq_flash_info is NULL */
if(NULL == epcq_flash_info)
{
return -EINVAL;
}
/* while Write in Progress bit is set, we wait */
while((IORD_ALTERA_EPCQ_CONTROLLER_STATUS(epcq_flash_info->csr_base) &
ALTERA_EPCQ_CONTROLLER_STATUS_WIP_MASK) ==
ALTERA_EPCQ_CONTROLLER_STATUS_WIP_BUSY)
{
alt_busy_sleep(1); /* delay 1us */
#if ALTERA_EPCQ_CONTROLLER_1US_TIMEOUT_VALUE > 0
if(timeout <= counter )
{
return -ETIME;
}
counter++;
#endif
}
return 0;
}