diff options
Diffstat (limited to 'sys/contrib/octeon-sdk/cvmx-flash.c')
-rw-r--r-- | sys/contrib/octeon-sdk/cvmx-flash.c | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/sys/contrib/octeon-sdk/cvmx-flash.c b/sys/contrib/octeon-sdk/cvmx-flash.c new file mode 100644 index 0000000..cb53027 --- /dev/null +++ b/sys/contrib/octeon-sdk/cvmx-flash.c @@ -0,0 +1,674 @@ +/***********************license start*************** + * Copyright (c) 2003-2010 Cavium Inc. (support@cavium.com). All rights + * reserved. + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + + * * Neither the name of Cavium Inc. nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + + * This Software, including technical data, may be subject to U.S. export control + * laws, including the U.S. Export Administration Act and its associated + * regulations, and may be subject to export or import regulations in other + * countries. + + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" + * AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR + * WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT TO + * THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY REPRESENTATION OR + * DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT DEFECTS, AND CAVIUM + * SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES OF TITLE, + * MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR PURPOSE, LACK OF + * VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, QUIET POSSESSION OR + * CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK ARISING OUT OF USE OR + * PERFORMANCE OF THE SOFTWARE LIES WITH YOU. + ***********************license end**************************************/ + + + + + + + +/** + * @file + * + * This file provides bootbus flash operations + * + * <hr>$Revision: 70030 $<hr> + * + * + */ + +#include "cvmx-config.h" +#include "cvmx.h" +#include "cvmx-sysinfo.h" +#include "cvmx-spinlock.h" +#include "cvmx-flash.h" + +#define MAX_NUM_FLASH_CHIPS 8 /* Maximum number of flash chips */ +#define MAX_NUM_REGIONS 8 /* Maximum number of block regions per chip */ +#define DEBUG 1 + +#define CFI_CMDSET_NONE 0 +#define CFI_CMDSET_INTEL_EXTENDED 1 +#define CFI_CMDSET_AMD_STANDARD 2 +#define CFI_CMDSET_INTEL_STANDARD 3 +#define CFI_CMDSET_AMD_EXTENDED 4 +#define CFI_CMDSET_MITSU_STANDARD 256 +#define CFI_CMDSET_MITSU_EXTENDED 257 +#define CFI_CMDSET_SST 258 + +typedef struct +{ + void * base_ptr; /**< Memory pointer to start of flash */ + int is_16bit; /**< Chip is 16bits wide in 8bit mode */ + uint16_t vendor; /**< Vendor ID of Chip */ + int size; /**< Size of the chip in bytes */ + uint64_t erase_timeout; /**< Erase timeout in cycles */ + uint64_t write_timeout; /**< Write timeout in cycles */ + int num_regions; /**< Number of block regions */ + cvmx_flash_region_t region[MAX_NUM_REGIONS]; +} cvmx_flash_t; + +static CVMX_SHARED cvmx_flash_t flash_info[MAX_NUM_FLASH_CHIPS]; +static CVMX_SHARED cvmx_spinlock_t flash_lock = CVMX_SPINLOCK_UNLOCKED_INITIALIZER; + + +/** + * @INTERNAL + * Read a byte from flash + * + * @param chip_id Chip to read from + * @param offset Offset into the chip + * @return Value read + */ +static uint8_t __cvmx_flash_read8(int chip_id, int offset) +{ + return *(volatile uint8_t *)(flash_info[chip_id].base_ptr + offset); +} + + +/** + * @INTERNAL + * Read a byte from flash (for commands) + * + * @param chip_id Chip to read from + * @param offset Offset into the chip + * @return Value read + */ +static uint8_t __cvmx_flash_read_cmd(int chip_id, int offset) +{ + if (flash_info[chip_id].is_16bit) + offset<<=1; + return __cvmx_flash_read8(chip_id, offset); +} + + +/** + * @INTERNAL + * Read 16bits from flash (for commands) + * + * @param chip_id Chip to read from + * @param offset Offset into the chip + * @return Value read + */ +static uint16_t __cvmx_flash_read_cmd16(int chip_id, int offset) +{ + uint16_t v = __cvmx_flash_read_cmd(chip_id, offset); + v |= __cvmx_flash_read_cmd(chip_id, offset + 1)<<8; + return v; +} + + +/** + * @INTERNAL + * Write a byte to flash + * + * @param chip_id Chip to write to + * @param offset Offset into the chip + * @param data Value to write + */ +static void __cvmx_flash_write8(int chip_id, int offset, uint8_t data) +{ + volatile uint8_t *flash_ptr = (volatile uint8_t *)flash_info[chip_id].base_ptr; + flash_ptr[offset] = data; +} + + +/** + * @INTERNAL + * Write a byte to flash (for commands) + * + * @param chip_id Chip to write to + * @param offset Offset into the chip + * @param data Value to write + */ +static void __cvmx_flash_write_cmd(int chip_id, int offset, uint8_t data) +{ + volatile uint8_t *flash_ptr = (volatile uint8_t *)flash_info[chip_id].base_ptr; + flash_ptr[offset<<flash_info[chip_id].is_16bit] = data; +} + + +/** + * @INTERNAL + * Query a address and see if a CFI flash chip is there. + * + * @param chip_id Chip ID data to fill in if the chip is there + * @param base_ptr Memory pointer to the start address to query + * @return Zero on success, Negative on failure + */ +static int __cvmx_flash_queury_cfi(int chip_id, void *base_ptr) +{ + int region; + cvmx_flash_t *flash = flash_info + chip_id; + + /* Set the minimum needed for the read and write primitives to work */ + flash->base_ptr = base_ptr; + flash->is_16bit = 1; /* FIXME: Currently assumes the chip is 16bits */ + + /* Put flash in CFI query mode */ + __cvmx_flash_write_cmd(chip_id, 0x00, 0xf0); /* Reset the flash chip */ + __cvmx_flash_write_cmd(chip_id, 0x55, 0x98); + + /* Make sure we get the QRY response we should */ + if ((__cvmx_flash_read_cmd(chip_id, 0x10) != 'Q') || + (__cvmx_flash_read_cmd(chip_id, 0x11) != 'R') || + (__cvmx_flash_read_cmd(chip_id, 0x12) != 'Y')) + { + flash->base_ptr = NULL; + return -1; + } + + /* Read the 16bit vendor ID */ + flash->vendor = __cvmx_flash_read_cmd16(chip_id, 0x13); + + /* Read the write timeout. The timeout is microseconds(us) is 2^0x1f + typically. The worst case is this value time 2^0x23 */ + flash->write_timeout = 1ull << (__cvmx_flash_read_cmd(chip_id, 0x1f) + + __cvmx_flash_read_cmd(chip_id, 0x23)); + + /* Read the erase timeout. The timeout is milliseconds(ms) is 2^0x21 + typically. The worst case is this value time 2^0x25 */ + flash->erase_timeout = 1ull << (__cvmx_flash_read_cmd(chip_id, 0x21) + + __cvmx_flash_read_cmd(chip_id, 0x25)); + + /* Get the flash size. This is 2^0x27 */ + flash->size = 1<<__cvmx_flash_read_cmd(chip_id, 0x27); + + /* Get the number of different sized block regions from 0x2c */ + flash->num_regions = __cvmx_flash_read_cmd(chip_id, 0x2c); + + int start_offset = 0; + /* Loop through all regions get information about each */ + for (region=0; region<flash->num_regions; region++) + { + cvmx_flash_region_t *rgn_ptr = flash->region + region; + rgn_ptr->start_offset = start_offset; + + /* The number of blocks in each region is a 16 bit little endian + endian field. It is encoded at 0x2d + region*4 as (blocks-1) */ + uint16_t blocks = __cvmx_flash_read_cmd16(chip_id, 0x2d + region*4); + rgn_ptr->num_blocks = 1u + blocks; + + /* The size of each block is a 16 bit little endian endian field. It + is encoded at 0x2d + region*4 + 2 as (size/256). Zero is a special + case representing 128 */ + uint16_t size = __cvmx_flash_read_cmd16(chip_id, 0x2d + region*4 + 2); + if (size == 0) + rgn_ptr->block_size = 128; + else + rgn_ptr->block_size = 256u * size; + + start_offset += rgn_ptr->block_size * rgn_ptr->num_blocks; + } + + /* Take the chip out of CFI query mode */ + switch (flash_info[chip_id].vendor) + { + case CFI_CMDSET_AMD_STANDARD: + __cvmx_flash_write_cmd(chip_id, 0x00, 0xf0); + case CFI_CMDSET_INTEL_STANDARD: + case CFI_CMDSET_INTEL_EXTENDED: + __cvmx_flash_write_cmd(chip_id, 0x00, 0xff); + break; + } + + /* Convert the timeouts to cycles */ + flash->write_timeout *= cvmx_clock_get_rate(CVMX_CLOCK_CORE) / 1000000; + flash->erase_timeout *= cvmx_clock_get_rate(CVMX_CLOCK_CORE) / 1000; + +#if DEBUG + /* Print the information about the chip */ + cvmx_dprintf("cvmx-flash: Base pointer: %p\n" + " Vendor: 0x%04x\n" + " Size: %d bytes\n" + " Num regions: %d\n" + " Erase timeout: %llu cycles\n" + " Write timeout: %llu cycles\n", + flash->base_ptr, + (unsigned int)flash->vendor, + flash->size, + flash->num_regions, + (unsigned long long)flash->erase_timeout, + (unsigned long long)flash->write_timeout); + + for (region=0; region<flash->num_regions; region++) + { + cvmx_dprintf(" Region %d: offset 0x%x, %d blocks, %d bytes/block\n", + region, + flash->region[region].start_offset, + flash->region[region].num_blocks, + flash->region[region].block_size); + } +#endif + + return 0; +} + + +/** + * Initialize the flash access library + */ +void cvmx_flash_initialize(void) +{ + int boot_region; + int chip_id = 0; + + memset(flash_info, 0, sizeof(flash_info)); + + /* Loop through each boot bus chip select region */ + for (boot_region=0; boot_region<MAX_NUM_FLASH_CHIPS; boot_region++) + { + cvmx_mio_boot_reg_cfgx_t region_cfg; + region_cfg.u64 = cvmx_read_csr(CVMX_MIO_BOOT_REG_CFG0 + boot_region*8); + /* Only try chip select regions that are enabled. This assumes the + bootloader already setup the flash */ + if (region_cfg.s.en) + { + /* Convert the hardware address to a pointer. Note that the bootbus, + unlike memory, isn't 1:1 mapped in the simple exec */ + void *base_ptr = cvmx_phys_to_ptr((region_cfg.s.base<<16) | 0xffffffff80000000ull); + if (__cvmx_flash_queury_cfi(chip_id, base_ptr) == 0) + { + /* Valid CFI flash chip found */ + chip_id++; + } + } + } + + if (chip_id == 0) + cvmx_dprintf("cvmx-flash: No CFI chips found\n"); +} + + +/** + * Return a pointer to the flash chip + * + * @param chip_id Chip ID to return + * @return NULL if the chip doesn't exist + */ +void *cvmx_flash_get_base(int chip_id) +{ + return flash_info[chip_id].base_ptr; +} + + +/** + * Return the number of erasable regions on the chip + * + * @param chip_id Chip to return info for + * @return Number of regions + */ +int cvmx_flash_get_num_regions(int chip_id) +{ + return flash_info[chip_id].num_regions; +} + + +/** + * Return information about a flash chips region + * + * @param chip_id Chip to get info for + * @param region Region to get info for + * @return Region information + */ +const cvmx_flash_region_t *cvmx_flash_get_region_info(int chip_id, int region) +{ + return flash_info[chip_id].region + region; +} + + +/** + * Erase a block on the flash chip + * + * @param chip_id Chip to erase a block on + * @param region Region to erase a block in + * @param block Block number to erase + * @return Zero on success. Negative on failure + */ +int cvmx_flash_erase_block(int chip_id, int region, int block) +{ + cvmx_spinlock_lock(&flash_lock); +#if DEBUG + cvmx_dprintf("cvmx-flash: Erasing chip %d, region %d, block %d\n", + chip_id, region, block); +#endif + + int offset = flash_info[chip_id].region[region].start_offset + + block * flash_info[chip_id].region[region].block_size; + + switch (flash_info[chip_id].vendor) + { + case CFI_CMDSET_AMD_STANDARD: + { + /* Send the erase sector command sequence */ + __cvmx_flash_write_cmd(chip_id, 0x00, 0xf0); /* Reset the flash chip */ + __cvmx_flash_write_cmd(chip_id, 0x555, 0xaa); + __cvmx_flash_write_cmd(chip_id, 0x2aa, 0x55); + __cvmx_flash_write_cmd(chip_id, 0x555, 0x80); + __cvmx_flash_write_cmd(chip_id, 0x555, 0xaa); + __cvmx_flash_write_cmd(chip_id, 0x2aa, 0x55); + __cvmx_flash_write8(chip_id, offset, 0x30); + + /* Loop checking status */ + uint8_t status = __cvmx_flash_read8(chip_id, offset); + uint64_t start_cycle = cvmx_get_cycle(); + while (1) + { + /* Read the status and xor it with the old status so we can + find toggling bits */ + uint8_t old_status = status; + status = __cvmx_flash_read8(chip_id, offset); + uint8_t toggle = status ^ old_status; + + /* Check if the erase in progress bit is toggling */ + if (toggle & (1<<6)) + { + /* Check hardware timeout */ + if (status & (1<<5)) + { + /* Chip has signalled a timeout. Reread the status */ + old_status = __cvmx_flash_read8(chip_id, offset); + status = __cvmx_flash_read8(chip_id, offset); + toggle = status ^ old_status; + + /* Check if the erase in progress bit is toggling */ + if (toggle & (1<<6)) + { + cvmx_dprintf("cvmx-flash: Hardware timeout erasing block\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + else + break; /* Not toggling, erase complete */ + } + } + else + break; /* Not toggling, erase complete */ + + if (cvmx_get_cycle() > start_cycle + flash_info[chip_id].erase_timeout) + { + cvmx_dprintf("cvmx-flash: Timeout erasing block\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + } + + __cvmx_flash_write_cmd(chip_id, 0x00, 0xf0); /* Reset the flash chip */ + cvmx_spinlock_unlock(&flash_lock); + return 0; + } + case CFI_CMDSET_INTEL_STANDARD: + case CFI_CMDSET_INTEL_EXTENDED: + { + /* Send the erase sector command sequence */ + __cvmx_flash_write_cmd(chip_id, 0x00, 0xff); /* Reset the flash chip */ + __cvmx_flash_write8(chip_id, offset, 0x20); + __cvmx_flash_write8(chip_id, offset, 0xd0); + + /* Loop checking status */ + uint8_t status = __cvmx_flash_read8(chip_id, offset); + uint64_t start_cycle = cvmx_get_cycle(); + while ((status & 0x80) == 0) + { + if (cvmx_get_cycle() > start_cycle + flash_info[chip_id].erase_timeout) + { + cvmx_dprintf("cvmx-flash: Timeout erasing block\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + status = __cvmx_flash_read8(chip_id, offset); + } + + /* Check the final status */ + if (status & 0x7f) + { + cvmx_dprintf("cvmx-flash: Hardware failure erasing block\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + + __cvmx_flash_write_cmd(chip_id, 0x00, 0xff); /* Reset the flash chip */ + cvmx_spinlock_unlock(&flash_lock); + return 0; + } + } + + cvmx_dprintf("cvmx-flash: Unsupported flash vendor\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; +} + + +/** + * Write a block on the flash chip + * + * @param chip_id Chip to write a block on + * @param region Region to write a block in + * @param block Block number to write + * @param data Data to write + * @return Zero on success. Negative on failure + */ +int cvmx_flash_write_block(int chip_id, int region, int block, const void *data) +{ + cvmx_spinlock_lock(&flash_lock); +#if DEBUG + cvmx_dprintf("cvmx-flash: Writing chip %d, region %d, block %d\n", + chip_id, region, block); +#endif + int offset = flash_info[chip_id].region[region].start_offset + + block * flash_info[chip_id].region[region].block_size; + int len = flash_info[chip_id].region[region].block_size; + const uint8_t *ptr = (const uint8_t *)data; + + switch (flash_info[chip_id].vendor) + { + case CFI_CMDSET_AMD_STANDARD: + { + /* Loop through one byte at a time */ + while (len--) + { + /* Send the program sequence */ + __cvmx_flash_write_cmd(chip_id, 0x00, 0xf0); /* Reset the flash chip */ + __cvmx_flash_write_cmd(chip_id, 0x555, 0xaa); + __cvmx_flash_write_cmd(chip_id, 0x2aa, 0x55); + __cvmx_flash_write_cmd(chip_id, 0x555, 0xa0); + __cvmx_flash_write8(chip_id, offset, *ptr); + + /* Loop polling for status */ + uint64_t start_cycle = cvmx_get_cycle(); + while (1) + { + uint8_t status = __cvmx_flash_read8(chip_id, offset); + if (((status ^ *ptr) & (1<<7)) == 0) + break; /* Data matches, this byte is done */ + else if (status & (1<<5)) + { + /* Hardware timeout, recheck status */ + status = __cvmx_flash_read8(chip_id, offset); + if (((status ^ *ptr) & (1<<7)) == 0) + break; /* Data matches, this byte is done */ + else + { + cvmx_dprintf("cvmx-flash: Hardware write timeout\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + } + + if (cvmx_get_cycle() > start_cycle + flash_info[chip_id].write_timeout) + { + cvmx_dprintf("cvmx-flash: Timeout writing block\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + } + + /* Increment to the next byte */ + ptr++; + offset++; + } + + __cvmx_flash_write_cmd(chip_id, 0x00, 0xf0); /* Reset the flash chip */ + cvmx_spinlock_unlock(&flash_lock); + return 0; + } + case CFI_CMDSET_INTEL_STANDARD: + case CFI_CMDSET_INTEL_EXTENDED: + { +cvmx_dprintf("%s:%d len=%d\n", __FUNCTION__, __LINE__, len); + /* Loop through one byte at a time */ + while (len--) + { + /* Send the program sequence */ + __cvmx_flash_write_cmd(chip_id, 0x00, 0xff); /* Reset the flash chip */ + __cvmx_flash_write8(chip_id, offset, 0x40); + __cvmx_flash_write8(chip_id, offset, *ptr); + + /* Loop polling for status */ + uint8_t status = __cvmx_flash_read8(chip_id, offset); + uint64_t start_cycle = cvmx_get_cycle(); + while ((status & 0x80) == 0) + { + if (cvmx_get_cycle() > start_cycle + flash_info[chip_id].write_timeout) + { + cvmx_dprintf("cvmx-flash: Timeout writing block\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + status = __cvmx_flash_read8(chip_id, offset); + } + + /* Check the final status */ + if (status & 0x7f) + { + cvmx_dprintf("cvmx-flash: Hardware failure erasing block\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; + } + + /* Increment to the next byte */ + ptr++; + offset++; + } +cvmx_dprintf("%s:%d\n", __FUNCTION__, __LINE__); + + __cvmx_flash_write_cmd(chip_id, 0x00, 0xff); /* Reset the flash chip */ + cvmx_spinlock_unlock(&flash_lock); + return 0; + } + } + + cvmx_dprintf("cvmx-flash: Unsupported flash vendor\n"); + cvmx_spinlock_unlock(&flash_lock); + return -1; +} + + +/** + * Erase and write data to a flash + * + * @param address Memory address to write to + * @param data Data to write + * @param len Length of the data + * @return Zero on success. Negative on failure + */ +int cvmx_flash_write(void *address, const void *data, int len) +{ + int chip_id; + + /* Find which chip controls this address. Don't allow the write to span + multiple chips */ + for (chip_id=0; chip_id<MAX_NUM_FLASH_CHIPS; chip_id++) + { + if ((flash_info[chip_id].base_ptr <= address) && + (flash_info[chip_id].base_ptr + flash_info[chip_id].size >= address + len)) + break; + } + + if (chip_id == MAX_NUM_FLASH_CHIPS) + { + cvmx_dprintf("cvmx-flash: Unable to find chip that contains address %p\n", address); + return -1; + } + + cvmx_flash_t *flash = flash_info + chip_id; + + /* Determine which block region we need to start writing to */ + void *region_base = flash->base_ptr; + int region = 0; + while (region_base + flash->region[region].num_blocks * flash->region[region].block_size <= address) + { + region++; + region_base = flash->base_ptr + flash->region[region].start_offset; + } + + /* Determine which block in the region to start at */ + int block = (address - region_base) / flash->region[region].block_size; + + /* Require all writes to start on block boundries */ + if (address != region_base + block*flash->region[region].block_size) + { + cvmx_dprintf("cvmx-flash: Write address not aligned on a block boundry\n"); + return -1; + } + + /* Loop until we're out of data */ + while (len > 0) + { + /* Erase the current block */ + if (cvmx_flash_erase_block(chip_id, region, block)) + return -1; + /* Write the new data */ + if (cvmx_flash_write_block(chip_id, region, block, data)) + return -1; + + /* Increment to the next block */ + data += flash->region[region].block_size; + len -= flash->region[region].block_size; + block++; + if (block >= flash->region[region].num_blocks) + { + block = 0; + region++; + } + } + + return 0; +} + |